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.
With streams, you would have to do this using indexed access:
List<Item> calculated =
IntStream.range(0, itemList.size() / 2)
.mapToObj(i -> calculateCorrectItem(itemList.get(2*i+0), itemList.get(2*i+1))
.collect(toList());
If you want to merge items based on their IDs, you can group the items by their ID:
itemList.stream()
.collect(groupingBy(Item::getId)) // Yields a Map<IdType, List<Item>>
.values() // Yields a Collection<List<Item>>, where each List<Item> contains items with the same id.
.stream()
.map(is -> /* invoke calculateCorrectItem, handling 1, 2 or more items in the list */)
.collect(...)
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);
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);