在 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())));
我有一个像这样的 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())));