Sorry maybe for dumb question. I am looking for elegant way to go over elements of my map and filter properties.
Let's say I have map with two elements.
Map<String, MyElement> myMap;
This is how looks my element
class MyElement {
Map <String, Property1> properties1;
Map <String, Property2> properties2;
}
MyElement[0] includes properties1 map filled with some properties, and properties2 is null.
MyElement[1] includes properties2 map filled with some properties, and properties1 is null.
It might be vise versa, I have no idea for which MyElelmet Internal Maps are null and for which are not.
I would like to go over each MyElement in map and assemble properties1 or properties2 from each element in case if it is not empty.
Result should be two separate maps (new collections)
Map <String, Property1> assembledProperties1;
Map <String, Property2> assembledProperties2;
You can think about it as a collecting results to multiple outputs (assembledProperties1, assembledProperties2).
Is there any elegant way to do it with Java streams, without ugly if statements?
Since don't want to utilize MyElement as a mutable container, you can define a special type of object that will carry references to the maps of properties.
In order to be able to perform mutable reduction on a stream of type MyElement with this object we need to define a method that will expect MyElement as a parameter to update maps based on the next element of the stream, and another method that is needed to merge partial results of execution in parallel (i.e. to combine the two objects).
public class PropertyWrapper {
private Map<String, Property1> properties1 = new HashMap<>();
private Map<String, Property2> properties2 = new HashMap<>();
public PropertyWrapper merge(MyElement element) {
if (element.getProperties1() != null) properties1.putAll(element.getProperties1());
if (element.getProperties2() != null) properties2.putAll(element.getProperties2());
return this;
}
public PropertyWrapper merge(PropertyWrapper other) {
this.properties1.putAll(other.getProperties1());
this.properties2.putAll(other.getProperties2());
return this;
}
// getters and toString()
}
With that, the actual code might look like that:
public static void main(String[] args) {
Map<String, MyElement> sourceMap =
Map.of("key1", new MyElement(Map.of("a", new Property1("a"), "b", new Property1("b")), null),
"key2", new MyElement(null, Map.of("c", new Property2("c"), "d", new Property2("d"))));
PropertyWrapper result = sourceMap.values().stream()
.collect(
PropertyWrapper::new,
PropertyWrapper::merge,
PropertyWrapper::merge);
System.out.println(result.getProperties1());
System.out.println(result.getProperties2());
}
Output
{a=Property1{a}, b=Property1{b}}
{d=Property2{d}, c=Property2{c}}
Also note that it's a good practice to avoid keeping nullable references to collections. If these fields will always be initialized with empty collection, the need of null-check will be eliminated.
Related
I'm in a weird situation where have a JSON API that takes an array with strings of neighborhoods as keys and an array of strings of restaurants as values which get GSON-parsed into the Restaurant object (defined with a String for the neighborhood and a List<String> with the restaurants). The system stores that data in a map whose keys are the neighborhood names and values are a set of restaurant names in that neighborhood. Therefore, I want to implement a function that takes the input from the API, groups the values by neighborhood and concatenates the lists of restaurants.
Being constrained by Java 8, I can't use more recent constructs such as flatMapping to do everything in one line and the best solution I've found is this one, which uses an intermediate map to store a Set of List before concatenating those lists into a Set to be store as value in the final map:
public Map<String, Set<String>> parseApiEntriesIntoMap(List<Restaurant> restaurants) {
if(restaurants == null) {
return null;
}
Map<String, Set<String>> restaurantListByNeighborhood = new HashMap<>();
// Here we group by neighborhood and concatenate the list of restaurants into a set
Map<String, Set<List<String>>> map =
restaurants.stream().collect(groupingBy(Restaurant::getNeighborhood,
Collectors.mapping(Restaurant::getRestaurantList, toSet())));
map.forEach((n,r) -> restaurantListByNeighborhood.put(n, Sets.newHashSet(Iterables.concat(r))));
return restaurantListByNeighborhood;
}
I feel like there has to be a way do get rid of that intermediate map and do everything in one line...does someone have a better solution that would allow me to do this?
You could with Java-8 simply use toMap with a mergeFunction defined as:
public Map<String, Set<String>> parseApiEntriesIntoMap(List<Restaurant> restaurants) {
// read below about the null check
return restaurants.stream()
.collect(Collectors.toMap(Restaurant::getNeighborhood,
r -> new HashSet<>(r.getRestaurantList()), (set1, set2) -> {
set1.addAll(set2);
return set1;
}));
}
Apart from which, one should ensure that the check and the result from the first block of code from your method
if(restaurants == null) {
return null;
}
when on the other hand dealing with empty Collections and Map, it should be redundant as the above code would return empty Map for an empty List by the nature of stream and collect operation itself.
Note: Further, if you may require a much relatable code to flatMapping in your future upgrades, you can use the implementation provided in this answer.
Or a solution without using streams, in this case, would look similar to the approach using Map.merge. It would use a similar BiFunction as:
public Map<String, Set<String>> parseApiEntriesIntoMap(List<Restaurant> restaurants) {
Map<String, Set<String>> restaurantListByNeighborhood = new HashMap<>();
for (Restaurant restaurant : restaurants) {
restaurantListByNeighborhood.merge(restaurant.getNeighborhood(),
new HashSet<>(restaurant.getRestaurantList()),
(strings, strings2) -> {
strings.addAll(strings2);
return strings;
});
}
return restaurantListByNeighborhood;
}
You can also flatten the Set<List<String>> after collecting them using Collectors.collectingAndThen
Map<String, Set<String>> res1 = list.stream()
.collect(Collectors.groupingBy(Restaurant::getNeighborhood,
Collectors.mapping(Restaurant::getRestaurantList,
Collectors.collectingAndThen(Collectors.toSet(),
set->set.stream().flatMap(List::stream).collect(Collectors.toSet())))));
I got a scenario like the following:
Map1 - Map<String, Map<String,List<Vo>>>
Map2 - Map<String, Set<String>
Is it possible to set the same have a same key reference for the above 2 Maps like the following?
Map<String, Collection<?> mapCommon=new HashMap<String, Collection<?>();
Can anyone please give some idea about how to set this?
edit: yes same reference
You are touching here two interesting elements.
Firstly - Map does not belong to Collection. List and Set do belong, but Map is a different one even though it shares some commonalities with Lists and Sets.
Secondly - Mixing the types into one commonMap the way you are trying is doable but it should be avoided as it is generally not considered as best practice. The problem we are dealing with is caused by type erasure. Once compiler compiles the code - it does not pass any information about generic types hold by Map or Set. Effectively your Map<String, List<Vo>> becomes raw-type Map<?> in the compiled code. The problem with that is casting back original values. The compiler will not allow you to check the instance if it is Map<String, List<Vo>> or Set<String>.
The fllowing piece of code will fail:
public static void processElement(Object commonMapObjectEitherMapOrSet) {
if (commonMapObjectEitherMapOrSet instanceof Map<String, List<Vo>>) {
//...
}
}
Error: Cannot perform instanceof check against parameterized type
Map>. Use the form Map instead since further
generic type information will be erased at runtime
The possible workaround would be to forget about generics and check if the instance is a raw-type Set or Map. The code below shows how check if Object is either Map or Set.
public static void processElement(Object commonMapObjectEitherMapOrSet) {
if (commonMapObjectEitherMapOrSet instanceof Map) {
System.out.println("Got map; but types held in the map are not known due to type-erasure");
// This is where things will get messy as you will get warnings:
Map<String, List<Vo>> map = (Map<String, List<Vo>>) commonMapObjectEitherMapOrSet;
// ...
}
if (commonMapObjectEitherMapOrSet instanceof Set) {
System.out.println("Got set; but types held in the set are not known due to type-erasure");
// This is where things will get messy as you will get warnings:
Set<String> set = (Set<String>) commonMapObjectEitherMapOrSet;
// ...
}
}
The problem with the above is casting the value from your commonMap back to your desired types ie. Map<String, List<Vo>> and Set<String>. The compiler won't be able to check if the casting is correct and will issue a warning. You can technically Suppress the warning with (#SuppressWarnings("unchecked") annotation ) but this may not be the best thing to do.
At this stage - it makes sense to consider whether or not to create your own specialized class to manage different types.
Back to your original question - to answer it I am posting the code that maps things to the common map:
package stackoverflow;
import java.util.*;
class Vo {}
public class MultipleRefs {
public static void main(String[] args) {
Map<String, List<Vo>> mapVo = new HashMap<>();
Set<String> set = new HashSet<>();
Map<String, Object> commonMap = new HashMap<>();
//commonMap.put("a", Map)
commonMap.put("mapVoOne", mapVo);
commonMap.put("setOne", set);
commonMap.forEach((key, value) -> processElement(value));
}
public static void processElement(Object commonMapObject) {
if (commonMapObject instanceof Map) {
System.out.println("Got map; but types held in the map are not known due to type-erasure");
// This is where things will get messy:
Map<String, List<Vo>> map = (Map<String, List<Vo>>) commonMapObject;
System.out.println(" processElement prints map: " + map);
}
if (commonMapObject instanceof Set) {
System.out.println("Got set; but types held in the set are not known due to type-erasure");
// This is where things will get messy:
Set<String> set = (Set<String>) commonMapObject;
System.out.println(" processElement prints set: " + set);
}
}
}
If I understand you would want to have the same key to be used for various different types of values.
Why not have a new Class itself that would consists of maps, sets, whose instances could be used as values
class MyClass {
private Map<String, List<Vo>> theMap;
private Set<String> theSet;
...
... // have its own getters and setters
}
And then you can have your top level map defined like this
Map<String, MyClass> myMainMap = new HashMap<String, MyClass>();
Or as an alternative have a tuple
You can check this link further to see how that is done.
What you want to do is impossible because Set and Map do not share any common implementation or super class except Object. You can see it in the official documentation :
Javadoc Map
Javadoc Set
You could do a Map<String, Object> but I strongly not advise you to doing that. How could you know if your object is a map or a set ? It is not possible to do that properly.
In my opinion, the best solution you have is to create a new class to wrap your two collections :
public class YourWrapper {
Map<String, Map<String,List<Vo>>> a;
Map<String, Set<String> b;
// getter setter etc...
}
After that you can create your collection :
Map<String, YourWrapper> myMap = new HashMap<String, YourWrapper>();
I have a method that maps keywords to a certain value. I want to return the actual hashmap so I can reference its key/value pairs
Yes. It is easily possible, just like returning any other object:
public Map<String, String> mapTheThings(String keyWord, String certainValue)
{
Map<String, String> theThings = new HashMap<>();
//do things to get the Map built
theThings.put(keyWord, certainValue); //or something similar
return theThings;
}
Elsewhere,
Map<String, String> actualHashMap = mapTheThings("keyWord", "certainValue");
String value = actualHashMap.get("keyWord"); //The map has this entry in it that you 'put' into it inside of the other method.
Note, you should prefer to make the return type Map instead of HashMap, as I did above, because it's considered a best practice to always program to an interface rather than a concrete class. Who's to say that in the future you aren't going to want a TreeMap or something else entirely?
I have been trying to learn Java 8's new functional interface features, and I am having some difficulty refactoring code that I have previously written.
As part of a test case, I want to store a list of read names in a Map structure in order to check to see if those reads have been "fixed" in a subsequent section of code. I am converting from an existing Map> data structure. The reason why I am flattening this datastructure is because the outer "String" key of the original Map is not needed in the subsequent analysis (I used it to segregate data from different sources before merging them in the intermediate data). Here is my original program logic:
public class MyClass {
private Map<String, Map<String, Short>> anchorLookup;
...
public void CheckMissingAnchors(...){
Map<String, Boolean> anchorfound = new HashMap<>();
// My old logic used the foreach syntax to populate the "anchorfound" map
for(String rg : anchorLookup.keySet()){
for(String clone : anchorLookup.get(rg).keySet()){
anchorfound.put(clone, false);
}
}
...
// Does work to identify the read name in the file. If found, the boolean in the map
// is set to "true." Afterwards, the program prints the "true" and "false" counts in
// the map
}
}
I attempted to refactor the code to use functional interfaces; however, I getting errors from my IDE (Netbeans 8.0 Patch 2 running Java 1.8.0_05):
public class MyClass {
private Map<String, Map<String, Short>> anchorLookup;
...
public void CheckMissingAnchors(...){
Map<String, Boolean> anchorfound = anchorLookup.keySet()
.stream()
.map((s) -> anchorlookup.get(s).keySet()) // at this point I am expecting a
// Stream<Set<String>> which I thought could be "streamed" for the collector method
// ; however, my IDE does not allow me to select the "stream()" method
.sequential() // this still gives me a Stream<Set<String>>
.collect(Collectors.toMap((s) -> s, (s) -> false);
// I receive an error for the preceding method call, as Stream<Set<String>> cannot be
// converted to type String
...
}
}
Is there a better way to create the "anchorfound" map using the Collection methods or is the vanilla Java "foreach" structure the best way to generate this data structure?
I apologize for any obvious errors in my code. My formal training was not in computer science but I would like to learn more about Java's implementation of functional programming concepts.
I believe what you need is a flatMap.
This way you convert each key of the outer map to a stream of the keys of the corresponding inner map, and then flatten them to a single stream of String.
public class MyClass {
private Map<String, Map<String, Short>> anchorLookup;
...
public void CheckMissingAnchors(...){
Map<String, Boolean> anchorfound = anchorLookup.keySet()
.stream()
.flatMap(s -> anchorlookup.get(s).keySet().stream())
.collect(Collectors.toMap((s) -> s, (s) -> false);
...
}
}
Eran's suggestion of flatMap is a good one, +1.
This can be simplified somewhat by using Map.values() instead of Map.keySet(), since the map's keys aren't used for any other purpose than to retrieve the values. Streaming the result of Map.values() gives a Stream<Map<String,Short>>. Here we don't care about the inner map's values, so we can use keySet() to extract the keys, giving a Stream<Set<String>>. Now we just flatMap these sets into Stream<String>. Finally we send the results into the collector as before.
The resulting code looks like this:
public class MyClass {
private Map<String, Map<String, Short>> anchorLookup;
public void checkMissingAnchors() {
Map<String, Boolean> anchorfound = anchorLookup.values().stream()
.map(Map::keySet)
.flatMap(Set::stream)
.collect(Collectors.toMap(s -> s, s -> false));
}
}
I created a class Foo that has the method toArray() that returns an Array<Int>.
Now, I have a HashMap mapping Strings to HashMaps, which map Objects to Foo. That is:
HashMap<String,HashMap<Object,Foo>>
And I want to create a new object of type:
HashMap<String,HashMap<Object,Array<Int>>>
That is obtained by calling the function toArray() for every element Foo in the original HashMAp.
To do so I normally would do something like:
public static HashMap<String,HashMap<Object,Array<Int>>> changeMap(Map mpOld) {
Object key2;
String key1;
Iterator it2;
HashMap<String,HashMap<Object,Array<Int>>> mpNew=
new HashMap<String,HashMap<Object,Array<Int>>>()
Iterator it1 = mpOld.keySet().iterator();
while (it1.hasNext()) {
key1=it1.next();
it2= mpOld.get(key1).keySet().iterator();
mpNew.put(key1,new HashMap<Object,Array<Int>>())
while (it2.hasNext()) {
key2=it2.next();
mpNew.get(key1).put(key2,mpOld.get(key1).get(key2).toArray());
//TODO clear entry mpOld.get(key1).get(key2)
}
//TODO clear entry mpOld.get(key1)
}
return mpNew;
}
A similar code works just fine, but the Size of the HashMap is too big to hold two of them in memory. As you can see I added two points where I want to clear some entries. The problem is, if I do, I get either a concurrency error, or the iterator loop just terminates.
I wonder if there is a better way to iterate through the Maps and copy the information.
Also, I'm working in a Scala project but here I have to use Java types for some compatibility issues. Although Java.util.HashMap is not an iterator, maybe Scala has some hidden functinality to deal with this?
Thanks,
Iterators offer remove(..) methods that safely removes the previously accessed item. Iterate over the Key/Value entries of the map, converting them and adding them to the new map, and removing the old ones as you go.
/**
* Transfers and converts all entries from <code>map1</code> to
* <code>map2</code>. Specifically, the {#link Foo} objects of the
* inner maps will be converted to integer arrays via {#link Foo#toArray}.
*
* #param map1 Map to be emptied.
* #param map2 Receptacle for the converted entries.
*/
private static void transfer(Map<String, Map<Object, Foo>> map1
, Map<String, Map<Object, int[]>> map2) {
final Iterator<Entry<String, Map<Object, Foo>>> mapIt
= map1.entrySet().iterator();
while (mapIt.hasNext()) {
final Entry<String, Map<Object, Foo>> mapEntry = mapIt.next();
mapIt.remove();
final Map<Object, int[]> submap = new HashMap<Object,int[]>();
map2.put(mapEntry.getKey(), submap);
final Iterator<Entry<Object,Foo>> fooIt
= mapEntry.getValue().entrySet().iterator();
while (fooIt.hasNext()) {
final Entry<Object,Foo> fooEntry = fooIt.next();
fooIt.remove();
submap.put(fooEntry.getKey(), fooEntry.getValue().toArray());
}
}
}
I did not have time to check it, but I guess something like this should work on scala Maps (assuming you use scala 2.8 which is finally here):
mpO.mapValues(_.mapValues(_.toArray))
It would take your outer map, and "replace" all inner maps with a new one, where the values are the Int arrays. Keys, and the general "structure" of the maps remain the same. According to scaladoc "The resulting map wraps the original map without copying any elements.", so it won't be a real replacement.
If you also do an
import scala.collection.JavaConversions._
then the java maps can be used the same way as scala maps: JavaConversions contain a bunch of implicit methods that can convert between scala and java collections.
BTW using a Map < String,HashMap < Object,Array < Int>>> might not be really convenient at the end, if I were you I would consider introducing some classes that would hide the complexity of this construct.
Edit reflecting to your comment
import scala.collection.JavaConversions._
import java.util.Collections._
object MapValues {
def main(args: Array[String]) {
val jMap = singletonMap("a",singletonMap("b", 1))
println(jMap)
println(jMap.mapValues(_.mapValues(_+1)))
}
}
prints:
{a={b=1}}
Map(a -> Map(b -> 2))
Showing that the implicits are applied both to the outer and inner map quite nicely. This is the purpose of the JavaConversions object: even if you have a java collection you can use it as a similar scala class (with boosted features).
You don't have to do anything else, just import JavaConversions._
For example considering String keys; lets call the input data: Map<String, Map<String, Object>> data
for (Entry<String, Map<String, Tuple>> entry : data.entrySet()) {
String itemKey = entry.getKey();
for (Entry<String, Object> innerEntry : entry.getValue().entrySet()) {
String innerKey = innerEntry.getKey();
Object o = innerEntry.getValue();
// whatever, here you have itemKey, innerKey and o
}
}
The set is backed by the map, so changes to the map are reflected in the set, and vice-versa. If the map is modified while an iteration over the set is in progress (except through the iterator's own remove operation), the results of the iteration are undefined. The set supports element removal, which removes the corresponding mapping from the map, via the Iterator.remove, Set.remove, removeAll, retainAll, and clear operations.
Why don't you call the remove () method on the iterator or set.remove (iterator.next ()) where iterator.next () returns the key, set is the keyset and iterator its iterator.
PS: also try to refactor your data structure, maybe some intermediate classes which handle the data retrieval? A map in a map with arrays as values doesn't say anything and is difficult to keep track of.