问题
I've got two lists:
var listA =
[
{ Id: 2, Date: "2014-11-28", Amount: 30 },
{ Id: 1, Date: "2014-11-27", Amount: 15 },
{ Id: 1, Date: "2014-11-28", Amount: 20 },
];
var listB =
[
{ Id: 1, Date: "2014-11-27", Amount: 15 },
{ Id: 2, Date: "2014-11-26", Amount: 25 },
];
I want to combine the data from both lists, grouping them by Id and using the highest date for each Id in the result, and summing the totals of the unique objects (ie. objects with the same Id and Date - there can only be one amount per Date and Id).
In other words, I want this result:
// "For ID X, the Amounts up to Date Y = total Z"
[
{"Id":1,"Date":"2014-11-28","Amount":35},
{"Id":2,"Date":"2014-11-28","Amount":55}
]
I'm very new to Ramda, but I've managed to merge the lists using this code:
// Helper functions to build predicate list
var predicateListFunc = function (props) { return R.allPredicates(R.map(R.curry(R.eqProps), props)); }
var compareProperties = R.unapply(predicateListFunc);
// Function to merge lists based on object Ids and Dates
var mergeLists = R.unionWith(compareProperties("Id", "Date"));
// Function to sort in date descending order; used later to facilitate grouping
var sortByDateDesc = R.compose(R.reverse, R.sortBy(R.prop("Date")));
// Merge the lists
var mergedData = sortByDateDesc(mergeLists(listA, listB));
For grouping and summing:
// My original code used a side-effect because I could not get the R.reduce to
// work. Turns out it was a typo that prevented the initial list from propagating
// correctly. I reimplemented it and spotted the typo after reading Furqan Zafar's
// comment)
var groupCalc = function (list, item) {
var index = R.findIndex(R.propEq("Id", item.Id), list);
if (index >= 0) {
list[index].Amount += item.Amount;
} else
list.push(item);
return list;
};
var groupedList = R.reduce(groupCalc, [], mergedData);
While it does appear to work, I'm wondering if there's a better way of solving this problem in Ramda? The documention for groupBy indicates that it's not useful here.
Updated version: jsFiddle
回答1:
Heres a fiddle that uses the R.reduce function to avoid side-effects: http://jsfiddle.net/013kjv54/6/
I only replaced your grouping code with the following:
var result = R.reduce(function(acc, tuple){
acc.push({
StockId: tuple[0],
Reference: R.maxBy(function(record){return new Date(record.Reference)}, tuple[1]).Reference,
Amount: R.reduce(function(acc, record){return acc + record.Amount}, 0, tuple[1])
});
return acc;
}, [], R.toPairs(R.groupBy(function(record){return record.StockId})(mergedData)));
回答2:
I did not see this when the question was asked. If you're still interested in alternative approaches, here is a somewhat different way of doing this:
var combine = function(acc, entry) {
return {
Id: entry.Id,
Date: acc.Date && acc.Date > entry.Date ? acc.Date : entry.Date,
Amount: (acc.Amount || 0) + entry.Amount
};
};
var process = R.pipe(
R.groupBy(R.prop('Id')),
R.values,
R.map(R.uniqWith(R.eqProps('Date'))),
R.map(R.reduce(combine, {}))
);
var result = process(R.concat(listA, listB));
You can see it in action on JSFiddle. As with many such approaches, it suffers from a potential problem in that the order of the results is tied to how the underlying JS engine orders its object key parameters, although that's mostly consistent across modern engines.
来源:https://stackoverflow.com/questions/27214451/grouping-and-summing-in-ramda-js