Java 8 Stream实现List<List<Integer>>纵向求和方案咨询
Hey there! I totally get where you're stuck—trying to compute a vertical sum (summing values at the same position across all inner lists) using Java 8's Stream API can feel tricky when you're used to mapping/reducing single-level collections. Let's walk through a couple of solid solutions tailored to your transactions list (where each inner list represents per-item quantities in a transaction).
Assumptions First
Before diving in, let's set a baseline: we'll assume all inner lists in transactions have the same length (since each position corresponds to the same product). If you need to handle variable-length lists, I'll add a note on that later.
Solution 1: Using Stream.reduce()
This is the most straightforward approach using the core reduce operation. We'll start with an initial all-zero list (matching the length of your transaction items) and iteratively sum each position with every subsequent transaction.
import java.util.List; import java.util.stream.Collectors; import java.util.stream.IntStream; // Your transactions list: List<List<Integer>> transactions = ...; List<Integer> verticalTotal = transactions.stream() .reduce( // Initial value: empty list if no transactions, else all zeros matching first transaction's length transactions.isEmpty() ? List.of() : transactions.get(0).stream().map(i -> 0).collect(Collectors.toList()), // Accumulator: sum current total with next transaction, position by position (currentSum, nextTransaction) -> IntStream.range(0, currentSum.size()) .mapToObj(index -> currentSum.get(index) + nextTransaction.get(index)) .collect(Collectors.toList()), // Combiner: merge two partial totals (for parallel streams) (partialSum1, partialSum2) -> IntStream.range(0, partialSum1.size()) .mapToObj(index -> partialSum1.get(index) + partialSum2.get(index)) .collect(Collectors.toList()) );
How It Works:
- Initial Value: We check if
transactionsis empty first (to avoid NPE). If not, we create a list of zeros with the same length as the first transaction—this is our starting total. - Accumulator: For each transaction, we loop through every index, add the transaction's value at that index to our running total, and build a new total list.
- Combiner: When using parallel streams, this merges two independently calculated partial totals by summing their corresponding positions.
Solution 2: Custom Collector
If you prefer a more reusable approach, you can create a custom Collector that handles the vertical sum logic directly.
import java.util.ArrayList; import java.util.List; import java.util.stream.Collector; import java.util.stream.IntStream; Collector<List<Integer>, List<Integer>, List<Integer>> verticalSumCollector = Collector.of( // Supplier: Initialize an empty list to hold the running total ArrayList::new, // Accumulator: Update the total with each transaction (runningTotal, transaction) -> { if (runningTotal.isEmpty()) { // First transaction: initialize total with zeros, then add the transaction values transaction.forEach(item -> runningTotal.add(0)); } // Sum each position IntStream.range(0, transaction.size()) .forEach(index -> runningTotal.set(index, runningTotal.get(index) + transaction.get(index))); }, // Combiner: Merge two running totals (for parallel streams) (totalA, totalB) -> { IntStream.range(0, totalA.size()) .forEach(index -> totalA.set(index, totalA.get(index) + totalB.get(index))); return totalA; }, // Optional characteristics: Mark as unordered since summation order doesn't matter Collector.Characteristics.UNORDERED ); // Usage List<Integer> verticalTotal = transactions.stream().collect(verticalSumCollector);
How It Works:
- Supplier: Creates an empty list to track our running total.
- Accumulator: For the first transaction, we initialize the total with zeros (matching the transaction length), then add each transaction's value to the corresponding position. For subsequent transactions, we just update each position directly.
- Combiner: Merges two separate running totals by summing their corresponding positions.
Edge Case Handling
- Empty
transactions: Both solutions return an empty list. If you need a default all-zero list (e.g., if you know the expected number of products), replace the initial empty list with a pre-sized zero list. - Variable-length inner lists: To avoid
IndexOutOfBoundsException, modify the accumulator to useMath.min(runningTotal.size(), transaction.size())when looping through indices. You can choose to ignore extra values in longer lists, or pad shorter lists with zeros—adjust based on your needs.
Hope these solutions clear up how to handle vertical summation with Java 8 Streams! Let me know if you need tweaks for specific edge cases.
内容的提问来源于stack exchange,提问作者Abhishek

