【问题标题】:Group, Collectors, Map (Int to String), Map (Map to Object)组、收集器、映射(Int 到字符串)、映射(映射到对象)
【发布时间】:2018-03-28 17:48:51
【问题描述】:

这是我在Group, Sum byType then get diff using Java streams 上一个问题的延续。

按照建议,我应该作为单独的主题发布,而不是更新原始主题。

因此,通过我之前的一组问题,我已经实现了这一点,现在继续进行。

背景:

我有以下数据集

Sample(SampleId=1, SampleTypeId=1, SampleQuantity=5, SampleType=ADD), 
Sample(SampleId=2, SampleTypeId=1, SampleQuantity=15, SampleType=ADD), 
Sample(SampleId=3, SampleTypeId=1, SampleQuantity=25, SampleType=ADD), 
Sample(SampleId=4, SampleTypeId=1, SampleQuantity=5, SampleType=SUBTRACT), 
Sample(SampleId=5, SampleTypeId=1, SampleQuantity=25, SampleType=SUBTRACT) 
Sample(SampleId=6, SampleTypeId=2, SampleQuantity=10, SampleType=ADD), 
Sample(SampleId=7, SampleTypeId=2, SampleQuantity=20, SampleType=ADD), 
Sample(SampleId=8, SampleTypeId=2, SampleQuantity=30, SampleType=ADD), 
Sample(SampleId=9, SampleTypeId=2, SampleQuantity=15, SampleType=SUBTRACT), 
Sample(SampleId=10, SampleTypeId=2, SampleQuantity=35, SampleType=SUBTRACT)

我目前正在使用这个:

sampleList.stream()
    .collect(Collectors.groupingBy(Sample::getTypeId,
        Collectors.summingInt(
            sample -> SampleType.ADD.equalsIgnoreCase(sample.getSampleType())
                ? sample.getSampleQuantity() :
                -sample.getSampleQuantity()
            )));

还有这个

sampleList.stream()
        .collect(Collectors.groupingBy(Sample::getSampleTypeId,
                Collectors.collectingAndThen(
                    Collectors.groupingBy(Sample::getSampleType,
                        Collectors.summingInt(Sample::getSampleQuantity)),
                            map -> map.getOrDefault(SampleType.ADD, 0)
                                    - map.getOrDefault(SampleType.SUBTRACT, 0))));

作为获得所需输出以在Map<Long, Integer> 中分组的公认答案:

{1=15, 2=10}

有了这个,我想知道是否可以将其扩展为更多内容。

首先,我怎样才能让它返回为Map<String, Integer> 而不是原来的Map<Long, Integer>。基本上,对于 SampleTypeId; 1 表示 HELLO,2 表示 WORLD。

所以我需要一个.map(或者可能是其他函数)通过​​调用一个函数比如convertType(sampleTypeId)来将数据从1转换为HELLO,从2转换为WORLD。所以预期的输出将是{"HELLO"=15, "WORLD"=10}。是对的吗?我应该如何编辑当前建议的解决方案?

最后,我想知道是否也可以将其返回到 Object 而不是 Map。所以假设我有一个对象; SummaryResult with (String) name and (int) result。所以它返回一个List<SummaryResult> 而不是原来的Map<Long, Integer>。我如何使用.map(或其他)功能来做到这一点?还是有其他方法可以做到这一点?预期的输出应该是这样的。

SummaryResult(name="hello", result=15), 
SummaryResult(name="world", result=10), 

非常感谢@M 之前给出的步骤中的解释。普罗霍罗夫。

更新:

更新后

sampleList.stream()
        .collect(Collectors.groupingBy(sample -> convertType(sample.getSampleTypeId()),
                Collectors.collectingAndThen(
                    Collectors.groupingBy(Sample::getSampleType,
                        Collectors.summingInt(Sample::getSampleQuantity)),
                            map -> map.getOrDefault(SampleType.ADD, 0)
                                    - map.getOrDefault(SampleType.SUBTRACT, 0))));

private String convertType(int id) {
    return (id == 1) ? "HELLO" : "WORLD";
}

【问题讨论】:

  • 每个问题都应该是完整的。虽然链接可以作为附加资源很好,但它不能作为理解您的问题所需信息的来源。
  • @Pshemo 指出,我已经用一些背景信息更新了这个问题。谢谢。

标签: java java-8 java-stream collectors


【解决方案1】:

对于第一部分,考虑到你有方法

String convertType(int typeId)

您只需要从这里更改第一个分类器

groupingBy(SampleType::getTypeId)

到这里

groupingBy(sample -> convertType(sample.getTypeId()))

其他一切都保持不变。

后一种类型有点棘手,从技术上讲,它根本无法从与流相关的解决方案中受益。

你需要的是这个:

