Dynamically nesting group by inside Collector - java

I have this piece of code which is working fine:
Collector nestedCollector = Collectors.groupingBy((Map map) -> map.get("PROCESS_NAME"),
Collectors.groupingBy((Map map) -> map.get("PROCESS_INSTANCE")));
However, I can't use fixed map keys in my code (PROCESS_NAME, PROCESS_INSTANCE).
What I need is to pass the list of keys via parameter and generate a nested collector, something like this:
List<String> keys = new ArrayList();
keys.add("PROCESS_NAME");
keys.add("PROCESS_INSTANCE");
keys.add("...some other key...");
Collector nestedCollector= Collectors.groupingBy(...nested groupingBy based on keys...)
I'm struggling with the syntax.
Can anybody help? Thank you.

Related

Default methods and Lambda Supplier Callbacks

I welcome methods in the API to easily create default initialisations.
For example in HashMaps. But why have they not been provided with Supplier Lambda methods? - Or am I missing an important step, or did I not learn the latest java Api versions?
Standard (Java8) version:
Map<String,List<Integer>> datas = new HashMap<>();
List<Integer> integersList = datas.getOrDefault( "somekey", new ArrayList<>() );
which would instantiate a new ArrayList anytime the code is executed - no matter if the new list is needed or not.
Desired Lambda supplier version:
Map<String,List<Integer>> datas = new HashMap<>();
List<Integer> integersList = datas.getOrDefault( "somekey", ()->new ArrayList() );
Would instantiate (or execute some instantiation code) only in case demanded key is not within the map.
The code of the getOrDefault()-Method could look something like this:
public V getOrDefault( K key, Supplier<V> supplier ) {
if ( !super.containsKey( key ) && supplier != null ) {
super.put( key, supplier.get() );
}
return super.get( key );
}
Why did they(?) not build it that way initially or added such functionality later on?
I guess there is even more examples where Lambda would solve an unnecessary code execution - not just Maps as shown with this example.
By the way: sorry for re-asking a question but I would not know how to exactly look for my question with different terms...
Be welcome to post helpful links.
Thanks for your shared knowledge :-)
What you are looking for exists since Java 8. Take a look at the javadoc of the HashMap and specifically the method Hashmap.computeIfAbsent. This method allows for adding new entries to the HashMap if none can be found using the key provided.
Examaple:
Map<Integer, String> map = new HashMap();
String created = map.computeIfAbsent(1, k -> "Test");
System.out.println(created);
The code above will trigger the HashMap to call the provided Function to add a new entry since it cannot find an existing one. It both returns the new entry and call the Hashmap.put method to add it.

Create a stream of the values in maps that are values in another map in Java

Sorry about the title of the question; it was kind of hard for me to make sense of it. If you guys have a better title, let me know and I can change it.
I have two types of objects, Bookmark and Revision. I have one large Map, like so:
Map<Long, Bookmark> mapOfBookmarks;
it contains key: value pairs like so:
1L: Bookmark1,
2L: Bookmark2,
...
Each Bookmark has a 'getRevisions()' method that returns a Map
public Map<Long, Revision> getRevisions();
I want to create a Stream that contains all revisions that exist under mapOfBookmarks. Essentially I want to do this:
List<Revision> revisions = new ArrayList<>();
for (Bookmark bookmark : mapOfBookmarks.values()) { // loop through each bookmark in the map of bookmarks ( Map<Long, Bookmark> )
for (Revision revision : bookmark.getRevisions().values()) { // loop through each revision in the map of revisions ( Map<Long, Revision> )
revisions.add(revision); // add each revision of each map to the revisions list
}
}
return revisions.stream(); // return a stream of revisions
However, I'd like to do it using the functionality of Stream, so more like:
return mapOfBookmarks.values().stream().everythingElseThatIsNeeded();
Which would essentially be like saying:
return Stream.of(revision1, revision2, revision3, revision4, ...);
How would I write that out? Something to note is that the dataset that it is looping through can be huge, making the list method a poor approach.
I'm using Windows 7 and Java 8
A flatmap is what you looking for. When you have streams contained within a stream that you wish to flatten, then flatmap is the answer,
List<Revision> all =
mapOfBookmarks.values().stream()
.flatMap(c -> c.getRevisions().values().stream())
.collect(Collectors.toList());
You are looking for the flatMap(mapper) operation:
Returns a stream consisting of the results of replacing each element of this stream with the contents of a mapped stream produced by applying the provided mapping function to each element.
In this case, we're making a Stream<Bookmark> by calling stream(), flat mapping it to the revisions of each bookmark and, finally, collecting that into a list with toList().
List<Revision> revisions =
mapOfBookmarks.values()
.stream()
.flatMap(bookmark -> boormark.getRevisions().values().stream())
.collect(Collectors.toList());
Note that your current code could also be improved by calling addAll instead of looping over each revisions:
for (Bookmark bookmark : mapOfBookmarks.values()) { // loop through each bookmark in the map of bookmarks ( Map<Long, Bookmark> )
revisions.addAll(bookmark.getRevisions().values());
}

