Java 将整数范围分组为函数的答案

Java 将整数范围分组为函数的答案,java,functional-programming,java-8,java-stream,Java,Functional Programming,Java 8,Java Stream,对于一系列整数,我想应用一个(“昂贵的”)操作,只过滤出那些有有趣答案的整数,然后对答案进行分组 第一个代码段可以工作,但它在代码和计算中重复了操作(“模数2”): IntStream.range(1, 10).boxed() .filter(p -> (p % 2 != 0)) // Expensive computation .collect(Collectors.groupingBy(p -> (p %

对于一系列整数,我想应用一个(“昂贵的”)操作,只过滤出那些有有趣答案的整数,然后对答案进行分组

第一个代码段可以工作,但它在代码和计算中重复了操作(“模数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
的数字和答案,然后对条目流进行操作:

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

}
A1是问题中的第一个算法,A2是我的答案。因此,即使使用helper类,速度也更快

以下是性能测量,也包括您答案中的算法(如A3所示):


我觉得奇怪的是,你的表现比我的好,虽然这或多或少是一样的。

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

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())));
IntStream.range(1,10).boxed()
.map(p->newAbstractMap.SimpleEntry(p%2,p))
.filter(entry->entry.getKey()!=0)
.collect(收集器).groupingBy(条目::getKey,
Collectors.mapping(Entry::getValue,Collectors.toList());
如果您不介意使用第三方代码,“我的库”特别为此类任务提供了语法糖:

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();
intstreamx.range(1,10).boxed()
//映射到映射。输入键是您昂贵的计算
//和值是输入元素。结果是EntryStream
//它实现了流并具有其他方法
.mapToEntry(p->p%2,函数.identity())
.filterKeys(k->k!=0)
.分组();

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

好吧,因为我描述了我将要做的事情,并且对此进行了一些讨论,我想我应该写下我所描述的内容:

    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())));
类IntPair{
最终整数输入,结果;
IntPair(inti,intr){input=i;result=r;}
}
地图输出=
IntStream.range(1,10)
.mapToObj(i->新的IntPair(i,i%2))
.filter(对->对.result!=0)
.collect(分组依据(配对->配对结果),
映射(pair->pair.input,toList());
请注意,helper类可以(也可能应该)是某种嵌套类,甚至是本地类


为字段命名有一个好处,那就是它确实使理解发生了什么变得更容易。当我最初写这篇文章时,我无意中颠倒了分组操作中
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())));
那个手术太便宜了,我不在乎它的重复。当然,如果你这样做了

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

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

这将解决问题。当然,这不是一个可重用的解决方案,但lambda表达式是一个可以使用的代码片段。

您能解释一下您想做什么吗?我们唯一的解释是,你正在尝试做一个“昂贵”的操作(模对我来说并不昂贵,但好吧,我会顺其自然)。但是什么是输入,什么是预期的输出呢?可能最好的方法是创建一个小值类(helper类)来包含原始int和昂贵操作的结果。看,为什么?如果你不想做两次同样昂贵的计算,你需要把它存储在某个地方,这是通过让一个自定义类处理这些结果来实现的。您可以使用
int[2]
数组,但会失去一些语义。@Markus您可以这样做:
IntStream.range(1,10).mapToObj(p->new int[]{p,p%2})。filter(arr->arr[1]!=0)。collect(groupingBy(arr->arr[1],mapping(arr->arr[0],toList())@Juru好吧,我的回答表明了我的偏好,但我完全愿意接受其他人有不同的偏好。像
Pair
这样的典型元组需要装箱,像
int[2]
技巧一样,不能将名称与不同的元素相关联。这些都是选择helper类的原因。但我同意它们冗长而笨重。不过,我不太清楚过去的情况。:-)为什么不干脆
range(…).mapToObj(p->newsimpleentry(p%2,p)).filter(e->e.getKey()!=0).collect(groupingBy(SimpleEntry::getKey,mapping(SimpleEntry::getValue,toList())然后?做一个小测试,在这个测试中,你执行这个实现100万次,然后在你的问题中执行另一个正确的实现,看看这两个都花了多长时间,这会让你对性能的影响有一个了解。@AlexisC。SimpleEntry在AbstractMap中定义。意味着包括AbstractMap static或新AbstractMap.SimpleEntry(…)
@Markus这只是一个导入,就像任何其他
import java.util.AbstractMap.SimpleEntry@Markus我做过
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())));
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()));