public List<SummaryResult> toSummaryResultList(Map<String, Integer> resultMap) {
  List<SummaryResult> list = new ArrayList<>(resultMap.size());
  for (Map.Entry<String, Integer> entry : resultMap.entrySet()) {
    String name = entry.getKey();
    Integer value = entry.getValue();

    // replace below with construction method you actually have
    list.add(SummaryResult.withName(name).andResult(value));
  }
  return list;
}

您可以将其用作收集器组合的一部分,您的整个收集器将被包裹到 collectingAndThen 调用中:

collectingAndThen(
  groupingBy(sample -> convertType(sample.getTypeId()),
     collectingAndThen(
       groupingBy(Sample::getSampleType,
         summingInt(Sample::getSampleQuantity)),
         map -> map.getOrDefault(SampleType.ADD, 0)
                - map.getOrDefault(SampleType.SUBTRACT, 0))),
  result -> toSummaryResultList(result))

但是,正如您所看到的,它是整个收集器被包装起来的,所以在我看来,上面的版本没有真正的好处,而下面的版本更简单,更容易理解(至少对我而言),它使用中间变量,但与其说是一堵代码墙:

// do the whole collecting thing like before
Map<String, Integer> map = sampleList.stream()
    .collect(Collectors.groupingBy(sample -> convertType(sample.getTypeId()),
            Collectors.collectingAndThen(
                Collectors.groupingBy(Sample::getSampleType,
                    Collectors.summingInt(Sample::getSampleQuantity)),
                        map -> map.getOrDefault(SampleType.ADD, 0)
                                - map.getOrDefault(SampleType.SUBTRACT, 0))));

// return the "beautified" result
return toSummaryResultList(map);

上面要考虑的另一点是:convertType 方法将被调用的次数与sampleList 中的元素一样多,所以如果convertType 调用“重”(例如,使用数据库或 IO),那么最好将其称为toSummaryResultList 转换的一部分,而不是作为流元素分类器。在这种情况下,您仍将从Map&lt;Integer, Integer&gt; 类型的地图中收集,并在循环内使用convertType。考虑到这一点,我不会添加任何代码,因为我认为此更改微不足道。

【讨论】:

  • 到目前为止,convertType 是一个简单的 if-else 来获取字符串,或者可能是 ENUM 值。所以我不认为它很重,但我确实看到在 Map.Entry 循环内完成转换可能也会更好。此外,关于前几行中提到的 convertType(id) > String convertType(int typeId) convertType(sample.getTypeId))。
  • @user1778855,它实际上不需要接受Sample 作为输入,它是需要更改的外部部分。我会更新答案。
  • 我仍然遇到一些关于 convertType 的问题。请参阅我使用 convertType 方法更新的帖子以及您更改第一个分类器的建议。它向我抱怨(类型不匹配:无法从 Map 转换为 Map)。
  • @user1778855,这不会发生在我身上。后一个代码块对我来说编译得很好。如果它不尝试将类型限定符添加到各个部分(lambda、收集器等)。
  • 我的错,我传入了错误的参数。 convertType 方法参数应该是 long 而不是 int。
【解决方案2】:

你确实可以使用 map() 函数

sampleList.stream()
        .collect(Collectors.groupingBy(Sample::getSampleTypeId,
                Collectors.collectingAndThen(
                        Collectors.groupingBy(Sample::getSampleType,
                                Collectors.summingInt(Sample::getSampleQuantity)),
                        map -> map.getOrDefault(SampleType.ADD, 0)
                                - map.getOrDefault(SampleType.SUBTRACT, 0))))
        .entrySet()            
        .stream()            
        .map(entry->new SummaryResult(entry.getKey()),entry.getValue())
        .collect(Collectors.toList());

【讨论】:

  • 几个问题:第二行使用的entry 是什么?如果getStringKeyFromLong 真的返回String,那么就不可能像你正在做的那样将它收集为Stream&lt;Sample&gt;。最后,调用的第二个map() 是什么? Map 没有那个方法。
  • 谢谢,忘记再次 stream() 结果,并删除了存根 getStringKeyFromLong。我只是考虑事先将条目映射到字符串,然后再次阅读并注意 OP 无论如何都希望接收一个新对象。
【解决方案3】:
ToIntFunction<Sample> signedQuantityMapper= sample -> sample.getQuantity() 
    * (sample.getType() == Type.ADD ? 1 : -1);

Function<Sample, String> keyMapper = s -> Integer.toString(s.getTypeId());

Map<String, Integer> result = sampleList.stream().collect(
    Collectors.groupingBy(
        keyMapper,
        Collectors.summingInt(signedQuantityMapper)));

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-01-23
    • 1970-01-01
    • 2021-02-24
    • 2019-09-18
    • 2019-05-27
    • 1970-01-01
    • 2021-11-15
    • 2018-05-29
    相关资源
    最近更新 更多