Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

java stream Collectors.groupingBy() multiple fields

Stream<Map.Entry<String, Long>> duplicates = notificationServiceOrderItemDto.getService()
    .getServiceCharacteristics()
    .stream()
    .collect(
        Collectors.groupingBy(
            ServiceCharacteristicDto::getName, Collectors.counting()
        )
     )
     .entrySet()
     .stream()
     .filter(e -> e.getValue() > 1);

Optional<String> dupName = duplicates.map(Map.Entry::getKey).findFirst();

works perfect. But I wold like to find duplicates not just with name but also name + value + key

That means if name + value + key is the same this is duplicate.

I am looking Collectors.groupingBy()

http://www.technicalkeeda.com/java-8-tutorials/java-8-stream-grouping

but I can not find correct solution

like image 673
mbrc Avatar asked Jun 15 '18 08:06

mbrc


3 Answers

Following works for me:

public class Groupingby
{
    static class Obj{
        String name;
        String value;
        String key;

        Obj(String name, String val, String key)
        {
            this.name = name;
            this.value = val;
            this.key = key;
        }
    }

    public static void main(String[] args)
    {
        List<Obj> objects = new ArrayList<>();

        objects.add(new Obj("A", "K", "Key1"));
        objects.add(new Obj("A", "K", "Key1"));
        objects.add(new Obj("A", "X", "Key1"));
        objects.add(new Obj("A", "Y", "Key2"));

        Map<List<String>, Long> collected = objects.stream().collect(Collectors.groupingBy(x -> Arrays.asList(x.name, x.value, x.key), Collectors.counting()));

        System.out.println(collected);
    }

}

// Output
// {[A, K, Key1]=2, [A, Y, Key2]=1, [A, X, Key1]=1}

Note that I am using list of attributes for grouping by, not string concatenation of attributes. This will work with non-string attributes as well.

If you are doing string concatenation, you may have some corner cases like attributes (A, BC, D) and (AB, C, D) will result in same string.

like image 178
Ashwinee K Jha Avatar answered Oct 11 '22 17:10

Ashwinee K Jha


Instead of

.collect(Collectors.groupingBy(ServiceCharacteristicDto::getName, Collectors.counting()))

you can write

.collect(Collectors.groupingBy(s->s.getName()+'-'+s.getValue()+'-'+s.getKey(), Collectors.counting()))
like image 34
Eran Avatar answered Oct 11 '22 17:10

Eran


You can replace ServiceCharacteristicDto::getName with:

x -> x.getName() + x.getValue() + x.getKey()

Use a lambda instead of a method reference.

But also think of what findFirst would actually mean here... you are collecting to a HashMap that has no encounter order, streaming its entries and getting the first element - whatever that is. You do understand that this findFirst can give different results on different input, right? Even re-shuffling the HashMap could return you a different findFirst result.

EDIT

to get away from possible un-intentional duplicates because of String concat, you could use:

x -> Arrays.asList(x.getName(), x.getValue(), x.getKey())
like image 3
Eugene Avatar answered Oct 11 '22 17:10

Eugene