英文:
java 11 lambda, aggregate by same property and the filter with a certain condition
问题
以下是翻译好的部分:
我有以下实体:
	ID    | PROPERTY
	FOO   | A
	FOO   | Z
	JOHN  | A
	DOE	  | Z
现在我想按ID对这些实体进行分组,并且如果一个或两个值都存在,则取具有属性'A'的值,否则取'Z'。
因此输出应为:
    FOO   | A
    JOHN  | A
    DOE   | Z
我考虑使用groupingBy流方法,但我不知道要使用哪些收集器,也不知道这是否是正确的解决方案:
    list.stream()
                .collect(Collectors.groupingBy(o -> o.getId(), ? 另一个收集器函数 ?)
                .collect(Collectors.toList()));
英文:
I have the following entities:
ID    | PROPERTY
FOO   | A
FOO   | Z
JOHN  | A
DOE	  | Z
Now I want to group these entities by ID and the take the one with property 'A' if one or both valuesare present , otherwise Z.
Thus the output should be:
FOO   | A
JOHN  | A
DOE   | Z
I was thinking about the groupingBy stream method, but I don't know how what collectors to use neither if is the correct solution:
list.stream()
            .collect(Collectors.groupingBy(o -> o.getId(), ? another collector function ?)
            .collect(Collectors.toList()));
答案1
得分: 1
试一试:
entities.stream()
        .collect(toMap(YourEntityType::getId, 
                       YourEntityType::getProperty,
                       (x, y) -> "A".equals(x) ? x : y));
更新:
我刚注意到在我编辑答案的时候,@Holger 在他的评论中发表了完全相同的内容。不过有两个小区别:
- 我更喜欢使用方法引用而不是 lambda 表达式(这是一种品味问题,但我觉得它们比 lambda 表达式更不容易出错)
 "A".equals(x)比x.equals("A")更安全。后者在x为null的情况下会引发 NPE(空指针异常)。
更新 2:
既然你想要以列表的形式获得结果,那么这个选项应该适合你:
entities.stream()
        .collect(collectingAndThen(
                    toMap(YourEntityType::getId, 
                          identity(),
                          (x, y) -> "A".equals(x.getProperty()) ? x : y,
                    m -> new ArrayList<YourEntityType>(m.values()));
英文:
Try this:
entities.stream()
        .collect(toMap(YourEntityType::getId, 
                       YourEntityType::getProperty,
                       (x, y) -> "A".equals(x) ? x : y));
Update:
I've just noticed that @Holger posted exactly the same thing in his comment, while I was editing my answer. There are two minor differences though:
- I prefer using method references instead of lambdas (It's a matter of taste, but I find them less error prone than lambdas)
 "A".equals(x)is safer thanx.equals("A"). The latter will fail with NPE in casexisnull.
Update 2:
Since you want the result in form of a list, then this option should work for you:
entities.stream()
        .collect(collectingAndThen(
                    toMap(YourEntityType::getId, 
                          identity(),
                          (x, y) -> "A".equals(x.getProperty()) ? x : y,
                    m -> new ArrayList<YourEntityType>(m.values()));
答案2
得分: 1
如果您坚持使用分组收集器,`? 另一个收集器函数 ?` 的代码如下所示:
```java
	public static void main(String[] args) {
		record C(String id, String property) {}
		List<C> list = List.of(new C("FOO","A"),new C("FOO","Z"),new C("JOHN","A"),new C("DOE","Z"),new C("XXX","X"));
		System.out.println(list);
		Object result = list.stream()
				.collect(Collectors.groupingBy(o -> o.id, Collectors.collectingAndThen(Collectors.reducing((l,r) -> "A".equals(l.property) ? l : r), Optional::get)));
		System.out.println(result);		
	}
这是IDEA建议的几乎与 @ETO 的解决方案相近的修正方式。
(抱歉,由于有记录,我懒得使用 getters。)
<details>
<summary>英文:</summary>
If you insisted on using grouping collector, the `? another collector function ?` would look like this:
```java
	public static void main(String[] args) {
		record C(String id, String property) {}
		List<C> list = List.of(new C("FOO","A"),new C("FOO","Z"),new C("JOHN","A"),new C("DOE","Z"),new C("XXX","X"));
		System.out.println(list);
		Object result = list.stream()
				.collect(Collectors.groupingBy(o -> o.id, Collectors.collectingAndThen(Collectors.reducing((l,r) -> "A".equals(l.property) ? l : r), Optional::get)));
		System.out.println(result);		
	}
, which Idea suggest to correct to almost @ETO's solution.
(Apologize, having records I am lazy to use getters.)
答案3
得分: 1
// 具有可重用方法的实用程序类,您也可以将其用于其他目的
public final class StreamUtil {
	private StreamUtil() {}
	public static <T, U> Predicate<T> distinctBy(Function<T, U> keyFunction) {
		final Set<U> uniqueKeys = synchronizedSet(new HashSet<>());
		return item -> uniqueKeys.add(keyFunction.apply(item));
	}
}
和您问题的解决方案
entities.stream()
 .sorting(Comparator.comparing(Entity::getProperty)) // 根据优先级对实体进行排序
 .filter(StreamUtil.distinctBy(Entity::getId)) // 只获取每个键的第一个实体
 ... // 在这里,您可以根据您的要求继续使用流。您可以进行collect()操作或继续进行映射/过滤操作
这个解决方案比其他建议的解决方案慢,因为涉及排序。
然而,它也有它的优点——更容易阅读,慢速性能只会在大集合上表现出来。
英文:
// Utility class with methods you can reuse for other purposes too
public final class StreamUtil {
	private StreamUtil() {}
	public static <T, U> Predicate<T> distinctBy(Function<T, U> keyFunction) {
		final Set<U> uniqueKeys = synchronizedSet(new HashSet<>());
		return item -> uniqueKeys.add(keyFunction.apply(item));
	}
}
and solution for your question
entities.stream()
 .sorting(Comparator.comparing(Entity::getProperty)) // order entities according to priorities
 .filter(StreamUtil.distinctBy(Entity::getId)) // take first entity only per key
 ... // here you have stream as per you requirements. You can collect() it or continue mapping/filtering
This solution is slower than other suggested here because of sorting.
However, it has its advantage - easier to read, while slowness will kick in on large collections only.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。


评论