将一系列整数分组为函数的答案

Grouping a range of integers to the answer of a function

对于一个整数范围,我想应用一个 ("expensive") 操作,只过滤掉那些有有趣答案的整数,然后对答案进行分组。

第一个代码片段有效,但它在代码和计算中重复了操作 ("modulus 2"):

IntStream.range(1, 10).boxed()
         .filter(p -> (p % 2 != 0))                     // Expensive computation
         .collect(Collectors.groupingBy(p -> (p % 2))); // Same expensive
                                                        // computation!

// {1=[1, 3, 5, 7, 9]} (Correct answer)

我尝试先映射到答案,然后过滤,然后分组——但初始整数当然会在途中丢失:

IntStream.range(1, 10).boxed()
         .map(p -> p % 2)                        // Expensive computation
         .filter(p -> p != 0)
         .collect(Collectors.groupingBy(p -> p));

// {1=[1, 1, 1, 1, 1]} (Of course, wrong answer)

我想映射到一个元组或类似的东西,但还没有想出一个干净的方法来做到这一点。

一种方法是先收集Map<Integer, Integer>的数字和答案,然后对条目流进行操作:

IntStream.range(1, 10).boxed()
         .collect(toMap(p -> p, p -> p % 2)) 
         .entrySet().stream()
         .filter(p -> p.getValue() != 0)
         .collect(groupingBy(p -> p.getValue(),
                  mapping(p -> p.getKey(),
                  toList())));

不过不确定对性能的影响。

所以这是我的解决方案:)。也许不是最好的。

import java.util.stream.IntStream;
import java.util.stream.Collectors;
import java.util.Map;
import java.util.List;

public class Test {

    private static final class Tuple {
        private final Integer t1;
        private final Integer t2;

        private Tuple(final Integer t1, final Integer t2) {
            this.t1 = t1;
            this.t2 = t2;
        }

        public Integer getT1() { return t1; }
        public Integer getT2() { return t2; }
    }

    private static String getValues(final List<Tuple> list) {
        final StringBuilder stringBuilder = new StringBuilder();
        for(Tuple t : list) {
            stringBuilder.append(t.getT2()).append(", ");
        }
        return stringBuilder.toString();
    }

     public static void main(String []args){

        Map<Integer, List<Tuple>> results =
        IntStream.range(1, 10).boxed()
         .map(p -> new Tuple(p % 2, p))                     // Expensive computation
         .filter(p -> p.getT1() != 0)
         .collect(Collectors.groupingBy(p -> p.getT1())); 

        results.forEach((k, v) -> System.out.println(k + "=" + getValues(v)));
     }

}

输出为 1=1, 3, 5, 7, 9;

关于性能:

sh-4.3# java -Xmx128M -Xms16M Test                                                                                                                                                                     
Time a1: 231                                                                                                                                                                                                  
Time a2: 125                                                                                                                                                                                                  
sh-4.3# java -Xmx128M -Xms16M Test
Time a1: 211                                                                                                                                                                                                  
Time a2: 127                                                                                                                                                                                                  
sh-4.3# java -Xmx128M -Xms16M Test
Time a1: 172                                                                                                                                                                                                  
Time a2: 100 

A1 是你在这个问题中的第一个算法,A2 在这个答案中是我的。所以即使有助手 class.

也更快

这是性能测量结果,其中还包括您答案中的算法(如 A3):

sh-4.3# java -Xmx128M -Xms16M HelloWorld                                                                                                                                                                      
Time a1: 202                                                                                                                                                                                                  
Time a2: 113                                                                                                                                                                                                  
Time a2: 170                                                                                                                                                                                                  
sh-4.3# java -Xmx128M -Xms16M HelloWorld                                                                                                                                                                      
Time a1: 195                                                                                                                                                                                                  
Time a2: 114                                                                                                                                                                                                  
Time a2: 169 

我觉得你的表现比我的好很奇怪,我认为它或多或少是一样的。

为什么不对昂贵的计算结果进行分组,然后过滤生成的地图?

IntStream.range(1, 10).boxed()
        .collect(groupingBy(x -> x % 2))
        .entrySet().stream()
        .filter(e -> e.getKey() != 0)
        .collect(toMap(Map.Entry::getKey, Map.Entry::getValue));

如果你想通过单流实现(不收集到中间地图),你可以这样做:

IntStream.range(1, 10).boxed()
    .map(p -> new AbstractMap.SimpleEntry<>(p % 2, p))
    .filter(entry -> entry.getKey() != 0)
    .collect(Collectors.groupingBy(Entry::getKey,
             Collectors.mapping(Entry::getValue, Collectors.toList())));

如果您不介意使用第三方代码,我的 StreamEx 库有专门用于此类任务的语法糖:

IntStreamEx.range(1, 10).boxed()
     // map to Map.Entry where keys are your expensive computation
     // and values are input elements. The result is EntryStream
     // which implements the Stream<Map.Entry> and has additional methods
    .mapToEntry(p -> p % 2, Function.identity())
    .filterKeys(k -> k != 0)
    .grouping();

在内部,它与第一个解决方案几乎相同。

好吧,既然我描述了我要做的事情,并且有一些关于它的讨论,我想我应该写下我正在描述的内容:

    class IntPair {
        final int input, result;
        IntPair(int i, int r) { input = i; result = r; }
    }

    Map<Integer, List<Integer>> output =
        IntStream.range(1, 10)
            .mapToObj(i -> new IntPair(i, i % 2))
            .filter(pair -> pair.result != 0)
            .collect(groupingBy(pair -> pair.result,
                mapping(pair -> pair.input, toList())));

请注意,助手 class 可以(而且可能应该)是某种嵌套 class,甚至是本地 class。

为字段命名的一个好处是它确实可以更容易地理解正在发生的事情。当我最初写这篇文章时,我无意中将 inputresult 在分组操作中的角色颠倒了,因此得到了错误的结果。重新阅读代码后,我很容易发现我是按 input 值而不是 result 值分组,而且它也很容易修复。如果我必须使用 arr[0]arr[1]tuple.t1tuple.t2.

,这将更难诊断和修复

一般的解决办法是记住计算的结果。例如。像 但如果你不想引入新的类型,你可以使用 int 数组代替:

Map<Integer, List<Integer>> map = IntStream.range(1, 10)
    .mapToObj(i -> new int[]{i, i % 2})
    .filter(pair -> pair[1] != 0)
    .collect(groupingBy(pair -> pair[1],
        mapping(pair -> pair[0], toList())));

这种特殊情况的具体解决方案是用简单的操作i&1代替昂贵的操作i%2:

Map<Integer, List<Integer>> map = IntStream.range(1, 10)
    .filter(i-> (i&1)!=0)
    .boxed().collect(groupingBy(i->i&1));

那个操作太便宜了,我不在乎它的重复。但如果你这样做,当然

Map<Integer, List<Integer>> map = IntStream.range(1, 10)
    .filter(i-> (i&1)!=0)
    .boxed().collect(groupingBy(i->1));

Map<Integer, List<Integer>> map = Collections.singletonMap(1, 
    IntStream.range(1, 10).filter(i-> (i&1)!=0)
             .boxed().collect(toList()));

将解决问题。当然,它不是可重用的解决方案,但 lambda 表达式无论如何都是一次性代码片段。