Grouping and Summing in Ramda.js

I have 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 data from both lists by grouping by ID and using the highest date for each ID in the result and summing the totals of unique objects (i.e. objects with the same IDs and dates - there can only be one sum per date and ID).

In other words, I need 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 am very new to Ramda but I managed to concatenate 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));

      

To group and summarize:

// 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 
// 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 works, I am wondering if there is a better way to solve this problem in Ramda? The doc for groupBy points out that this is not useful here.

Updated version: jsFiddle

+3


source to share


2 answers


Here's a script that uses the R.reduce function to avoid side effects: http://jsfiddle.net/013kjv54/6/

I 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)));

      

+4


source


I didn't see this when the question was asked. If you are still interested in alternative approaches, here is a slightly different way to do it:

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 the JSFiddle . As with many such approaches, it runs into the potential problem that the order of results is tied to how the underlying JS engine orders its key object parameters, although this is mostly compatible with modern models.

+4


source







All Articles