How to maintain JSON's order in Groovy's JsonSlurper?

I am reading a simple JSON....
{"A":0,"B":0,"C":2,"D":0,"F":5}
into a map using JsonSlurper in Groovy...
Map gradeDistributon = jsonSlurper.parseText(jsonString)
But when iterating over this map with a closure..
gradeDistributon.each{ entry ->
println "From map got key ${entry.key}"
I am seeing the keys are not in the order they were in the original JSON, for example 'C' comes first. I think this is because Map does not maintain insertion order in Java. Is there a way I can keep the order of the original JSON?
If it means reading the JSON in a different way (instead of into a Map with JsonSlurper) then I am fine with that if you can show me how.
You can set JVM system property jdk.map.althashing.threshold to make JsonSlurper to use a LinkedHashMap instead of TreeMap as the internal Map implementation, e.g. -Djdk.map.althashing.threshold=512.
The reason is in source code of groovy.json.internal.LazyMap used by JsonSlurper.
private static final String JDK_MAP_ALTHASHING_SYSPROP = System.getProperty("jdk.map.althashing.threshold");
private void buildIfNeeded() {
if (map == null) {
/** added to avoid hash collision attack. */
if (Sys.is1_7OrLater() && JDK_MAP_ALTHASHING_SYSPROP != null) {
map = new LinkedHashMap<String, Object>(size, 0.01f);
} else {
map = new TreeMap<String, Object>();
}
}
}
Please note this solution should be used as a hack as it depends on Groovy's internal implementation details. So this behavior may change in future version of Groovy.
See my blog post for details.
So it was just a matter of sorting the keys after JsonSlurper built the Map, for that I just read into a TreeMap which sorts the keys by default..
TreeMap gradeDistributon = jsonSlurper.parseText(jsonString)
I can't reproduce your behaviour with groovy 2.4.5 but you can try using LinkedHashMap (allow to iterate over map keys maintaining the order in which the entries were inserted):
import groovy.json.*
def jsonText = '''
{"A":0,"B":0,"C":2,"D":0,"F":5,"G":7,"H":9}
'''
LinkedHashMap json = new JsonSlurper().parseText(jsonText)
json.each{ entry ->
println "${entry.key}"
}
NOTE: as stated by #XenoN the JsonSlurper() sort the json keys during the parsing process so independently of the input order (ie. {"H":0,"B":0,"A":2,"D":0,"G":5,"F":7,"C":9}) the output of JsonSlurper will be always: {"A":2,"B":0,"C":9,"D":0,"F":7,"G":5,"H":0}.
Using the LinkedHashMap instead of a HashMap we preserve the order given by JsonSlurper.
I run the same code on Groovy 2.4.x and on 3.0.x.
On 2.4 the order is preserved,but on 3.0 is sorted asc by default.
use the JsonSluperClassic().parse() instead it will preserve the order

lambda foreach add to map not working

I got the following variables
List<Pruefvorschrift> listP = new ArrayList<Pruefvorschrift>();
ObservableMap<TestDevice,List<Pruefvorschrift>> testDev = FXCollections.emptyObservableMap();
in one function i want to fill the testDev by using lambda expression
//first call REST service and get data
List<TestDevice> test_dev = call.getTestDevice("");
//now do a foreach to add each entry (as key) to the testDev ObservableMap with a empty List (as value)
test_dev.stream().forEach(td ->{
TestDevice t = td;
testDev.put(t, listP);
});
but all i get is a error
java.lang.UnsupportedOperationException at
java.util.AbstractMap.put(AbstractMap.java:209)
which obviously is this row
testDev.put(t, listP);
maybe i misunderstood the new stream api but i only want to fill the observable map with all the result of the call (key) and an empty List (value which will be modified later).
Any help? Thx
Whatever Map type is returned by FXCollections#emptyObservableMap
FXCollections.emptyObservableMap();
does not support the put method. You can't add anything to it. As the javadoc states
Creates and[sic] empty unmodifiable observable list.
This has nothing to do with lambda expressions or the Stream api.
just to complete here (Sotirios Delimanolis was absolute right and me so wrong :). My problem was solved by doing a correct job with the map itself
//create empty map
Map<TestDevice,List<Pruefvorschrift>> map = new HashMap<TestDevice,List<Pruefvorschrift>>();
//use this map to create the ObservableMap
ObservableMap<TestDevice,List<Pruefvorschrift>> testDev = FXCollections.observableMap(map);
And all works...Thx Sotirios

looking for a smart and fast searching algorithm

lets say i have 2 arrays of the objects which are mapped to each other in the following schemna:
array1 :
String [] prog_types1 = {"Program1","Program2","Program3","Program4"};
and array2 :
String [] prog_types2 ={"SubProgram1","SubProgram2","SubProgram3","SubProgram4",
"SubProgram5","SubProgram6","SubProgram7","SubProgram8","SubProgram9","SubProgram10"};
as it understood from its names, prog_types2 is an extension for prog_types1, but has some repeated values, so the full mapping between these programs would looks liek this:
prog_types1 prog_types2
ProgramType1 SubProgramType1
ProgramType1 SubProgramType2
ProgramType1 SubProgramType7
ProgramType1 SubProgramType9
ProgramType2 SubProgramType12
ProgramType2 SubProgramType7
ProgramType2 SubProgramType9
ProgramType3 SubProgramType1
ProgramType3 SubProgramType2
ProgramType3 SubProgramType21
ProgramType3 SubProgramType27
ProgramType3 SubProgramType7
ProgramType5 SubProgramType12
ProgramType5 SubProgramType9
my question is : what is the best way to map these arrays to each other, from the perspective of faster processing and reuse?
I have implemented it as :
-- set of classes (class prog1 and prog2 and after put it into vector)...
-- hashtable with hashset
-- possible one more array
the way i am looking for should not consist of creating the same prog2 objects again for prog1 object, as it would be in all of the ways described earlier, but map it by the index position for example or in any other way.
just lookin for a nice algorythmical way to resolve it...
thanks in advance
p.s. it should be used within 1 package only between couple of classes and the main use of it would be a population of the prog2 types values based on the prog1 type value
p.s.2 java7
Using MultiMap from Guava Libraries, you could say:
Multimap<String, String> mmap = ArrayListMultimap.create();
mmap.put("Program1", "SubProgramType1");
mmap.put("Program1", "SubProgramType2");
// etc.
mmap.get("Program1")
would look like:
[SubProgramType1, SubProgramType2, SubProgramType7, SubProgramType9]
BTW, Hashtable is not used now for hashed collections, has been superceded by HashMap :)
IMO the best way would be a:
Map<String, List<String>> programs = new HashMap<String, List<String>>();
with the strings in the first list as keys and the corresponding subprograms composing the value list. Now the mapping is obvious:
ProgramType1 -> [SubProgramType1, SubProgramType2, SubProgramType7, SubProgramType9]
ProgramType2 -> [SubProgramType12, SubProgramType7, SubProgramType9]
ProgramType3 -> [SubProgramType1, SubProgramType2, SubProgramType21, SubProgramType27, SubProgramType7]
ProgramType5 -> [SubProgramType12, SubProgramType9]
Guava ListMultimap, that gives List<E>, not Collection<E> - little more pleasant.
private ListMultimap<String,Something> stuff = ArrayListMultimap.create();
// ...
public void add(String key, Something item) {
stuff.put(key, item);
}
public List<Something> get(String key) {
// might as well use the Lists convenience API while we're at it.
return Lists.newArrayList(stuff.get(key));
}
http://www.coffee-bytes.com/2011/12/22/guava-multimaps
btw, since i need :
-- separately use Program1 values
-- separately use SubProgram1 values
-- populate SubProgram1 values based on Program1 value
the easiest solution here would be to declare a double dimensional array with all the dublicates (as it dysplayed in full map schema) and for 1) and 2) populate data from it using non repeating algorythm and 3) loop cycle from 2nd dimension
so no reason to declare 3 objects, huge memory save and nice approach.
i am giving myself a star for it:)

Categories

Resources