【问题标题】:How to filter and map on a value in a Java 8 stream without redundant calculation?如何在没有冗余计算的情况下过滤和映射 Java 8 流中的值?
【发布时间】:2017-09-04 15:31:44
【问题描述】:

我对 Java 8 集合流功能有肤浅的了解,所以我不确定以下是否可能:我想 filter 一个基于整数比较的集合,并将该值重用于映射。

具体来说,我有一个Collection<String> strings,如果 Levenshtein 距离小于值 levenshteinLimit,我想将其每个值映射到 Levenshtein 距离到固定的 String x

String x = "some string";
Collection<String> strings = new LinkedList<>(Arrays.asList("not some string",
        "some other string"));
int levenshteinLimit = 10;
Map<Integer, String> stringsLevenshteinMap = strings.stream()
        .filter(string -> LevenshteinDistance.getDefaultInstance().apply(x, string) < levenshteinLimit)
        .collect(Collectors.toMap(string -> LevenshteinDistance.getDefaultInstance().apply(x, string), Function.identity()));
System.out.println(stringsLevenshteinMap);

效果很好并表达了我正在寻找的结果,但需要对距离进行冗余计算。到目前为止,这还不是问题。没有流的解决方案也是可能的。我正在尝试学习一些新东西。

我认为先过滤然后映射更有效,因为对象的数量可能更小,这意味着工作量更少。

以上代码使用 Apache commons-text 1.1。

【问题讨论】:

    标签: java collections java-stream


    【解决方案1】:

    带有元组中间对象的东西应该可以工作:

    Map<Integer, String> stringsLevenshteinMap = strings.stream()
        .map(s -> new Tuple<>(LD.getInstance().apply(x, s), s)
        .filter(t -> t.getFirst() < maxDistance)
        .collect(Collectors.toMap(Tuple::getFirst, Tuple::getSecond));
    

    【讨论】:

    • 那么,没有办法使用昂贵的new?使用Map.Entry 是否有帮助,因为将它们添加到Map 会更便宜?
    • 为什么将它们添加到地图会更便宜?是的,有一种方法:使用 for 循环而不是使用流。但是您正在预优化,这是万恶之源。在 Java 中创建短生命周期的对象很便宜,并且几乎 0 可能导致性能问题。
    【解决方案2】:

    如果你想避免一个临时对象持有键和值,你需要一个自定义收集器,追溯内置收集器所做的事情,但直接合并过滤。顺便说一句,我认为Collectors.toMap 在这里不合适,因为不能保证每个距离只有一个字符串。因此,我改用Collectors.groupingBy 作为模板:

    public static <T> Collector<T,?,Map<Integer,List<T>>>
                      grouping(ToIntFunction<T> f, int limit) {
        return Collector.of(HashMap::new,
            (m,t) -> {
                int v = f.applyAsInt(t);
                if(v < limit) m.computeIfAbsent(v, x -> new ArrayList<>()).add(t);
            },
            (m1,m2) -> {
                m2.forEach((k,v) -> m1.merge(k, v, (l1,l2)->{ l1.addAll(l2); return l1; }));
                return m1;
            });
    }
    

    这基本上做了Collectors.groupingBy 所做的事情,但将其使用限制为评估为int 数字的关键函数,并且仅处理映射到低于指定限制的数字的元素。也可以将其概括为使用FunctionPredicate

    这可以像这样使用

    Map<Integer, List<String>> stringsLevenshteinMap
        = Stream.of("not some string", "some other string")
                .collect(grouping(
                    string -> LevenshteinDistance.getDefaultInstance().apply(x, string),
                    levenshteinLimit));
    

    但必须强调的是,不能保证这比创建一个保存两个值的临时对象更好;这取决于很多环境因素。通常,创建临时对象并不昂贵。使用 holder 对象的方法更加灵活,尤其是在您想稍后更改流操作时。

    【讨论】:

      【解决方案3】:

      首先,您可以通过避免创建无用的 LinkedList 来加快代码速度。

      现在,关于您的问题,如果您想继续使用流来执行此操作,那么解决方案是将每个字符串映射到包含字符串及其距离的对象,然后过滤这些对象,然后收集到地图:

      String x = "some string";
      int levenshteinLimit = 10;
      
      List<String> strings = Arrays.asList("not some string", "some other string"));
      Map<Integer, String> stringsLevenshteinMap = 
          strings.stream()
                 .map(string -> new StringWithDistance(string, LevenshteinDistance.getDefaultInstance().apply(x, string))
                 .filter(o -> o.getDistance() < levenshteinLimit)
                 .collect(Collectors.toMap(StringWithDistance::getDistance, StringWithDistance.getString));
      
      System.out.println(stringsLevenshteinMap);
      

      【讨论】:

        【解决方案4】:

        我想这应该可行:

        Map<Integer, String> stringsLevenshteinMap = strings.stream()
            .map(string -> LevenshteinDistance.getDefaultInstance().apply(x, string))
            .filter(val -> val < levenshteinLimit)
            .collect(Collectors.toMap(val -> val, Function.identity()));
        

        但正如我所说:这不是我的想法,所以我不能保证功能或可编译性。但这个想法应该是显而易见的。我们不是计算两次,而是映射到计算值并从那里继续进行过滤和收集。

        【讨论】:

        • val -&gt; val = Function.identity() 以及
        • 我正要回答这样的问题,但您在第一个映射中丢失了字符串。
        • @daniu 据我理解的代码,该字符串除了apply的重复调用之外没有其他任何用途,所以这里没有真正的损失。
        • 您正在创建一个地图,其中包含作为键的距离和作为值的距离,而不是字符串。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2017-07-14
        • 2019-11-10
        • 1970-01-01
        • 2017-01-20
        • 2014-06-06
        • 1970-01-01
        • 2021-09-13
        相关资源
        最近更新 更多