【问题标题】:Grouping a range of integers to the answer of a function将一系列整数分组到函数的答案
【发布时间】:2015-07-31 21:44:18
【问题描述】:

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

第一个 sn-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)

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

【问题讨论】:

  • 您能解释一下您要做什么吗?我们唯一的解释是您正在尝试进行“昂贵”的操作(模数对我来说并不是很昂贵,但好的,我会顺其自然)。但是输入是什么,预期输出是什么?
  • 可能最好的方法是创建一个小值类(帮助类)来包含原始 int 和昂贵操作的结果。见stackoverflow.com/a/29340148/1441122
  • 为什么?如果您不想两次执行相同的昂贵计算,则需要将其存储在某处,这是通过让自定义类处理这些结果来实现的。您可以使用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<A,B> 这样的典型元组需要装箱,并且像int[2] 技巧一样,您不能将名称与不同的元素相关联。这些是更喜欢辅助类的原因。但我同意它们冗长而笨重。虽然不确定旧时。 :-)

标签: java functional-programming java-8 java-stream


【解决方案1】:

好吧,既然我描述了我要做什么,并且对此进行了一些讨论,我想我应该写下我所描述的:

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

请注意,帮助类可以(并且可能应该)是某种嵌套类,甚至是本地类。

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

【讨论】:

    【解决方案2】:

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

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

    【讨论】:

    • 不是单行,但首先使用groupingBy 构建地图可能更有效。然后拨打map.remove(0);
    • 它必须是map.keySet().removeIf(x-&gt;x==0),因为!=0 只是“有趣”测试的占位符。此外,这种方法需要 3 个参数 groupingByHashMap::new -- groupingBy 不能保证可变映射。是的,这样会稍微高效一些,但问题是关于昂贵计算的结果进行分组,所以没关系。
    【解决方案3】:

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

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

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

    【讨论】:

      【解决方案4】:

      一般的解决方案是记住计算的结果。例如。像 suggested by 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%2替换为简单的操作i&amp;1

      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 表达式无论如何都是一次性的代码片段。

      【讨论】:

        【解决方案5】:

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

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

        不确定是否会影响性能。

        【讨论】:

        • 那为什么不直接range(...).mapToObj(p -&gt; new SimpleEntry&lt;&gt;(p % 2, p)).filter(e -&gt; e.getKey() != 0).collect(groupingBy(SimpleEntry::getKey, mapping(SimpleEntry::getValue, toList())));呢?
        • 做一个小测试,你执行这个实现 100 万次,另一个在你的问题中正确执行一次,看看两者都花了多长时间,这应该让你对性能影响有所了解。
        • @AlexisC。 SimpleEntry 在 AbstractMap 中定义。方法包括 AbstractMap static 或 new AbstractMap.SimpleEntry&lt;&gt;(...).
        • @Markus 好吧,这只是一个导入,就像任何其他 import java.util.AbstractMap.SimpleEntry;
        • @Markus 我在回答中进行了性能测试,它比原始实现要快,但 StuartMarks 建议的实现仍然优于它。
        【解决方案6】:

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

        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 在此答案中是我的。因此,即使使用辅助类也更快。

        这是性能测量,还包括您答案中的算法(如 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 
        

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-07-24
          • 1970-01-01
          • 2021-12-25
          • 2019-05-17
          • 2013-10-13
          • 1970-01-01
          相关资源
          最近更新 更多