Reputation: 322
I am trying to figure out what is the best way to sum up all the same item's quantities specifically the data is formed like below:
data = [
{Item Name: Item 2, Quantity: 1},
{Item Name: Item 1, Quantity: 1},
{Item Name: Item 3, Quantity: 1},
{Item Name: Item 2, Quantity: 2},
{Item Name: Item 1, Quantity: 2},
{Item Name: Item 3, Quantity: 2},
];
and what I am trying to achieve is:
totalList = [{Item Name: Item 1, Quantity: 3}, {Item Name: Item 2, Quantity: 3}, {Item Name: Item 3, Quantity: 3}];
I have tried using a tempData variable to hold onto the element and compare the rest however, this seems like it only compares that first one to the rest of the list.
var tempData = {};
var totalList = [];
data.forEach((element) {
if (tempData.isEmpty) {
tempData = element;
totalList.add(tempData);
} else {
if (tempData['Item Name'] == element['Item Name']) {
tempData['Quantity'] = tempData['Quantity'] + element['Quantity'];
totalList.add(tempData);
} else {
tempData = {
'Item Name': element['Item Name'],
'Quantity': element['Quantity']
};
totalList.add(tempData);
}
}
});
The above didnt seem to give me the output I was looking for...
What should I do instead?
Thanks for your help in advance.
Upvotes: 0
Views: 77
Reputation: 90175
Your data structure is not pretty; the 'Item Name'
and 'Quantity'
labels not very useful in the structure itself, so I would get rid of them and create a simplified Map<String, int>
that directly maps names to quantities. Ideally you could just use the simplified structure from then on, but if you really need the explicit labels, you could convert back.
void main(List<String> args) async {
var data = [
{'Item Name': 'Item 2', 'Quantity': 1},
{'Item Name': 'Item 1', 'Quantity': 1},
{'Item Name': 'Item 3', 'Quantity': 1},
{'Item Name': 'Item 2', 'Quantity': 2},
{'Item Name': 'Item 1', 'Quantity': 2},
{'Item Name': 'Item 3', 'Quantity': 2},
];
// Sum everything into a simpler data structure.
var totalCounts = <String, int>{};
for (var map in data) {
var name = map['Item Name'] as String;
var quantity = map['Quantity'] as int;
totalCounts[name] = (totalCounts[name] ?? 0) + quantity;
}
// Reformat back into the original structure.
var totalList = <Map<String, dynamic>>[
for (var entry in totalCounts.entries)
{'Item Name': entry.key, 'Quantity': entry.value},
];
// Optional: Sort.
totalList.sort((a, b) => a['Item Name'].compareTo(b['Item Name']));
print(totalList); // Prints: [{Item Name: Item 1, Quantity: 3}, {Item Name: Item 2, Quantity: 3}, {Item Name: Item 3, Quantity: 3}]
}
In real code, I additionally would add:
const nameLabel = 'Item Name';
const quantityLabel = 'Quantity';
and use those everywhere instead of the string literals to reduce opportunities for making typos.
Upvotes: 1
Reputation: 31299
I have created the following solution which are not that pretty but it works. The concept is to create a Map<String, Map<String, Object>>
which keep track of elements we already have visited by using the "Item Name" of each element as key.
void main() {
final data = [
{'Item Name': 'Item 2', 'Quantity': 1},
{'Item Name': 'Item 1', 'Quantity': 1},
{'Item Name': 'Item 3', 'Quantity': 1},
{'Item Name': 'Item 2', 'Quantity': 2},
{'Item Name': 'Item 1', 'Quantity': 2},
{'Item Name': 'Item 3', 'Quantity': 2},
];
final result = [
...data.fold(
<String, Map<String, Object>>{},
(Map<String, Map<String, Object>> sum, element) => sum
..update(
element['Item Name'] as String,
(value) => value
..update('Quantity',
(value) => (value as int) + (element['Quantity'] as int)),
ifAbsent: () => Map.from(element))).values
];
result.sort(
(a, b) => (a['Item Name'] as String).compareTo(b['Item Name'] as String));
print(result); // [{Item Name: Item 1, Quantity: 3}, {Item Name: Item 2, Quantity: 3}, {Item Name: Item 3, Quantity: 3}]
}
Upvotes: 0