我尝试合并两个集合,但是有条件地,我只想添加唯一值。什么构成唯一性应由谓词(或类似)决定,而不是等于函数。
例如,假设我们有以下两个Person对象集合:
List<Employee> list1 = Arrays.asList(new Employee(1, "Adam", "Smith", Type.CEO), new Employee(2, "Bob", "Jones", Type.OfficeManager), new Employee(3, "Carl", "Lewis", Type.SalesPerson));
List<Employee> list2 = Arrays.asList(new Employee(4, "Xerxes", "Brown", Type.OfficeManager), new Employee(5, "Yuri", "Gagarin", Type.Janitor), new Employee(6, "Zain", "Wilson", Type.SalesPerson));
...并假设我想通过添加list1和list2中的元素将这些列表合并到一个新列表中,但排除那些具有相应&#34;相同&#34;的元素。 person对象已添加到新列表中,其唯一性由Type枚举(Type.CEO,Type.OfficeManager等)确定。
然后,合并后的预期结果是包含以下人员的新列表:
Employee(1, "Adam", "Smith", Type.CEO)
Employee(2, "Bob", "Jones", Type.OfficeManager)
Employee(3, "Carl", "Lewis", Type.SalesPerson)
Employee(5, "Yuri", "Gagarin", Type.Janitor)
什么是最好的&#34;以一般的 Java 8/9 方式实现这一目标的方法?即我不想写一些特定于Person对象或Type枚举的东西,我不想写一些使用对象的equals方法的东西。相反,我想使用 BiPredicate 或类似的东西。
但是,我也不想自己执行任何循环。 Streams 似乎是一个不错的选择,但我无法弄清楚如何实现这一目标。如何编写BiPredicate,其中一个值来自一个流而另一个值来自另一个流,而不是自己执行循环?
我想使用BiPredicate(或类似的)的原因是我希望能够将此函数与高级逻辑一起使用,其中不可能简单地提取所有元素的某些属性,然后对值进行分组基于这个属性的独特性。
有什么建议吗?
/吉米亨德里
更新: 为了澄清我讨论谓词的原因,这里有一个更复杂的例子:
让我们假设我们像以前一样拥有两个Employee对象集合。但是这次,唯一性逻辑不能使用映射函数表示给Employee对象的特定属性。相反,它使用EmployeeRegistry中的一些数据,如下所示:如果两个员工属于同一个税级或,如果他们属于同一个&#34;类型&#34;然后他们被认为是平等的。由于这种OR逻辑,不可能将其减少为用于对数据进行分组的唯一密钥。
Update2 :为简单起见,下面是一个不太复杂的示例,但仍然很复杂,不能简单地映射到字段。这有点人为,但这是为了简单起见。
让我们假设我们有两个字符串集合。并且唯一性计算如下:
按照 Federico Peralta Schaffner 的建议,使用方法 Collectors.toMap 似乎有效,但我不确定如何编写{{ 1}}遵循标准并同时有效的实现。我能想到的唯一功能实现是返回一个常量值(即不管字符串都是相同的值)。
更新3 :考虑到我的&#34;平等的 OR-logic &#34;算法打破等于合同,并且难以(不可能?)编写有效的hashCode实现,我现在回到我开始的地方。即需要某种类似谓词的东西。这是一个更新的现实世界&#34;例如:
让我们假设我们像以前一样拥有两个Employee对象集合,并且我们希望将这些集合合并为一个集合。但这一次我们想避免包括那些不相处的人。为了确定两个人是否相处,我们有一个HumanRelationsDepartment对象,方法isOkToWorkWithEachother(Person,Person)。当检测到两个不相处的人时,只有其中一个人被添加到新的集合中。哪一个可以由映射函数确定,默认逻辑可以是第一个人被选中。
编写解决此问题的旧学校代码是相当简单的。我正在寻找的是一种无环路流的解决方案。这样的解决方案是否存在?表现不是问题。
答案 0 :(得分:2)
您可以通过Collectors.toMap
:
Collection<Employee> merged = Stream.of(list1, list2)
.flatMap(Collection::stream)
.collect(Collectors.toMap(e -> calculateGroup(e), e -> e, (e1, e2) -> e1)))
.values();
因此,根据某个Map<SomeGroupType, Employee>
方法接收calculateGroup
实例并返回表示Employee
所属的组的内容,这会创建Employee
。这可能是Employee
的一些属性,即type
,或者更复杂的东西,可以从其他地方获取数据,以确定该组,即税率,根据员工的年收入。这是地图的键,它将根据您的特定需求确定唯一性。这种方法的唯一要求是,无论您使用什么类的密钥,它都必须始终如一地实现equals
和hashCode
。
映射的值将只是连接流的Employee
个实例。对于合并函数(Collectors.toMap
第三个参数),我使用了(e1, e2) -> e1
,这意味着我们将保留已经存在于地图中的值是同等的钥匙。如果您想改写值,请将其更改为(e1, e2) -> e2
。
答案 1 :(得分:1)
<input type="checkbox" [checked]="roleObj.checked" id ={{roleObj.roleID}} />
答案 2 :(得分:1)
对于两个流的简单合并,您可以使用concat(只需更新reducer&#39; s逻辑):
Collection<Employee> merged = Stream.concat(list1.stream(), list2.stream())
.collect(Collectors.groupingBy(emp -> emp.getType(),
Collectors.reducing(null, (e1, e2) -> e1 ) ))
.values();
对于2个集合的元素合并(假设长度相同),您可以使用基于索引的整数流来模拟两个列表中的zipping
,然后使用将两者合并的缩减器之一。
1 - 确保列表按类型排序,因为它决定了唯一性:
List<Employee> list1Sorted = list1.stream()
.sorted(Comparator.comparing(Employee::getType))
.collect(Collectors.toList());
List<Employee> list2Sorted = list2.stream()
.sorted(Comparator.comparing(Employee::getType))
.collect(Collectors.toList());
2 - 声明&#34; reducer&#34;这将合并同一索引处的两个对象:
//This is returning an arbitrary value. You may want to add your own logic:
BiFunction<Employee, Employee, Employee> reducer = (e1, e2) -> e1;
3 - 现在假设列表具有相同的长度并模拟zip
操作:
List<Employee> mergedList = IntStream.range(0, list1.size())
.mapToObj(i -> new Employee[] {list1Sorted.get(i), list2Sorted.get(i)})
.map(e -> reducer.apply(e[0], e[1]))
.collect(Collectors.toList());
简化它:制作通用的zip
方法:
public static <T> List<T> zipStreams(List<T> list1, List<T> list2, BiFunction<T, T, T> employeeMerger, Comparator<T> sortComparator) {
if(list1.size() != list2.size()) {
throw new IllegalArgumentException("Lists must be of the same length");
}
List<T> list1Sorted = sortComparator == null ? list1: list1.stream()
.sorted(sortComparator)
.collect(Collectors.toList()),
list2Sorted = sortComparator == null ? list2: list2.stream()
.sorted(sortComparator)
.collect(Collectors.toList());
return IntStream.range(0, list1Sorted.size())
.mapToObj(i -> Arrays.<T>asList(list1Sorted.get(i), list2Sorted.get(i)))
.map(list -> employeeMerger.apply(list.get(0), list.get(1)))
.collect(Collectors.toList());
}
显然,这非常特定于合并员工列表元素。
现在我们可以用:
来调用它zipStreams(list1, list2, (e1, e2) -> e1, Comparator.comparing(Employee::getType));
答案 3 :(得分:0)
将它们映射到具有唯一值作为键的映射,然后将条目映射到列表。