I have a list of pairs as follows:
// pairs = [(k1, obj1), (k2, obj1)]
List<Pair<String, Object> pairs;
Then I want to expand those objects to many objects and reduce them by key
pairs.stream().map(pair -> {
// every object is expanded into many objects, obj11, obj12, ...
List<Object> objects = expand(pair.getRight());
// the return is [(k1, obj11), (k1, obj12), (k2, obj21), (k2, obj22)]
return objects.stream().map(object -> new MutablePair<String, Object>(pair.getLeft(), object)).collect(Collectors.toList());
}).reduce(...
// how to reduce it by key and get a list of pairs
...);
My question is, how to reduce the expanded objects by key and get a list of pairs?
I mean the expected result is:
pairs = [(k1, obj11), (k1, obj12), (k2, obj21), (k2, obj22)]
Looks like you need flatMap instead of map, and collect instead of reduce:
List<Pair<String, Object>> expandedPairs =
pairs.stream()
.flatMap(pair -> expand(pair.getRight()).stream()
.map(object -> new MutablePair<String, Object>(pair.getLeft(), object)))
.collect(Collectors.toList());
Related
I need to map a list of pairs of objects into <ocurrences, list of Objs with those ocurrences>, I've tried using streams directly on the input list of pairs but I'm still kind of new to java and couldn't figure it out, so I was trying to do something like this, but it's probably not close to the best way to do it.
public Map<Integer,ArrayList<Obj>> numBorders(List<Pair<Obj,Obj>> lf) {
Map<Integer,ArrayList<Obj>> nBorders = new HashMap<>();
List<Obj> list = new ArrayList<>();
for(Pair<Obj, Obj> pair : lf) {
list.add(pair.getKey());
list.add(pair.getValue());
}
nBorders = list.stream().collect(Collectors.groupingBy(...);
return nBorders;
}
so for example, for lf = {(o1,o2),(o3,o2),(o5,o4),(o4,o1),(o3,o4),(o7,o1),(o5,o8),(o3,o10),(o4,o5),(o3,o7),(o9,o8)} the result should be {(1,{o9,o10}),(2,{o2,o7,o8,}),(3,{o1,o5}),(4,{o3,o4})}.
I'm really confused on how to do this, if someone could help, I'd appreciate it, thanks.
This can be done this way:
create a stream from the pairs to concatenate first/second values using Stream::flatMap
count the occurrences - build an intermediate map <Obj, Integer> using Collectors.groupingBy + Collectors.summingInt (to keep integer)
create an inverse map <Integer, List> from the stream of the entries in the intermediate map using Collectors.groupingBy + Collectors.mapping
Optionally, if an order in the resulting map is critical, a LinkedHashMap may be created from the entries of the intermediate frequency map sorted by value.
public Map<Integer,ArrayList<Obj>> numBorders(List<Pair<Obj,Obj>> lf) {
return lf.stream() // Stream<Pair>
.flatMap(p -> Stream.of(p.getKey(), p.getValue())) // Stream<Obj>
.collect(Collectors.groupingBy(
obj -> obj,
Collectors.summingInt(obj -> 1)
)) // Map<Obj, Integer>
.entrySet()
.stream() // Stream<Map.Entry<Obj, Integer>>
.sorted(Map.Entry.comparingByValue())
.collect(Collectors.groupingBy(
Map.Entry::getValue, // frequency is key
LinkedHashMap::new,
Collectors.mapping(Map.Entry::getKey, Collectors.toList())
)); // Map<Integer, List<Obj>>
}
I converted below json data (in example) to List<Map<String, String>> and from that i want to construct a new Map using Java 8 streams and the output should look like below. could someone help me with this?
Key value
Service1: DEACTIVATED
Service2: ACTIVATED
Service3: DEACTIVATED
Ex:
[
{
name=Service1,
desiredState=DEACTIVATED
},
{
name=Service2,
desiredState=ACTIVATED
},
{
name=Service3,
desiredState=DEACTIVATED
}
]
From what I could comprehend, you aim to convert List<Map<String,String>> to Map<String,String>.
List<Map<String,String>> myMap = .... // map which you have already.
Map<String,String> resultMap = myMap.stream()
.flatMap(map -> map.entrySet().stream()) // Get a flatMap of the entryset. This will form a stream of Map.Entry
.collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue(), (k1, k2) -> k2));
Here (k1,k2) -> k2 is a merge function in case if there are multiple entries for same keys while constructing the resultMap.
If I get it well, you need to merge your maps in a way such that the value of the name key is the key that maps to the value of the desiredState key. You could do it this way:
Map<String, String> result = listOfMaps.stream()
.collect(Collectors.toMap(
m -> m.get("name"),
m -> m.get("desiredState"),
(o, n) -> n));
This assumes that all maps from the list have name and desiredState entries. The (o, n) -> n merge function must be provided, in case there are collisions when creating the result map (i.e. entries with the same key). Here, between old and new values, I've chosen the new value.
I have a large list of items that I need to convert into a map of items of same type:
List<Item> items = //10^6 items of different types
Map<Type, List<Item>> itemsByType = new ConcurrentHashMap<>();
for (Item item : items) {
itemsByType.computeIfAbsent(
item.getType(),
i -> new ArrayList<>()
).add(item);
}
Each type is then ordered by long type identifier; and, each list of type-items is ordered by long item identifier. And, finally, the ordered list is processed.
This works fine, but I'm wondering if there's a more efficient way to do all of this...?
You can use java-8 groupingBy
Map<Type, List<Item>> itemsByType = items.stream()
.sorted(Comparator) //for any sorting you can use sorted with comparator
.collect(Collectors.groupingBy(Item::getType));
If you want ConcurrentHashMap you can use groupingByConcurrent
ConcurrentMap<Type, List<Item>> itemsByType = items.stream()
.collect(Collectors.groupingByConcurrent(Item::getType));
You can use the overloaded groupingBy with TreeMap so the map is already sorted based on key
TreeMap<Type, List<Item>> map = list
.stream()
.collect(Collectors.groupingBy(
Item::Type,
() -> new TreeMap<>(Comparator.comparingLong(Type::getId)),
Collectors.toList()));
You can also collect the map with sorted keys and sorted values in one chain
Map<Type, List<Item>> str = list1.stream()
.collect(
Collectors.groupingBy(
Item::Type,
() -> new TreeMap<>(Comparator.comparingLong(Type::getId)),
Collectors.collectingAndThen(
Collectors.toList(),
list -> list.stream()
.sorted(Comparator.comparingLong(Item::getId))
.collect(Collectors.toList()))));
You could use a MultiMap, e.g., guava's. Here is their code example:
ListMultimap<String, String> multimap = ArrayListMultimap.create();
for (President pres : US_PRESIDENTS_IN_ORDER) {
multimap.put(pres.firstName(), pres.lastName());
}
for (String firstName : multimap.keySet()) {
List<String> lastNames = multimap.get(firstName);
out.println(firstName + ": " + lastNames);
}
... produces output such as:
Zachary: [Taylor]
John: [Adams, Adams, Tyler, Kennedy] // Remember, Quincy!
George: [Washington, Bush, Bush]
Grover: [Cleveland, Cleveland] // Two, non-consecutive terms, rep'ing NJ!
...
A TreeMultimap has sorted keys and values, which is what you want, if I understood your title correctly.
A Multimap is particularly useful in case you need to check if a certain value is present for a certain key, because that is supported without getting the collection for the key and then searching that:
multimap.containsEntry("John", "Adams");
I would like to convert my map which looks like this:
{
key="someKey1", value=Apple(id="1", color="green"),
key="someKey2", value=Apple(id="2", color="red"),
key="someKey3", value=Apple(id="3", color="green"),
key="someKey4", value=Apple(id="4", color="red"),
}
to another map which puts all apples of the same color into the same list:
{
key="red", value=list={apple1, apple3},
key="green", value=list={apple2, apple4},
}
I tried the following:
Map<String, Set<Apple>> sortedApples = appleMap.entrySet()
.stream()
.collect(Collectors.toMap(l -> l.getColour, ???));
Am I on the right track? Should I use filters for this task? Is there an easier way?
Collectors.groupingBy is more suitable than Collectors.toMap for this task (though both can be used).
Map<String, List<Apple>> sortedApples =
appleMap.values()
.stream()
.collect(Collectors.groupingBy(Apple::getColour));
Or, to group them into Sets use:
Map<String, Set<Apple>> sortedApples =
appleMap.values()
.stream()
.collect(Collectors.groupingBy(Apple::getColour,
Collectors.mapping(Function.identity(),
Collectors.toSet())));
or (as Aomine commented):
Map<String, Set<Apple>> sortedApples =
appleMap.values()
.stream()
.collect(Collectors.groupingBy(Apple::getColour, Collectors.toSet()));
if you want to proceed with toMap you can get the result as follows:
map.values() // get the apples
.stream() // Stream<Apple>
.collect(toMap(Apple::getColour, // group by colour
v -> new HashSet<>(singleton(v)), // have values as set of apples
(l, r) -> {l.addAll(r); return l;})); // merge colliding apples by colour
stream over the map values instead of entrySet because we're not concerned with the map keys.
Apple::getColour is the keyMapper function used to extract the "thing" we wish to group by, in this case, the Apples colour.
v -> new HashSet<>(singleton(v)) is the valueMapper function used for the resulting map values
(l, r) -> {l.addAll(r); return l;} is the merge function used to combine two HashSet's when there is a key collision on the Apple's colour.
finally, the resulting map is a Map<String, Set<Apple>>
but this is better with groupingBy and toSet as downstream:
map.values().stream().collect(groupingBy(Apple::getColour, toSet()));
stream over the map values instead of entrySet because we're not concerned with the map keys.
groups the Apple's by the provided classification function i.e. Apple::getColour and then collect the values in a Set hence the toSet downstream collector.
finally, the resulting map is a Map<String, Set<Apple>>
short, readable and the idiomatic approach.
You could also do it without a stream:
Map<String, Set<Apple>> res = new HashMap<>();
map.values().forEach(a -> res.computeIfAbsent(a.getColour(), e -> new HashSet<>()).add(a));
iterate over the map values instead of entrySet because we're not concerned with the map keys.
if the specified key a.getColour() is not already associated with a value, attempts to compute its value using the given mapping function e -> new HashSet<>() and enters it into the map. we then add the Apple to the resulting set.
if the specified key a.getColour() is already associated with a value computeIfAbsent returns the existing value associated with it and then we call add(a) on the HashSet to enter the Apple into the set.
finally, the resulting map is a Map<String, Set<Apple>>
You can use Collectors.groupingBy and Collectors.toSet()
Map<String, Set<Apple>> sortedApples = appleMap.values() // Collection<Apple>
.stream() // Stream<Apple>
.collect(Collectors.groupingBy(Apple::getColour, // groupBy colour
Collectors.mapping(a -> a, Collectors.toSet()))); // collect to Set
You've asked how to do it with streams, yet here's another way:
Map<String, Set<Apple>> result = new LinkedHashMap<>();
appleMap.values().forEach(apple ->
result.computeIfAbsent(apple.getColor(), k -> new LinkedHashSet<>()).add(apple));
This uses Map.computeIfAbsent, which either returns the set mapped to that color or puts an empty LinkedHashSet into the map if there's nothing mapped to that color yet, then adds the apple to the set.
EDIT: I'm using LinkedHashMap and LinkedHashSet to preserve insertion order, but could have used HashMap and HashSet, respectively.
I have a structure such as Map<String,List<Map<String,Object>>. I want to apply a function to the map as follows. The method takes a key and uses a
Map<String,Object> of the list. Each key has several Map<String,Object> in the list. How can I apply the process method to the map's key for each value of Map<String,Object>? I was able to use to forEach loops(see below) but I have a feeling this is not the best way to solve the problem in a functional way.
TypeProcessor p=new TypeProcessor.instance();
//Apply this function to the key and each map from the list
// The collect the Result returned in a list.
Result process(String key, Map<String,Object> dataPoints);
List<Result> list = new ArrayList<>();
map.forEach(key,value) -> {
value.forEach(innerVal -> {
Result r=p.process(key,innerVal);
list.add(r):
});
});
It seems from your code that you want to apply process for the entire Map, so you could do it like this:
List<Result> l = map.entrySet()
.stream()
.flatMap(e -> e.getValue().stream().map(value -> process(e.getKey(), value)))
.collect(Collectors.toList());
Well, assuming map contains key, you don't need any foreach. Just obtain the value from the outer map, stream it, map to your new object and collect to a List:
List<Result> list =
map.get(key)
.stream()
.map(v -> p.process(key,v))
.collect(Collectors.toList());