java 11中的lambda,按相同属性进行聚合,并使用特定条件进行筛选。

huangapple go评论77阅读模式
英文:

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") 更安全。后者在 xnull 的情况下会引发 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) -&gt; &quot;A&quot;.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)
  • &quot;A&quot;.equals(x) is safer than x.equals(&quot;A&quot;). The latter will fail with NPE in case x is null.

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) -&gt; &quot;A&quot;.equals(x.getProperty()) ? x : y,
                    m -&gt; new ArrayList&lt;YourEntityType&gt;(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&lt;C&gt; list = List.of(new C(&quot;FOO&quot;,&quot;A&quot;),new C(&quot;FOO&quot;,&quot;Z&quot;),new C(&quot;JOHN&quot;,&quot;A&quot;),new C(&quot;DOE&quot;,&quot;Z&quot;),new C(&quot;XXX&quot;,&quot;X&quot;));
		System.out.println(list);
		Object result = list.stream()
				.collect(Collectors.groupingBy(o -&gt; o.id, Collectors.collectingAndThen(Collectors.reducing((l,r) -&gt; &quot;A&quot;.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 &lt;T, U&gt; Predicate&lt;T&gt; distinctBy(Function&lt;T, U&gt; keyFunction) {
		final Set&lt;U&gt; uniqueKeys = synchronizedSet(new HashSet&lt;&gt;());
		return item -&gt; 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.

huangapple
  • 本文由 发表于 2020年10月9日 18:48:59
  • 转载请务必保留本文链接:https://go.coder-hub.com/64278525.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定