对于一个整数范围,我想应用一个("昂贵的")操作,只过滤掉那些有有趣答案的整数,然后对答案进行分组。
第一个代码片段可以工作,但是它在代码和计算中都重复了操作("取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类可以(也应该)是某种嵌套类,甚至是一个局部类。
为字段命名的一个好处是,它确实使它更容易理解正在发生的事情。当我最初写这篇文章时,我无意中颠倒了input
和result
在分组操作中的作用,因此我得到了错误的结果。重读代码后,我很容易看到我是由input
值分组,而不是result
值,这也很容易修复。如果我不得不使用arr[0]
和arr[1]
或tuple.t1
和tuple.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
我觉得很奇怪,你的表现比我的好,我还以为差不多呢