RaT
RaT

Reputation: 1194

Apply operations on groupBy result of stream

I have a List of fruit which contains name and ID

List<Fruit> fruitList = Arrays.asList(new Fruit("apple", 1), new Fruit("orange", 2), new Fruit("orange", 3));

I want to rename the fruit name with name_Id only if duplicate fruit name exists like in case of my list orange will rename to orange_2 and orange_3 but the apple will remain apple.. How can I do it in single stream expression.

The solution that I came up with is

Map<String, List<Fruit>> fruitMap = fruitList.stream().collect(Collectors.groupingBy(Fruit::getName));

        Set<Entry<String, List<Fruit>>> entry = fruitMap.entrySet();

        for (Entry<String, List<Fruit>> en : entry) {
            List<Fruit> fruit = en.getValue();
            if (fruit.size() > 1) {
                fruit.stream().map(d -> {
                    d.setName(d.getName() + "_" + d.getId());
                    return d;
                }).collect(Collectors.toList());
            }
        }    
    }

but this is way more than one stream expression.

Upvotes: 2

Views: 139

Answers (3)

fps
fps

Reputation: 34460

Here's a (sort of) one-liner:

fruitList.stream().collect(Collectors.groupingBy(Fruit::getName))
    .values().stream()
        .filter(list -> list.size() > 1)
        .forEach(list -> list.forEach(Fruit::renameWithId));

This assumes you have the following method in Fruit:

public void renameWithId() {
    name = name + "_" + id;
}

If you can't modify your Fruit class, you could do the renaming inline:

fruitList.stream().collect(Collectors.groupingBy(Fruit::getName))
    .values().stream()
        .filter(list -> list.size() > 1)
        .forEach(list -> list.forEach(fruit ->
            fruit.setName(fruit.getName() + "_" + fruit.getId())));

These long one-liners are so long that they are no longer one-liners, despite their name... Besides, the code ends up being hardly readable and is a pain to maintain and test. So I suggest you move the code that traverses the map and renames the fruits to a new method:

private void renameRepeatedFruits(Map<String, List<Fruit>> fruitMap) {
    fruitMap.values().stream()
        .filter(list -> list.size() > 1)
        .forEach(list -> list.forEach(Fruit::renameWithId));
}

This would allow you to greatly simplify the first version of the code to:

renameRepeatedFruits(
    fruitList.stream().collect(Collectors.groupingBy(Fruit::getName)));

Upvotes: 3

RaT
RaT

Reputation: 1194

Below is the code I came up with, but it is still in 2 expression

Map<String, Long> fruitMap = fruitList.stream()
                .collect(Collectors.groupingBy(Fruit::getName, Collectors.counting()));

        fruitList.forEach(f -> {
            if (fruitMap.get(f.getName()) > 1) {
                f.setName(f.getName() + "_" + f.getId());
            }
        });

Upvotes: 0

Eugene
Eugene

Reputation: 120858

I don't think you can get away without streaming a few times. Here is a smaller solution and slightly more readable I think:

 Set<String> repeated = fruits.stream()
            .collect(Collectors.groupingBy(Fruit::getName, Collectors.counting()))
            .entrySet()
            .stream()
            .filter(e -> e.getValue() > 1)
            .map(Entry::getKey)
            .collect(Collectors.toSet());

    fruits.forEach(f -> {
        if (repeated.contains(f.getName())) {
            f.setName(f.getName() + "_" + f.getId());
        }
    });

    System.out.println(fruits); // [name = apple id = 1, name = orange_2 id = 2, name = orange_3 id = 3] 

Upvotes: 2

Related Questions