将一组整数分组成一个函数的答案



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

第一个代码片段可以工作,但是它在代码和计算中都重复了操作("取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)

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

既然我描述了我要做的事情,并且对此有一些讨论,我想我应该把我所描述的写下来:

    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())));

注意,helper类可以(也应该)是某种嵌套类,甚至是一个局部类。

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

为什么不对昂贵的计算结果进行分组,然后过滤得到的映射呢?

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();

内部与第一个解决方案基本相同

一般的解决方案是记住计算结果。例如,像Stuart Marks建议的那样,但是如果你不想引入一个新的类型,你可以使用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表达式无论如何都是一次性使用的代码片段。

一种方法是先收集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是我在这个答案中的算法。因此,即使使用helper类,它也会更快。

这是性能测量也包括算法在你的答案(作为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 

我觉得很奇怪,你的表现比我的好,我还以为差不多呢

最新更新