Reputation: 1194
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
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
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
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