Drools: Accessing ArrayList as value in Map - java

I am trying to access the elements of the ArrayList that is the value of a map.
For example:
{"height": [-10,20]} and I am trying to get the individual value say "-10" in order to make a comparison in the when condition.
Right now i am doing:
rule "test"
when
Params(tol: tolerance) //recieving the Map
eval( tol.get("height").get(0) < 0 )
then
...
end
It says that the get function is not part of the type Object. How do i get to the arraylist value?

Assuming that your classes look something like this:
class Params {
private Map<String, List<Integer>> tolerance;
public Map<String, List<Integer>> getTolerance() { return this.tolerance; }
}
Then you should be able to structure a rule like this:
rule "test"
when
// get the Tolerance map and assign to $tolerance
Params( $tolerance: tolerance )
// get the 'height' list from the $tolerance map, assign to $height
Map( $height: this["height"] ) from $tolerance
// Check if the first integer in the $height list is negative
Integer( this < 0 ) from $height.get(0)
then
...
end
The this[ key ] syntax only works for Maps. Depending on how you've configured Drools and how old the version of Drools you're using is, $height may be extracted as an object, which means you'll have to first convert to list before you use the get(#) method.
Avoid eval whenever possible because the Drools compiler cannot optimize those calls.

Related

Java Stream - Retrieving repeated records from CSV

