'Mapping pair of elements into one with Stream IPA
So I'm wondering what is the best solution to the following problem:
I have a list of items (a custom class) in a java collection ex
List<Item> itemList = ... item1,item2,item3 etc
Each item in the collection however has a corresponding logical pair also in the collection (so the pair's are not necessarily following each other by index in the collection)
I have a helper method like
Item calculateCorrectItem(Item item1, Item item2)
which can return the correct one of a pair based on some business logic (the details of that is not relevant)
I would like to replace an item and its pair in the collection, with the result of the method above - so that every 2 elements of a pair in the collection are replaced with the calculated one based on those two.
Some details:
We can assume that every element has one and only one pair.
Each item has the pair's ID as a property, like
public class Item {
private String id;
private String pairId;
the equal method is true when the ID of two items are the same.
...getters,setters
}
Also, the references in the collection which i want to filter are also existing in a global cache, where every Item can be easily retrieved from, like
globalCache.getItemById(String id)
So an actual pair reference can be easily retrieved if the ID of the pair is known.
What could be an elegant solution (maybe by utilizing the Stream IPA)? In the end, the only expectation is that the collection contains one Item of each pair, the ordering doesn't matter.
Solution 1:[1]
Here's another approach that performs a mutable reduction using a map (you can use a hash map if preserving the source list's order of pair IDs is unimportant):
Collection<Item> correctItems1 = itemList.stream().collect(
LinkedHashMap<String, Item>::new,
(map, item) -> map.merge(item.getPairId(), item, this::calculateCorrectItem),
Map::putAll)
.values();
List<Item> result = new ArrayList<>(correctItems1);
Solution 2:[2]
I'm assuming that method calculateCorrectItem(Item item1, Item item2) will produce the same result regardless of the order of the arguments and that duplicated results has to be removed from the resulting list.
List<Item> items = ... ; // obtain the items
Map<String, Item> itemById = items.stream()
.collect(Collectors.toMap(Item::getId, // relies on uniquness of Id
Function.identity()));
// set is used to alliminate duplicates since their order is not important
Set<Item> itemSet = items.stream()
.map(item-> pairById.containsKey(item.getPairId()) ? item : // if pair isn't present return the same item, othewise merge them
calculateCorrectItem(item, pairById.get(item.getPairId())))
.collect(Collectors.toSet());
List<Item> result = new ArrayList<>(itemSet);
Solution 3:[3]
Here is another approach using a Collectors.toMap with a merge function:
- First, create a record for demo and intialize a list with some data
record Item(String getId, int getValue) {
}
Random r = new Random();
List<Item> items = r.ints(10, 1, 5)
.mapToObj(id -> new Item(id+"", r.nextInt(100) + 1))
.toList();
System.out.println("The raw data");
items.forEach(System.out::println);
System.out.println();
- Now stream the list
- use the third argument of
toMapto "merge" the new items.
Collection<Item> collection = items.stream()
.collect(Collectors.toMap(Item::getId, item->item,
(item1, item2) -> calculateCorrectItem(item1,
item2)))
.values();
System.out.println("The new list of combined items");
collection.forEach(System.out::println);
Prints
The raw data
Item[getId=1, getValue=14]
Item[getId=4, getValue=42]
Item[getId=4, getValue=19]
Item[getId=2, getValue=16]
Item[getId=4, getValue=20]
Item[getId=3, getValue=57]
Item[getId=3, getValue=47]
Item[getId=3, getValue=22]
Item[getId=1, getValue=3]
Item[getId=4, getValue=73]
The new list of combined items
Item[getId=1, getValue=17]
Item[getId=2, getValue=16]
Item[getId=3, getValue=126]
Item[getId=4, getValue=154]
The method used for the above. It just sums the values and returns a new Item instance.
public static Item calculateCorrectItem(Item one, Item two) {
return new Item(one.getId(), one.getValue() + two.getValue());
}
And a simple non-stream solution prints out the same results as before.
Map<String, Item> result = new HashMap<>();
for (Item item : items) {
result.compute(item.getId(),
(k, v) -> v == null ? item : new Item(v.getId(),
v.getValue() + item.getValue()));
}
result.values().forEach(System.out::println);
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | |
| Solution 2 | |
| Solution 3 |
