如何在执行 Collectors.toMap() 之前删除会导致冲突的键
How to remove Keys that would cause Collisions before executing Collectors.toMap()
,但是,我不想忽略重复值,而是想事先从该流中删除任何值并将它们打印出来。
例如,来自这个片段:
Map<String, String> phoneBook = people.stream()
.collect(toMap(Person::getName,
Person::getAddress));
如果有重复条目,将导致抛出 java.lang.IllegalStateException: Duplicate key
错误。
该问题中提出的解决方案使用 mergeFunction
在发现碰撞时保留第一个条目。
Map<String, String> phoneBook =
people.stream()
.collect(Collectors.toMap(
Person::getName,
Person::getAddress,
(address1, address2) -> {
System.out.println("duplicate key found!");
return address1;
}
));
如果流中的重复键发生冲突,我不想保留第一个条目,而是想知道是哪个值导致了冲突,并确保在生成的映射中没有出现该值。
即如果 "Bob"
在流中出现三次,它不应该出现在 map 中一次。
在创建该地图的过程中,我想过滤掉任何重复的名称并以某种方式记录它们。
我想确保在创建列表时不能有重复的条目,并且有某种方法可以知道哪些条目在传入流中有重复的键。我正在考虑事先使用 groupingBy
和 filter
来查找重复键,但我不确定最好的方法是什么。
在处理完整个输入流之前,您无法知道哪些键是重复的。因此,任何 pre-processing 步骤都必须在您的主要逻辑之前完成输入的完整传递,这是一种浪费。
另一种方法可以是:
- 使用合并函数为违规键插入虚拟值
- 同时,将有问题的密钥插入
Set<K>
- 处理输入流后,迭代
Set<K>
以从主映射中删除有问题的键。
如果我没有正确理解您在评论中的说明,那么在您的列表中出现不止一次的人不应包含在最终地图中。
if "Bob" appeared three times in the stream, it should not be in the map even once.
并且每个出现不止一次的人都应该存储在List
个重复的人中。
I would like to filter out any duplicate names and record them some way.
这应该是您要找的。
List<String> peopleDuplicated = new ArrayList<>();
Map<String, String> phoneBook2 = people.stream()
.collect(Collectors.groupingBy(Person::getName)) //grouping people by name
.entrySet().stream() //creating a stream from the entries of the grouped map
.peek(e -> {
//Adding to the duplicated names list the name of every person with more than one address
if (e.getValue().size() > 1){
peopleDuplicated.add(e.getKey());
}
})
.filter(e -> e.getValue().size() == 1) //keeping only the people which have occurred only once (no duplicates)
.collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue().get(0).getAddress())); //mapping the entries into a new map
警告
由于重复元素是通过有状态的 lambda 存储的,因此该解决方案应 仅 与 non-parallel 流一起使用,因为其结果可能是在并行执行中不可预测。
用数学术语来说,您想对分组聚合进行分区并分别处理这两个部分。
Map<String, String> makePhoneBook(Collection<Person> people) {
Map<Boolean, List<Person>> phoneBook = people.stream()
.collect(Collectors.groupingBy(Person::getName))
.values()
.stream()
.collect(Collectors.partitioningBy(list -> list.size() > 1,
Collectors.mapping(r -> r.get(0),
Collectors.toList())));
// handle duplicates
phoneBook.get(true)
.forEach(x -> System.out.println("duplicate found " + x));
return phoneBook.get(false).stream()
.collect(Collectors.toMap(
Person::getName,
Person::getAddress));
}
I would like to remove any values from that stream beforehand.
正如@JimGarrison 指出的那样,预处理数据没有意义。
在处理所有数据集之前,您无法提前知道名称是否唯一。
您必须考虑的另一件事是,在流管道内部(在收集器之前)您了解之前遇到的数据。因为中间操作的结果不应该依赖于任何状态。
如果您认为流的行为就像一系列循环,因此假设可以在收集流元素之前对其进行预处理,那是不正确的。流管道的元素一次一个地被延迟处理。 IE。 管道中的所有操作将应用于单个元素并且每个操作仅在需要时应用(这就是懒惰的意思)。
有关更多信息,请查看 this tutorial and API documentation
实施
您可以通过利用 Collectors.teeing()
和 自定义对象 在 单流语句 中分离唯一值和重复项包含 duplicated 和 unique 条目的单独集合 phone book.
由于这个对象的主要功能只是携带数据,所以我将其实现为 Java 16 记录。
public record FilteredPhoneBook(Map<String, String> uniquePersonsAddressByName,
List<String> duplicatedNames) {}
Collector teeing()
需要三个参数:两个 collectors 和一个 function 合并两个收集器产生的结果。
groupingBy()
结合 counting()
生成的 map 用于确定重复的名称。
由于没有必要处理数据,用作第二个收集器的toMap()
将创建一个包含所有名称.
当两个收集器将结果交给 merger
函数时,它会负责删除重复项。
public static FilteredPhoneBook getFilteredPhoneBook(Collection<Person> people) {
return people.stream()
.collect(Collectors.teeing(
Collectors.groupingBy(Person::getName, Collectors.counting()), // intermediate Map<String, Long>
Collectors.toMap( // intermediate Map<String, String>
Person::getName,
Person::getAddress,
(left, right) -> left),
(Map<String, Long> countByName, Map<String, String> addressByName) -> {
countByName.values().removeIf(count -> count == 1); // removing unique names
addressByName.keySet().removeAll(countByName.keySet()); // removing all duplicates
return new FilteredPhoneBook(addressByName, new ArrayList<>(countByName.keySet()));
}
));
}
另一种解决此问题的方法是利用 Map<String,Boolean>
作为发现重复项的方法,正如@Holger 所建议的那样。
第一个收集器将使用 toMap()
编写。它将 true
与只遇到过一次的键相关联,如果至少找到一个重复项,它的 mergeFunction
将分配 false
的值。
其余逻辑不变
public static FilteredPhoneBook getFilteredPhoneBook(Collection<Person> people) {
return people.stream()
.collect(Collectors.teeing(
Collectors.toMap( // intermediate Map<String, Boolean>
Person::getName,
person -> true, // not proved to be a duplicate and initially considered unique
(left, right) -> false), // is a duplicate
Collectors.toMap( // intermediate Map<String, String>
Person::getName,
Person::getAddress,
(left, right) -> left),
(Map<String, Boolean> isUniqueByName, Map<String, String> addressByName) -> {
isUniqueByName.values().removeIf(Boolean::booleanValue); // removing unique names
addressByName.keySet().removeAll(isUniqueByName.keySet()); // removing all duplicates
return new FilteredPhoneBook(addressByName, new ArrayList<>(isUniqueByName.keySet()));
}
));
}
main()
- 演示
public static void main(String[] args) {
List<Person> people = List.of(
new Person("Alise", "address1"),
new Person("Bob", "address2"),
new Person("Bob", "address3"),
new Person("Carol", "address4"),
new Person("Bob", "address5")
);
FilteredPhoneBook filteredPhoneBook = getFilteredPhoneBook(people);
System.out.println("Unique entries:");
filteredPhoneBook.uniquePersonsAddressByName.forEach((k, v) -> System.out.println(k + " : " + v));
System.out.println("\nDuplicates:");
filteredPhoneBook.duplicatedNames().forEach(System.out::println);
}
输出
Unique entries:
Alise : address1
Carol : address4
Duplicates:
Bob
例如,来自这个片段:
Map<String, String> phoneBook = people.stream()
.collect(toMap(Person::getName,
Person::getAddress));
如果有重复条目,将导致抛出 java.lang.IllegalStateException: Duplicate key
错误。
该问题中提出的解决方案使用 mergeFunction
在发现碰撞时保留第一个条目。
Map<String, String> phoneBook =
people.stream()
.collect(Collectors.toMap(
Person::getName,
Person::getAddress,
(address1, address2) -> {
System.out.println("duplicate key found!");
return address1;
}
));
如果流中的重复键发生冲突,我不想保留第一个条目,而是想知道是哪个值导致了冲突,并确保在生成的映射中没有出现该值。
即如果 "Bob"
在流中出现三次,它不应该出现在 map 中一次。
在创建该地图的过程中,我想过滤掉任何重复的名称并以某种方式记录它们。
我想确保在创建列表时不能有重复的条目,并且有某种方法可以知道哪些条目在传入流中有重复的键。我正在考虑事先使用 groupingBy
和 filter
来查找重复键,但我不确定最好的方法是什么。
在处理完整个输入流之前,您无法知道哪些键是重复的。因此,任何 pre-processing 步骤都必须在您的主要逻辑之前完成输入的完整传递,这是一种浪费。
另一种方法可以是:
- 使用合并函数为违规键插入虚拟值
- 同时,将有问题的密钥插入
Set<K>
- 处理输入流后,迭代
Set<K>
以从主映射中删除有问题的键。
如果我没有正确理解您在评论中的说明,那么在您的列表中出现不止一次的人不应包含在最终地图中。
if "Bob" appeared three times in the stream, it should not be in the map even once.
并且每个出现不止一次的人都应该存储在List
个重复的人中。
I would like to filter out any duplicate names and record them some way.
这应该是您要找的。
List<String> peopleDuplicated = new ArrayList<>();
Map<String, String> phoneBook2 = people.stream()
.collect(Collectors.groupingBy(Person::getName)) //grouping people by name
.entrySet().stream() //creating a stream from the entries of the grouped map
.peek(e -> {
//Adding to the duplicated names list the name of every person with more than one address
if (e.getValue().size() > 1){
peopleDuplicated.add(e.getKey());
}
})
.filter(e -> e.getValue().size() == 1) //keeping only the people which have occurred only once (no duplicates)
.collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue().get(0).getAddress())); //mapping the entries into a new map
警告
由于重复元素是通过有状态的 lambda 存储的,因此该解决方案应 仅 与 non-parallel 流一起使用,因为其结果可能是在并行执行中不可预测。用数学术语来说,您想对分组聚合进行分区并分别处理这两个部分。
Map<String, String> makePhoneBook(Collection<Person> people) {
Map<Boolean, List<Person>> phoneBook = people.stream()
.collect(Collectors.groupingBy(Person::getName))
.values()
.stream()
.collect(Collectors.partitioningBy(list -> list.size() > 1,
Collectors.mapping(r -> r.get(0),
Collectors.toList())));
// handle duplicates
phoneBook.get(true)
.forEach(x -> System.out.println("duplicate found " + x));
return phoneBook.get(false).stream()
.collect(Collectors.toMap(
Person::getName,
Person::getAddress));
}
I would like to remove any values from that stream beforehand.
正如@JimGarrison 指出的那样,预处理数据没有意义。
在处理所有数据集之前,您无法提前知道名称是否唯一。
您必须考虑的另一件事是,在流管道内部(在收集器之前)您了解之前遇到的数据。因为中间操作的结果不应该依赖于任何状态。
如果您认为流的行为就像一系列循环,因此假设可以在收集流元素之前对其进行预处理,那是不正确的。流管道的元素一次一个地被延迟处理。 IE。 管道中的所有操作将应用于单个元素并且每个操作仅在需要时应用(这就是懒惰的意思)。
有关更多信息,请查看 this tutorial and API documentation
实施
您可以通过利用 Collectors.teeing()
和 自定义对象 在 单流语句 中分离唯一值和重复项包含 duplicated 和 unique 条目的单独集合 phone book.
由于这个对象的主要功能只是携带数据,所以我将其实现为 Java 16 记录。
public record FilteredPhoneBook(Map<String, String> uniquePersonsAddressByName,
List<String> duplicatedNames) {}
Collector teeing()
需要三个参数:两个 collectors 和一个 function 合并两个收集器产生的结果。
groupingBy()
结合 counting()
生成的 map 用于确定重复的名称。
由于没有必要处理数据,用作第二个收集器的toMap()
将创建一个包含所有名称.
当两个收集器将结果交给 merger
函数时,它会负责删除重复项。
public static FilteredPhoneBook getFilteredPhoneBook(Collection<Person> people) {
return people.stream()
.collect(Collectors.teeing(
Collectors.groupingBy(Person::getName, Collectors.counting()), // intermediate Map<String, Long>
Collectors.toMap( // intermediate Map<String, String>
Person::getName,
Person::getAddress,
(left, right) -> left),
(Map<String, Long> countByName, Map<String, String> addressByName) -> {
countByName.values().removeIf(count -> count == 1); // removing unique names
addressByName.keySet().removeAll(countByName.keySet()); // removing all duplicates
return new FilteredPhoneBook(addressByName, new ArrayList<>(countByName.keySet()));
}
));
}
另一种解决此问题的方法是利用 Map<String,Boolean>
作为发现重复项的方法,正如@Holger 所建议的那样。
第一个收集器将使用 toMap()
编写。它将 true
与只遇到过一次的键相关联,如果至少找到一个重复项,它的 mergeFunction
将分配 false
的值。
其余逻辑不变
public static FilteredPhoneBook getFilteredPhoneBook(Collection<Person> people) {
return people.stream()
.collect(Collectors.teeing(
Collectors.toMap( // intermediate Map<String, Boolean>
Person::getName,
person -> true, // not proved to be a duplicate and initially considered unique
(left, right) -> false), // is a duplicate
Collectors.toMap( // intermediate Map<String, String>
Person::getName,
Person::getAddress,
(left, right) -> left),
(Map<String, Boolean> isUniqueByName, Map<String, String> addressByName) -> {
isUniqueByName.values().removeIf(Boolean::booleanValue); // removing unique names
addressByName.keySet().removeAll(isUniqueByName.keySet()); // removing all duplicates
return new FilteredPhoneBook(addressByName, new ArrayList<>(isUniqueByName.keySet()));
}
));
}
main()
- 演示
public static void main(String[] args) {
List<Person> people = List.of(
new Person("Alise", "address1"),
new Person("Bob", "address2"),
new Person("Bob", "address3"),
new Person("Carol", "address4"),
new Person("Bob", "address5")
);
FilteredPhoneBook filteredPhoneBook = getFilteredPhoneBook(people);
System.out.println("Unique entries:");
filteredPhoneBook.uniquePersonsAddressByName.forEach((k, v) -> System.out.println(k + " : " + v));
System.out.println("\nDuplicates:");
filteredPhoneBook.duplicatedNames().forEach(System.out::println);
}
输出
Unique entries:
Alise : address1
Carol : address4
Duplicates:
Bob