Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Reduce a collection of objects by common field in Java-8

I have a list of object Ob defined as

class Ob {
   private String type;
   private List<String> attire;
   // standard getter and setters

   public Ob (String type){
        this.type=type;
    }

    public Ob addAttrire(String att){
        if(attire == null){
            attire = new ArrayList<>();
        }
        attire.add(att);
        return this;
    }
}

I receive objects as

[{
"type" : "upper"
attires : [{"t1","t2"}]
},
{
"type" : "upper"
attires : ["t3","t4"]
},
{
"type" : "lower"
attires : ["l1","l2"]
}]

which I have to combine as

[{
"type" : "upper"
attires : ["t1","t2","t3","t4"]
},{
"type" : "lower"
attires : ["l1","l2"]
}]

How can I use stream to do that. Does reduce help? The stream one can use is

List<Ob> coll = new ArrayList<>();


 coll.add(new Ob("a").addAttrire("1").addAttrire("2").addAttrire("3"));
        coll.add(new Ob("a").addAttrire("1").addAttrire("2").addAttrire("3"));
        coll.add(new Ob("a").addAttrire("1").addAttrire("2").addAttrire("3"));
        coll.add(new Ob("b").addAttrire("1").addAttrire("2").addAttrire("3"));
        coll.add(new Ob("b").addAttrire("1").addAttrire("2").addAttrire("3"));
        coll.add(new Ob("b").addAttrire("1").addAttrire("2").addAttrire("3"));



Collection<Ob> values = coll.stream()
                .collect(toMap(Ob::getType, Function.identity(), (o1, o2) -> {
                    o1.getAttire().addAll(o2.getAttire());
                    return o1;
                })).values();

Updated the question with solution of Ruben. There is no requirement to remove duplicates, but it can be done using set in Ob for attire. The current solution worked flawlessly.

like image 478
rohit Avatar asked Mar 15 '23 15:03

rohit


2 Answers

You could collect toMap with a merge function that merges the lists

    Collection<Ob> values = coll.stream()
     .collect(toMap(Ob::getType, Function.identity(), (o1, o2) -> {
        o1.getAttire().addAll(o2.getAttire());
        return o1;
    })).values();
like image 133
Ruben Avatar answered Mar 17 '23 04:03

Ruben


This solution uses the groupingBy collector and then a separate step that creates a new Ob which is the result of merging all the Obs that have the same type.

I think Rubens solution has an advantage over this answer because it's a little shorter and simpler. I think this answer has an advantage because it doesn't modify the original Obs and hence is more in a functional style.

public static void testTrickyStreamSet() {
    Stream<Ob> s = Stream.of(
        new Ob("a", "1", "2"),
        new Ob("b", "1", "4"),
        new Ob("a", "1", "3"),
        new Ob("b", "1", "5"));

     List<Ob> os = s.collect(groupingBy(o -> o.type))
         .entrySet().stream()
         .map(e -> new Ob(e.getKey(), 
             e.getValue().stream().flatMap(o -> o.attire.stream()).collect(toList())))
         .collect(toList());

     // Prints [<Ob type=a, attire=[1, 2, 3]>, <Ob type=b, attire=[1, 4, 5]>]
     System.out.println(os);
}

public static class Ob {
    public String type;
    public List<String> attire;

    public Ob(String type, String... attire) {
        this.type = type;
        this.attire = Arrays.asList(attire);
    }

    public Ob(String type, List<String> attire) {
        this.type = type;
        this.attire = new ArrayList<>(attire);
    }

    @Override
    public String toString() {
        return "<Ob type=" + type + ", attire=" + attire + ">";
    }
}
like image 26
Lii Avatar answered Mar 17 '23 05:03

Lii