0

I got a list of objects from class A in a list. Some of these objects are equal in id and name but not in list <B> , and list b is ALWAYS different.

I need to merge these so that my list is only made out of object a's with same name and id exists and all the b from same group are collected I can make use of jdk 8 plus utils so streams are ok to use here.. Although I think reflection here is more usable?

PS: I can not change content of a of b class as they are generated classes and no access / expansion possibility

 @Test public void test() { List.of(new A(1, "a1", List.of(new B(1, "1b"))), new A(1, "a1", List.of(new B(2, "2b"))), new A(2, "a2", List.of(new B(3, "3b")))); //expected List.of(new A(1, "a1", List.of(new B(1, "1b"), new B(2, "2b"))), new A(2, "a2", List.of(new B(3, "3b")))); } class A { public A(int id, String name, List<B> listB) { this.id = id; this.name = name; this.listB = listB; } int id; String name; List<B> listB; } class B { public B(int id, String name) { this.id = id; this.name = name; } int id; String name; } 
2
  • They share a common super class or something? What's the input format? Commented Sep 23, 2021 at 13:18
  • nop .. A is huge class and B too xD and no connection, here are the property names indeed misleading Commented Sep 23, 2021 at 13:32

2 Answers 2

2

You could use

record Key(int id, String name) {}; List<A> result = input.stream().collect( Collectors.groupingBy(a -> new Key(a.getId(), a.getName()), LinkedHashMap::new, Collectors.flatMapping(a -> a.getListB().stream(), Collectors.toList()))) .entrySet().stream() .map(e -> new A(e.getKey().id(), e.getKey().name(), e.getValue())) .collect(Collectors.toList()); if(!result.equals(expected)) { throw new AssertionError("expected " + expected + " but got " + result); } 

This constructs new lists with new A objects, which is suitable for immutable objects. Your use of List.of(…) suggests a preference towards immutable objects. If you have mutable objects and want to perform the operation in-place, you could do

List<A> result = new ArrayList<>(input); // only needed if input is an immutable list record Key(int id, String name) {}; HashMap<Key,A> previous = new HashMap<>(); result.removeIf(a -> previous.merge(new Key(a.getId(), a.getName()), a, (old, newA) -> { var l = old.getListB(); if(l.getClass() != ArrayList.class) old.setListB(l = new ArrayList<>(l)); l.addAll(newA.getListB()); return old; }) != a); if(!result.equals(expected)) { throw new AssertionError("expected " + expected + " but got " + result); } 

This removes the duplicates from the list and adds their Bs to the previously encountered original. It does the minimum of changes required to get the intended list, e.g. if there are no duplicates, it does nothing.

If A objects with the same id always have the same name, in other words, there is no need for a key object checking both, you could simplify this approach to

List<A> result = new ArrayList<>(input); // only needed if input is an immutable list HashMap<Integer,A> previous = new HashMap<>(); result.removeIf(a -> previous.merge(a.getId(), a, (old, newA) -> { var l = old.getListB(); if(l.getClass() != ArrayList.class) old.setListB(l = new ArrayList<>(l)); l.addAll(newA.getListB()); return old; }) != a); if(!result.equals(expected)) { throw new AssertionError("expected " + expected + " but got " + result); } 
Sign up to request clarification or add additional context in comments.

Comments

2

If you need preserve an instance for each id you can write (I assume objects have getters and setters)

System.out.println(xs.stream() .collect(groupingBy(A::getId, toList())) .values().stream() .peek(g -> g.get(0).setListB( g.stream() .flatMap(h -> h.getListB().stream()) .collect(groupingBy(B::getId, toList())) .values().stream() .map(i -> i.get(0)) .collect(toList()))) .map(g -> g.get(0)) .collect(toList())); 

your input case with output

[A(id=1, name=a1, listB=[B(id=1, name=b1), B(id=2, name=b2)]), A(id=2, name=a2, listB=[B(id=3, name=b3)])] 

if you can create new instances then you can renormalize the lists

System.out.println(xs.stream() .flatMap(a -> a.getListB().stream().map(b -> List.<Object>of(a.id, a.name, b.id, b.name))) .distinct() .collect(groupingBy(o -> o.get(0), toList())) .values() .stream() .map(zs -> new A((int) zs.get(0).get(0), (String) zs.get(0).get(1), zs.stream().map(z -> new B((int) z.get(2), (String) z.get(3))).collect(toList()))) .collect(toList())); 

(you can change the ugly .get(0).get(0) using some intermediate class called DenormalizedRow or so)

4 Comments

only problem here i do not know which part of B is filled in, i just know they are always different:-S but they can be even null ...you will not have 2 nulls for same A though!
@UnnameDSoS filter as required every stream
would it be possible to create a collector from this ? or would it not be possible since you need previous item?
@UnnameDSoS I do not understand your question (you can create a collector but, why?)

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.