将一系列整数分组为函数的答案
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。
为字段命名的一个好处是它确实可以更容易地理解正在发生的事情。当我最初写这篇文章时,我无意中将 input
和 result
在分组操作中的角色颠倒了,因此得到了错误的结果。重新阅读代码后,我很容易发现我是按 input
值而不是 result
值分组,而且它也很容易修复。如果我必须使用 arr[0]
和 arr[1]
或 tuple.t1
和 tuple.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 表达式无论如何都是一次性代码片段。
对于一个整数范围,我想应用一个 ("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。
为字段命名的一个好处是它确实可以更容易地理解正在发生的事情。当我最初写这篇文章时,我无意中将 input
和 result
在分组操作中的角色颠倒了,因此得到了错误的结果。重新阅读代码后,我很容易发现我是按 input
值而不是 result
值分组,而且它也很容易修复。如果我必须使用 arr[0]
和 arr[1]
或 tuple.t1
和 tuple.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 表达式无论如何都是一次性代码片段。