I searched the site and didn't find something similar. I'm newbie to using the Java stream, but I understand that it's a replacement for a loop command. However, I would like to know if there is a way to filter a CSV file using stream, as shown below, where only the repeated records are included in the result and grouped by the Center field.
Initial CSV file
Final result
In addition, the same pair cannot appear in the final result inversely, as shown in the table below:
This shouldn't happen
Is there a way to do it using stream and grouping at the same time, since theoretically, two loops would be needed to perform the task?
Thanks in advance.
You can do it in one pass as a stream with O(n) efficiency:
class PersonKey {
// have a field for every column that is used to detect duplicates
String center, name, mother, birthdate;
public PersonKey(String line) {
// implement String constructor
}
// implement equals and hashCode using all fields
}
List<String> lines; // the input
Set<PersonKey> seen = new HashSet<>();
List<String> unique = lines.stream()
.filter(p -> !seen.add(new PersonKey(p))
.distinct()
.collect(toList());
The trick here is that a HashSet has constant time operations and its add() method returns false if the value being added is already in the set, true otherwise.
What I understood from your examples is you consider an entry as duplicate if all the attributes have same value except the ID. You can use anymatch for this:
list.stream().filter(x ->
list.stream().anyMatch(y -> isDuplicate(x, y))).collect(Collectors.toList())
So what does the isDuplicate(x,y) do?
This returns a boolean. You can check whether all the entries have same value except the id in this method:
private boolean isDuplicate(CsvEntry x, CsvEntry y) {
return !x.getId().equals(y.getId())
&& x.getName().equals(y.getName())
&& x.getMother().equals(y.getMother())
&& x.getBirth().equals(y.getBirth());
}
I've assumed you've taken all the entries as String. Change the checks according to the type. This will give you the duplicate entries with their corresponding ID

Get map from two list having similar object ID

I'm new to java stream API.
I have 2 lists, and if both their internal object ID matches wants to put some attributes to MAP.
Below is the implementation.
List<LookupMstEntity> examTypeDetails; //This list contains values init.
List<MarksMstEntity> marksDetailList; //This list contains values init.
//FYI above entities have lombok setter, getter, equals & hashcode.
Map<Long, Integer> marksDetailMap = new HashMap<>();
//need below implementation to changed using java 8.
for (LookupMstEntity examType : examTypeDetails) {
for (MarksMstEntity marks : marksDetailList) {
if (examType.getLookupId() == marks.getExamTypeId())
marksDetailMap.put(examType.getLookupId(), marks.getMarks());
}
}
Creating a set of lookupIds Set<Long> ids helps you to throw away duplicate values and to get rid of unnecessary checks.
Then you can filter marksDetailList accordingly with examTypeId values:
filter(m -> ids.contains(m.getExamTypeId()))
HashSet contains() method has constant time complexity O(1).
Try this:
Set<Long> ids = examTypeDetails.stream().map(LookupMstEntity::getLookupId)
.collect(Collectors.toCollection(HashSet::new));
Map<Long, Integer> marksDetailMap = marksDetailList.stream().filter(m -> ids.contains(m.getExamTypeId()))
.collect(Collectors.toMap(MarksMstEntity::getExamTypeId, MarksMstEntity::getMarks));
As long as you are looking for these with equal ID, it doesn't matter which ID you use then. I suggest you to start streaming the marksDetailList first since you need its getMarks(). The filtering method searches if there is a match in IDs. If so, collect the required key-values to the map.
Map<Long, Integer> marksDetailMap = marksDetailList.stream() // List<MarksMstEntity>
.filter(mark -> examTypeDetails.stream() // filtered those where ...
.map(LookupMstEntity::getLookupId) // ... the lookupId
.anyMatch(id -> id == mark.getExamTypeId())) // ... is present in the list
.collect(Collectors.toMap( // collected to Map ...
MarksMstEntity::getExamTypeId, // ... with ID as a key
MarksMstEntity::getMarks)); // ... and marks as a value
The .map(..).anyMatch(..) can be shrink into one:
.anyMatch(exam -> exam.getLookupId() == mark.getExamTypeId())
As stated in the comments, I'd rather go for the for-each iteration as you have already used for sake of brevity.
An observation:
First, your resultant map indicates that there can only be one match for ID types (otherwise you would have duplicate keys and the value would need to be a List or some other way of merging duplicate keys, not an Integer. So when you find the first one and insert it in the map, break out of the inner loop.
for (LookupMstEntity examType : examTypeDetails) {
for (MarksMstEntity marks : marksDetailList) {
if (examType.getLookupId() == marks.getExamTypeId()) {
marksDetailMap.put(examType.getLookupId(),
marks.getMarks());
// no need to keep on searching for this ID
break;
}
}
}
Also if your two classes were related by a parent class or a shared interface that had access to to the id, and the two classes were considered equal based on that id, then you could do something similar to this.
for (LookupMstEntity examType : examTypeDetails) {
int index = marksDetailList.indexOf(examType);
if (index > 0) {
marksDetailMap.put(examType.getLookupId(),
marksDetaiList.get(index).getMarks());
}
}
Of course the burden of locating the index is still there but it is now under the hood and you are relieved of that responsibility.
You can do it with O(N) time complexity using HashMap, first convert two lists into Map<Integer, LookupMstEntity> and Map<Integer, MarksMstEntity> with id as key
Map<Integer, LookupMstEntity> examTypes = examTypeDetails.stream()
.collect(Collectors.toMap(LookupMstEntity::getLookupId,
Function.identity()) //make sure you don't have any duplicate LookupMstEntity objects with same id
Map<Integer, MarksMstEntity> marks = marksDetailList.stream()
.collect(Collectors.toMap(MarksMstEntity::getExamTypeId,
Function.identity()) // make sure there are no duplicates
And then stream the examTypes map and then collect into map if MarksMstEntity exists with same id in marks map
Map<Integer, Integer> result = examTypes.entrySet()
.stream()
.map(entry->new AbstractMap.SimpleEntry<Integer, MarksMstEntity>(entry.getKey(), marks.get(entry.getKey())))
.filter(entry->entry.getValue()!=null)
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));

How to Iterate through a SortedSet of Map.Entry

I have a data structures asgn I am working on. In it, we were required to create a HashMap of word objects (Word is a custom class). Where the key is a String, and the value is a custom Word object. From the HashMap I am required to create a treeMap that will sort the Word objects based on custom Word comparators I created. Thus far I have been able to sort the HashMap by creating the following Method which returns a SortedSet of Map.Entry:
public static SortedSet<Map.Entry<String , Word>> entriesSortedByValues
(Map<String , Word> map , Comparator<Word> comp){
SortedSet<Map.Entry<String , Word>> sortedEntries = new
TreeSet<Map.Entry<String , Word>>(
new Comparator<Map.Entry<String , Word>>()
{
public int compare(Map.Entry<String , Word> e1, Map.Entry<String , Word> e2)
{
int c = comp.compare(e1.getValue() , e2.getValue());
return c;
}
});
sortedEntries.addAll(map.entrySet());
return sortedEntries;
}
This works well, and returns the set ordered appropriately. However I now want to iterate over it to print the Word objects. Like so:
SortedSet t_set1 = entriesSortedByValues(h_Map , Word.alpha);
// this works^^ (returns correctly ordered set of Map.Entry<String , Word>)
for (Map.Entry<String , Word> entry : t_set_1)
{
System.out.println(///WORD OBJECT)
}
I have tried to run for loops over it, for each, and creater an iterator. I keep getting Incompatible Type Errors, or Entry has private access errors. This is probably a simple fix, but I have been banging my head against the wall trying everything to get it to work. PLZ HELP :(
You need to add the generic type of the SortedSet variable:
SortedSet<Map.Entry<String, Word>> t_set1 = entriesSortedByValues(h_Map , Word.alpha);
This is caused by generics being implemented with Type Erasure to maintain backwards compatibility with Java 1.4. By not adding the generic type you are effectively creating a SortedSet<Object>.

Drools: how to iterate list and add to another list

I am trying to iterate a ArrayList and add to another ArrayList using jboss drools inside the rule.
I have a pojo with the list.
Class DroolsPojo{
List<String> answers;
//getters and setters
}
My pojo returning a list like {"a","b","c","","",""}. I want to iterate the list and want to add elements which are not equal to ""(not empty elements of the list).
How can I do this with drools?
Is there any way to get the element count which is not equal to "" with the drools.
My rule is like as follows.
rule "rule1"
when
dpojo:DroolsPojo(answers!=null)
then
List list = dpojo.getAnswers();
//want to iterate the list here
end
How to do this with drools?
So the rule just has to fire when the answers instance variable is not null?
Using dialect mvel, something like this should work:
package drools.xxx
dialect "mvel"
import drools.xxx.DroolsPojo
rule "rule1"
when
$dpojo : DroolsPojo(answers!=null)
$answersWithoutEmptyStrings : List() from collect ( String(length > 0) from $dpojo.answers )
then
insert($answersWithoutEmptyStrings)
end
Here I do the collect (iterating) in the when clause.
On the right hand side you just write plain old Java code:
List list = dpojo.getAnswers();
for( Object obj: list ){
String s = (String)obj;
if( s.length() > 0 ){ ... }
}
The parser doesn't like generics (yet?) so you'll have to work around that.

How to retrieve a collection of values from a HashMap

I'm writing a function to test if a HashMap has null values. The method .values() SHOULD return a collection of just the values, but instead I receive a map with both the keys and values stored inside. This is no good as the purpose of my function is to check if the values are null, but if I return a map with keys AND values then .values().isEmpty() returns false if I have a key stored with no value.
public Map<KEY, List<VALUES>> methodName() {
if (MAPNAME.values().isEmpty()) {
throw new CustomErrorException(ExceptionHandler.getErrorWithDescription(ErrorConstants.ERROR_MSG_01));
} else {
return MAPNAME;
}
}
In the above example, .values() always returns a map containing all the keys and values. My method never throws a CustomErrorException if the HashMap has a key, which is bad since it's supposed to detect if there are no values. Help!
There's no such thing as a Map implementation that has a key stored without a value. All Map implementations either:
throw an exception in put when the value is null
Add an entry with a key and a value of null
A key that maps to null is very different than a key without a value. The key has a value, and that value is null (and that means that the values collection won't be empty, unless the map is empty). A key without a value is a key that's not contained in the map.
Long story short, you probably want to use MAPNAME.values().contains(null) or even just MAPNAME.containsValue(null) to do what you want. Alternatively, if you're checking that every key maps to null, check that by iterating over the .values() collection.
You're returning the map -- MAPNAME, not the values:
return MAPNAME.values();
If you're trying to determine if the map contains any null values, you should iterate over the collection of values and check each one to see if its null.
A map that contains an entry with a null value is not empty.
You're not being very clear about what you want -- your map values are lists -- considering that, there are three ways to have a key map to "no values":
A key mapped to null (then the test is map.values().contains(null) )
A key mapped to an empty list (then the test is map.values().contains(Collections.emptyList()) )
A key mapped to a list full of nulls.
What your method above is doing right now is throwing an exception if the map is truly empty (no keys), and returning the map otherwise.
It is not clear what you want. If you want the method to throw an exception only if the map has no meaningful values (all keys map either to null or to empty lists) then something like this is what you need:
public Map<KEY, List<VALUES>> methodName() {
for( List<VALUES> values : MAPNAME.values() ) // 1
if( null != values ) // 2
for( VALUES value : values ) // 3
if( null != value ) // 4
return MAPNAME;
throw new CustomErrorException(ExceptionHandler.getErrorWithDescription(ErrorConstants.ERROR_MSG_01));
}
This throws an exception in the all reasonably conceivable "empty map" scenarios -- if (1) the map is truly empty, or (2) it contains only null keys, or (3) it only contains only null values or empty lists, or (4) it contains only null values or empty lists or lists of nulls.
(Levels of "emptiness" tests in the text above correspond to the comment labels in the code).
Use the values() method in this way:-
Collection set=MAPNAME.values();
And then use a foreach loop to check if every value is null or not.

Categories