在 java 对象中创建 12 个月的滚动数据

Create 12 months rollover data inside a java object

我有一个像这样的 java 对象 Map<Integer, Long> monthCaseCountMap

此对象包含如下格式的数据:

202103, 2
202104, 4
202105, 0
202106, 9
.
.
.
.
202202, 10

第一个值是月份键,第二个值是案例数。

我需要逐月创建月度滚动数据或案例计数累积。

结果应该类似于 -

202103, 2
202104, 6
202105, 6
202106, 15
.
.
.
.
202202, 98

我们如何使用 Java 流或使用任何其他方法来做到这一点。

感谢@eritren 提供正确的解决方案。

您为这项工作使用了错误的工具。 Java 当您需要执行的操作是 [A] 仅对流的单个元素进行操作,或 [B] 对所有元素进行操作时,流是完成这项工作的正确工具。

您想要对其中一些进行操作(具体来说,'predecessors')。从根本上说,Stream 并不是为此而设计的。您为这项工作使用了错误的工具。例如,Stream 有 .parallel(),在那个视图中,'the sum of all stuff before me' 的概念甚至不再有意义。

你可以通过涉及排序的概念来重新定义问题(“我想要这个键的所有值的总和以及在这个键之前排序的所有值”)但这将是非常低效的。

当您尝试将工具用于非设计目的时,可以预料的是,流版本要么慢得多,要么令人厌恶(混合流和 non-stream 概念,因此得到两者的缺点和两者的优点),并且很可能有更多的代码和更复杂的代码。

只需使用迭代过程 - 您所定义的操作在迭代思维模式下要简单得多。如果您想改进您的代码,请记住:您应该使用能够准确描述您所拥有的内容的最具体的类型。 Integer 没什么好说的,LocalDate 更准确,因为地图中的键显然是日期,或者至少是月+年。

public class Hospital {
  SortedMap<LocalDate, Long> caseCounts = createCaseCountMap();
// createCaseCountMap ends up doing something like:
//    caseCounts.put(LocalDate.of(2021, 03, 1), 2);
//    caseCounts.put(LocalDate.of(2021, 04, 1), 4);
//    .... etc

// option 1:

  public long getSumUntil(LocalDate mark) {
    return caseCounts.headMap(mark, true).values().stream()
      .mapToLong(Long::longValue).sum();
  }

// option 2:

  public SortedMap<LocalDate, Long> makeIncrementalMap() {
    var out = new TreeMap<LocalDate, Long>();
    long sum = 0L;
    for (var entry : caseCounts.entrySet()) {
      sum += entry.getValue();
      out.put(entry.getKey(), sum);
    }
    return out;
  }
}

在这里,对于选项 2,使用流是一个非常愚蠢的想法,因为该操作从根本上取决于它的一些而不是所有它的邻居,而流并不适用于这些邻居。

首先,您需要排序您可能在 TreeMap 中创建的条目。

然后想不惜一切代价用流解决这个问题以累积 你可以模仿 条目集 上的迭代使用 iterate() 方法的 for 循环。

排序映射的第一个条目用作种子。然后,对于流中创建的每个连续条目,较高条目的键将用作新条目的键,并将较高条目的值与流中先前条目的值之和用作值。该过程将继续,直到更高的条目存在,即 getAccumulatedEntry() 返回的结果不是 null。此条件由谓词 Objects::nonNull.

表示
public static void main(String[] args) {
    Map<Integer, Long> monthCaseCount =
            Map.of(202103, 2L,
                    202104, 4L,
                    202105, 0L,
                    202106, 9L);

    NavigableMap<Integer, Long> monthCaseCountSorted = new TreeMap<>(monthCaseCount);

    Map<Integer, Long> monthCaseCountAccumulated =
            Stream.iterate(monthCaseCountSorted.firstEntry(),
                           Objects::nonNull,
                           entry -> getAccumulatedEntry(monthCaseCountSorted, entry))
                    .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));

    monthCaseCountAccumulated.entrySet().stream() // this part was added only for demonstration purpose
            .sorted(Map.Entry.comparingByKey())
            .forEach(entry -> System.out.printf("%d : %d%n", entry.getKey(), entry.getValue()));
}

private static Map.Entry<Integer, Long> getAccumulatedEntry(NavigableMap<Integer, Long> monthCaseCountSorted,
                                                            Map.Entry<Integer, Long> entry) {
    Integer higher = monthCaseCountSorted.higherKey(entry.getKey());

    return higher == null ?
            null : Map.entry(higher, entry.getValue() + monthCaseCountSorted.get(higher));
}

输出

202103 : 2
202104 : 6
202105 : 6
202106 : 15

注意:更有效的方法是调整monthCaseCountSorted图中的值。流不适合这样的任务。函数不得引起副作用,并且函数的每次执行都必须有相同的结果(如果您尝试通过直接更改 monthCaseCountSorted 中的值来实现 stream-based 解决方案,这两个条件都将被违反)

另一种选择是使用 AtomicLong 存储总和并将累加值收集到新映射中:

Map<Integer, Long> monthCaseCountMap = Map.of(  202103, 2L,
                                                202104, 4L,
                                                202105, 0L,
                                                202106, 9L);

AtomicLong sum = new AtomicLong(0L);

Map<Integer, Long> accumulated = 
        monthCaseCountMap.entrySet()
                .stream()
                .sorted(Map.Entry.comparingByKey())
                .collect(Collectors.toMap(Map.Entry::getKey, e -> sum.addAndGet(e.getValue())));