Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Adding two lists of own type

I have a simple User class with a String and an int property.

I would like to add two Lists of users this way:

  • if the String equals then the numbers should be added and that would be its new value.
  • The new list should include all users with proper values.

Like this:

List1: { [a:2], [b:3] }
List2: { [b:4], [c:5] }
ResultList: {[a:2], [b:7], [c:5]}

User definition:

public class User { 
    private String name;
    private int comments;
}

My method:

public List<User> addTwoList(List<User> first, List<User> sec) {
    List<User> result = new ArrayList<>();
    for (int i=0; i<first.size(); i++) {
        Boolean bsin = false;
        Boolean isin = false;
        for (int j=0; j<sec.size(); j++) {
            isin = false; 
            if (first.get(i).getName().equals(sec.get(j).getName())) {
                int value= first.get(i).getComments() + sec.get(j).getComments();
                result.add(new User(first.get(i).getName(), value));
                isin = true;
                bsin = true;
            }
            if (!isin) {result.add(sec.get(j));}
        }
        if (!bsin) {result.add(first.get(i));}
    }
    return result;      
}

But it adds a whole lot of things to the list.

like image 539
dingoKid Avatar asked Jan 02 '19 17:01

dingoKid


2 Answers

This is better done via the toMap collector:

 Collection<User> result = Stream
    .concat(first.stream(), second.stream())
    .collect(Collectors.toMap(
        User::getName,
        u -> new User(u.getName(), u.getComments()),
        (l, r) -> {
            l.setComments(l.getComments() + r.getComments());
            return l;
        }))
    .values();
  • First, concatenate both the lists into a single Stream<User> via Stream.concat.
  • Second, we use the toMap collector to merge users that happen to have the same Name and get back a result of Collection<User>.

if you strictly want a List<User> then pass the result into the ArrayList constructor i.e. List<User> resultSet = new ArrayList<>(result);


Kudos to @davidxxx, you could collect to a list directly from the pipeline and avoid an intermediate variable creation with:

List<User> result = Stream
    .concat(first.stream(), second.stream())
    .collect(Collectors.toMap(
         User::getName,
         u -> new User(u.getName(), u.getComments()),
         (l, r) -> {
              l.setComments(l.getComments() + r.getComments());
              return l;
         }))
    .values()
    .stream()
    .collect(Collectors.toList());
like image 86
Ousmane D. Avatar answered Oct 01 '22 00:10

Ousmane D.


You have to use an intermediate map to merge users from both lists by summing their ages.

One way is with streams, as shown in Aomine's answer. Here's another way, without streams:

Map<String, Integer> map = new LinkedHashMap<>();
list1.forEach(u -> map.merge(u.getName(), u.getComments(), Integer::sum));
list2.forEach(u -> map.merge(u.getName(), u.getComments(), Integer::sum));

Now, you can create a list of users, as follows:

List<User> result = new ArrayList<>();
map.forEach((name, comments) -> result.add(new User(name, comments)));

This assumes User has a constructor that accepts name and comments.


EDIT: As suggested by @davidxxx, we could improve the code by factoring out the first part:

BiConsumer<List<User>, Map<String, Integer>> action = (list, map) -> 
        list.forEach(u -> map.merge(u.getName(), u.getComments(), Integer::sum));

Map<String, Integer> map = new LinkedHashMap<>();
action.accept(list1, map);
action.accept(list2, map);

This refactor would avoid DRY.

like image 26
fps Avatar answered Oct 01 '22 00:10

fps