I have a few simple JSON files that have syntax
"key1": "value1"
"key2": "value2"
etc.
I need to create a Map from each of them, and then merge them into one. The following code works as is supposed to:
private TranslationBundle assembleBundle(List<String> translations) {
TranslationBundle translationBundle = new TranslationBundle();
Map<String, String> bundledTranslationMap = new HashMap<>();
translations.forEach(translation -> {
bundledTranslationMap.putAll(getSingleTranslationMap(translation).);
});
translationBundle.setTranslationMap(bundledTranslationMap);
return translationBundle;
}
private Map<String, String> getSingleTranslationMap(String translation){
ObjectMapper mapper = new ObjectMapper();
try{
return mapper.readValue(translation, new TypeReference<Map<String, String>>(){});
}catch (Exception e) {
e.printStackTrace();
}
return null;
}
What I want to achieve is to rewrite the code in the assembleBundle method into a more functional one, like
translations.stream().
map(this::getSingleTranslationMap)
.collect(collect to single HashMap);
In the collect() method, as far as I know, I have to somehow access the key and value for each result of getSingleTranslationMap. Since there is no variable name assigned to the result of this method, how should it be done? Maybe I'm trying a wrong approach at all?
You can transform the individual Maps returned by getSingleTranslationMap into a Stream of map entries, and collect them into a single Map:
Map<String, String> bundledTranslationMap =
translations.stream()
.flatMap(translation -> getSingleTranslationMap(translation).entrySet()
.stream())
.collect(Collectors.toMap(Map.Entry::getKey,Map.Entry::getValue));
Of course, if getSingleTranslationMap returns a Map with a single Entry, it might make more sense for that method to return a Map.Entry instead of a Map, which would simplify the above Stream pipeline.
P.S., if duplicate keys are possible, you should add a merge function to the toMap() call.
Related
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.
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 need to make async calls with a timeout of 10 seconds, and need to perform this for every element from a map. The results of the async calls are stored in another map. Is it safe to use a HashMap in this case or do I need to use ConcurrentMap?
Map<String, String> x = ArrayListMultimap.create();
Map<String, Boolean> value = Maps.newHashMap();
x.keySet().paralleStream().forEach(req -> {
try {
Response response = getResponseForRequest(req);
value.put(req, response.getTitle());
} catch(TimeoutException e) {
value.put(req, null);
}
}
Is this thread safe? I'm not able to figure out. I know the alternative way is to create a concurrent hashmap, and think of some other filler value instead of null as Concurrent maps dont support null values.
You can use .map() instead of .forEach() and return a map created with Collectors.toMap() terminating function instead of modifying external map in parallel. Consider following example:
Map result = x.keySet()
.parallelStream()
.map(req -> {
try {
Response response = getResponseForRequest(req);
return new AbstractMap.SimpleEntry<>(req, response.getTitle());
} catch (TimeoutException e) {
return new AbstractMap.SimpleEntry<>(req, null);
}
})
.collect(Collectors.toMap(AbstractMap.SimpleEntry::getKey, AbstractMap.SimpleEntry::getValue));
In this example you return a SimpleEntry object that represents a key and value for each element and when all entries are processed you collect them to a single map.
Simplification
Holger suggested even more simplified solution by getting rid of AbstractMap.SimpleEntry at all:
Map result = x.keySet()
.parallelStream()
.collect(Collectors.toMap(Function.identity(), req -> {
try {
Response response = getResponseForRequest(req);
return response.getTitle()
} catch (TimeoutException e) {
return null
}
}));
Pick whatever works better for you.
I want to transform keys in a HashMap. The map has lower_underscore keys but an expected map should have camelCase keys. The map may also have null values.
The straightfoward code to do this is here:
Map<String, Object> a = new HashMap<String, Object>() {{
put("foo_bar", 100);
put("fuga_foga", null); // A value may be null. Collectors.toMap can't handle this value.
}};
Map<String, Object> b = new HashMap<>();
a.forEach((k,v) -> b.put(toCamel(k), v));
I want to know the method to do this like Guava's Maps.transformValues() or Maps.transformEntries(), but these methods just transforms values.
Collectors.toMap() is also close, but this method throws NullPointerException when a null value exists.
Map<String, Object> collect = a.entrySet().stream().collect(
Collectors.toMap(x -> toCamel(x.getKey()), Map.Entry::getValue));
If you absolutely want to solve this using streams, you could do it like this:
Map<String, Object> b = a.entrySet()
.stream()
.collect(HashMap::new,
(m, e) -> m.put(toCamel(e.getKey()), e.getValue()),
HashMap::putAll);
But I find the "conventional" way shown in your question easier to read:
Map<String, Object> b = new HashMap<>();
a.forEach((k,v) -> b.put(toCamel(k), v));
This is intended as a comment, but got too long for that.
Wanting something like Guava's Maps.transformValues() or Maps.transformEntries() doesn't make too much sense I think.
Those methods return a view of the original map and when you get some
value using a key then the value is transformed by some function that you specified.
(I could be wrong here because I'm not familiar with Guava but I'm making these assumptions based on documentation)
If you wanted to do "transform" the keys then you could do it by writing a wapper for the map like so:
public class KeyTransformingMap<K, V> implements Map {
private Map<K, V> original;
private Function<K, K> reverseTransformer;
public V get(Object transformedKey) {
K originalKey = reverseTransformer.apply((K) transformedKey);
return original.get(originalKey);
}
// delegate all other Map methods directly to original map (or throw UnsupportedOperationException)
}
In your case where you have a map with snake case keys but want camel case keys,
the reverseTransformer function would take in a camel case string and return a snake case string.
I.e reverseTransformer.apply("snakeCase") returns "snake_case" which you can then use as a key for the original map.
Having said all that I think that the straightforward code you suggested is the best option.
I need to validate if map (String to String) entry doesn't contain same key and value pair (case-insensitive). For example -
("hello", "helLo") // is not a valid entry
I was wondering if Google collection's Iterable combined with Predicates some how could solve this problem easily.
Yes I could have simple iterator for entries to do it myself, but thinking of any thing already up.
Looking for something in-lined with Iterables.tryFind(fromToMaster, Predicates.isEqualEntry(IGNORE_CASE)).isPresent()
If you want to use guava, you can use the Maps utils, specifically the filterEntries function.
An example to filter only entries where the key does not equal the value (ignoring the case) could look like this
Map<String, String> map = new HashMap<>();
map.put("hello", "helLo");
map.put("Foo", "bar");
Map<String, String> filtered = Maps.filterEntries(map, new Predicate<Map.Entry<String, String>>() {
#Override
public boolean apply(Map.Entry<String, String> input) {
return !input.getKey().equalsIgnoreCase(input.getValue());
}
});
System.out.println(filtered); // will print {Foo=bar}
However there is no default Predicate in guava's Predicates I know of that does what you want.
Addition:
If you want a validation mechanism without creating a new map, you can use Iterables and the any method to iterate over the entry set of the map. To make the condition more readable I would assign the predicate to a variable or a member field of the class you are working in.
Predicate<Map.Entry<String, String>> keyEqualsValueIgnoreCase = new Predicate<Map.Entry<String, String>>() {
#Override
public boolean apply(Map.Entry<String, String> input) {
return input.getKey().equalsIgnoreCase(input.getValue());
}
};
if (Iterables.any(map.entrySet(), keyEqualsValueIgnoreCase)) {
throw new IllegalStateException();
}
or if you need the entry, you can use the Iterables#tryFind method and use the returned Optional
Optional<Map.Entry<String, String>> invalid = Iterables.tryFind(map.entrySet(), keyEqualsValueIgnoreCase);
if(invalid.isPresent()) {
throw new IllegalStateException("Invalid entry " + invalid.get());
}