2

I'm struggling to reduce a list :

Let's say I have a List<Item> listItems with an Item class defined such as :

public class Item {
    private String effect;
    private String value;

    public String getEffect() {
        return effect;
    }

    public void setEffect(String effect) {
        this.effect = effect;
    }

    public String getValue() {
        return value;
    }

    public void setValue(String value) {
        this.value = value;
    }
}

Now, in my listItems list, I have some elements with the same effect property.

I want to remove all element from my listItems list with the same effect property except the one with the higher value property. (value is number represented as a String).

Also, I wanna keep all the element with an unique effect property.

How can I achieve that ? I guess I'm gonna have to deal with Comparator.

Stefan Zobel
  • 3,182
  • 7
  • 28
  • 38
Ellone
  • 3,644
  • 12
  • 40
  • 72
  • Do you want to do it for a given `effect` or need to search for all duplicate `effect`s ? – c0der Oct 18 '18 at 17:33
  • all duplicate `effect` unfortunatly, My list needs to become `effect` unique. – Ellone Oct 18 '18 at 17:34
  • One way is you can implement hashCode and equals method in Item class based on effect variable and then in just one line you can create a Set which will remove all duplicate Item based on effect and will have effect unique. – Pushpesh Kumar Rajwanshi Oct 18 '18 at 17:39
  • 2
    think of formulating these into `Map` where keySet would be your unique effects and their values would be the Item with the largest value corresponding to the effect. – Naman Oct 18 '18 at 17:39
  • can you override `equals` and `hashCode` for `Item`? if yes, based on both or a single property? – Eugene Oct 18 '18 at 17:51

3 Answers3

4

It seems that you want to group the items of your list by effect, using max(value) as an aggregation. You can do this as follows:

Map<String, Item> byEffectHavingMaxValue = listItems.stream()
    .collect(Collectors.toMap(
            Item::getEffect,      // group by effect
            Function.identity(),  // keep items as values
            BinaryOperator.maxBy( // when effect exists, keep the item with max value
                    Comparator.comparingInt(i -> Integer.parseInt(i.getValue())))));

Collection<Item> result = byEffectHavingMaxValue.values();

The solution above collects elements of the stream to a Map. For this, it uses an overload of Collectors.toMap that requires 3 arguments:

  • keyMapper: a Function that transforms elements of the stream to keys
  • valueMapper: a Function that transforms elements of the stream to values
  • mergeFunction: a BinaryOperator that is used to resolve collisions between values associated with the same key

In this case, the Item::getEffect method reference is being used as the keyMapper function, which transforms an Item instance to its effect. Then, Function.identity() is being used as the valueMapper function, which does nothing, i.e. it leaves each Item instance untouched. Finally, BinaryOperator.maxBy(Comparator.comparingInt(i -> Integer.parseInt(i.getValue()))) is being used as the mergeFunction, which is a BinaryOperator that receives two values of the map that have the same key (i.e. two Item instances with the same effect), and resolves the collision in the map by selecting the Item instance that has the max value (value is first converted to Integer, so that we compare numeric values instead of strings).

fps
  • 33,623
  • 8
  • 55
  • 110
  • the OP also needs elements that don't share other `effect` property, isn't it? – Eugene Oct 18 '18 at 19:37
  • right, but this map will report `effect` that is present in two `Item`s. So if you have `ItemA{effect = 1, value = 2}; ItemB={effect=1, value=3}; ItemC={effect = 3, value = 10}` the OP needs two things, first is the result of your operation, second is `ItemC` only - as it's the only one in the `List` that does not share `effect` with anyone else - at least this is my understanding – Eugene Oct 18 '18 at 19:51
  • This seems like the right way to achieve what I want. Could you elaborate on the `toMap` parameters ? From what I've read [here](https://docs.oracle.com/javase/8/docs/api/java/util/stream/Collectors.html#toMap-java.util.function.Function-java.util.function.Function-java.util.function.BinaryOperator-) it's using effect as a key, the Int Comparator as a merging function, but I don't really get what does the "mapping function" `identity` do. Keeping item as values ? (Like an hascode ?) Thanks – Ellone Oct 19 '18 at 08:57
1
  1. As explained by nullpointer, you could have a map with effect as key ,
    and item as value. Please refer the code snippets below

Add elements to list like shown below

List<Item> itemList = new ArrayList<>();
itemList.add(new Item("e1", "10"));
itemList.add(new Item("e1", "20"));
itemList.add(new Item("e1", "30"));
itemList.add(new Item("e2", "10"));
itemList.add(new Item("e3", "10"));
itemList.add(new Item("e3", "-10"));

Logic to eliminate duplicates and keep items with highest value

Map<String, Item> effectMap = new HashMap<>();
for (Item item : itemList) {
    Item itemWithHigherValue = effectMap.get(item.getEffect());
    if (itemWithHigherValue == null || 
        (Integer.parseInt(item.getValue()) > 
        Integer.parseInt(itemWithHigherValue.getValue()))) 
    {
        effectMap.put(item.getEffect(), item);
    }

        }

Printing the results

List<Item> uniqueItemList = new ArrayList<Item>(effectMap.values());

for (Item item : uniqueItemList) {
    System.out.println(item);
}
Vinoth A
  • 1,099
  • 9
  • 10
1

"I wanna keep all the element with an unique effect property": assuming all Item objects have valid effect and value :

//group list entries to sub - lists having the same effect value
Map<String, List<Item>> map = itemList.stream()
                                      .collect(Collectors.groupingBy(Item::getEffect)) ;

List<Item> uniqueEfectMaxValue = new ArrayList<>();
//get item having max value and add to uniqueEfectMaxValue
map.forEach((key,list)->{
                          Item item = list.stream().
                             collect(Collectors.maxBy(Comparator.
                                  comparing(Item::getValue))).get();
                          uniqueEfectMaxValue.add(item);
                         }
             );
c0der
  • 18,467
  • 6
  • 33
  • 65