【问题标题】:Use of stream, filter and average on list and jdk8在列表和 jdk8 上使用流、过滤器和平均
【发布时间】:2015-05-25 05:41:50
【问题描述】:

我有这个看起来像这样的数据列表;

{id, datastring}

{1,"a:1|b:2|d:3"}
{2,"a:2|c:2|c:4"}
{3,"a:2|bb:2|a:3"}
{4,"a:3|e:2|ff:3"}

这里我需要做的是进行平均之类的操作,或者查找字符串中某个元素小于某个值的所有id。

这里有一些例子;

平均值

{a,2}{b,2}{bb,2}{c,3}{d,3}{e,2}{ff,3}

找到所有id的c

{2}

找到所有id的位置

{1,2,3}

这样可以很好地使用 stream() 和 filter() 吗??

【问题讨论】:

  • 是的。这将是。 (如果我猜对了)

标签: java list filter java-8 java-stream


【解决方案1】:

是的,您可以使用流操作来实现这一点,但我建议为这些数据创建一个类,以便每一行对应一个特定实例。这将使您的生活更轻松 IMO。

class Data {
    private int id;
    private Map<String, List<Integer>> map;
    ....
}

也就是说,让我们来看看如何实现这一点。一、find all的实现:

public static Set<Integer> ids(List<Data> list, String value, Predicate<Integer> boundPredicate) {
    return list.stream()
               .filter(d -> d.getMap().containsKey(value))
               .filter(d -> d.getMap().get(value).stream().anyMatch(boundPredicate))
               .map(d -> d.getId())
               .collect(toSet());
}

这本书读起来很简单。您会从列表中获得Stream&lt;Data&gt;。然后你应用一个过滤器,这样你只得到具有映射中给定值的实例,并且有一个值满足你给定的谓词。然后将每个实例映射到其对应的 id,并在 Set 中收集结果流。

调用示例:

Set<Integer> set = ids(list, "a", value -> value < 3);

哪个输出:

[1, 2, 3]

平均请求有点棘手。我最终得到了另一个实现,最后你最终得到了一个Map&lt;String, IntSummaryStatistics&gt;(其中确实包含平均值)以及其他信息。

Map<String, IntSummaryStatistics> stats = list.stream()
                .flatMap(d -> d.getMap().entrySet().stream())
                .collect(toMap(Map.Entry::getKey,
                               e -> e.getValue().stream().mapToInt(i -> i).summaryStatistics(),
                               (i1, i2) -> {i1.combine(i2); return i1;}));

你首先得到一个Stream&lt;Data&gt;,然后你flatMap每个映射的每个条目集都有Stream&lt;Entry&lt;String, List&lt;Integer&gt;&gt;。现在您将此流收集到一个映射中,其中每个键都由条目的键映射,每个List&lt;Integer&gt; 都由其对应的IntSummaryStatistics 值映射。如果您有两个相同的键,则组合它们各自的 IntSummaryStatistics 值。

给定你的数据集,你会得到一个Map&lt;String, IntSummaryStatistics&gt;

ff => IntSummaryStatistics{count=1, sum=3, min=3, average=3.000000, max=3}
bb => IntSummaryStatistics{count=1, sum=2, min=2, average=2.000000, max=2}
a => IntSummaryStatistics{count=5, sum=11, min=1, average=2.200000, max=3}
b => IntSummaryStatistics{count=1, sum=2, min=2, average=2.000000, max=2}
c => IntSummaryStatistics{count=2, sum=6, min=2, average=3.000000, max=4}
d => IntSummaryStatistics{count=1, sum=3, min=3, average=3.000000, max=3}
e => IntSummaryStatistics{count=1, sum=2, min=2, average=2.000000, max=2}

您可以轻松地从中获取平均值。


这里有一个完整的working example,不过实现当然可以改进。

【讨论】:

  • 谢谢亚历克西斯,我刚刚试了一下,它似乎真的达到了我的预期。明天我会更深入地研究它,因为它有很多我还不熟悉的新东西..
  • 请注意,您可以通过使用getOrDefault 代替两个filter 来避免一次查找:stream().filter(d -&gt; d.getMap().getOrDefault(id, emptyList()).stream().anyMatch(predicate)).map(...)
  • 嗨,Alexis,我如何用 sqlResults 的输出替换 Files.lines() 负载? java.sql.ResultSet 类型的 sqlResult 我不熟悉 .map 和 .collect(toList()) 谢谢
  • 你在哪里/如何定义“emptyList()”?
  • @AndreCouture 您好,对于 ResultSet 部分,您可以查看:docs.oracle.com/javase/tutorial/jdbc/basics/retrieving.html。基本上是一个 while 循环,然后在列表中添加一个新的 Data 对象。 emptyList() 只是 Collections 类中的一个静态方法,它返回一个空列表。另外,由于您从数据库中获取这些数据,因此直接使用 SQL 查询执行您想要的操作可能更简单?它也将更加高效。还是只是为了实践 Java 8 的新特性?
【解决方案2】:

我知道你有你的答案,但这里也是我的版本:

 Map<String, Double> result = list.stream()
            .map(Data::getElements)
            .flatMap((Multimap<String, Integer> map) -> {
                return map.entries().stream();
            })
            .collect(Collectors.groupingBy(Map.Entry::getKey,
                    Collectors.averagingInt((Entry<String, Integer> token) -> {
                        return token.getValue();
                    })));
    System.out.println(result);

    List<Integer> result2 = list.stream()
            .filter((Data data) -> {
                return data.getElements().get("c").stream().anyMatch(i -> i < 4);
            })
            .map(Data::getId)
            .collect(Collectors.toList());
    System.out.println(result2);

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-10-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-07-12
    相关资源
    最近更新 更多