Search code examples
javascriptarraysnode.jssortinglodash

Sort an array of Objects and take N elements


in a NodeJS service I have an array with objects that have these properties:

  • batchType: string
  • batchId: string (is a hash)
  • transactionId: string (it's a hash)

This array stores all transactions of different types of batches.

Basically what I need is to be able to get N items from the array, but respecting certain rules:

  1. At least get 1 item from each type of batch
  2. At least get 1 item for each batchId
  3. Sometimes the array could have only one type of batch

This is an example of the array:

let batchTransactionsArray = [
  { batchType: 'type1', batchId: '123', transactionId: 'ffasf23' },
  { batchType: 'type1', batchId: '312', transactionId: '423' },
  { batchType: 'type1', batchId: '123', transactionId: '534' },
  { batchType: 'type1', batchId: '312', transactionId: '86' },
  { batchType: 'type2', batchId: '111', transactionId: '97' },
  { batchType: 'type1', batchId: '312', transactionId: '1945' },
  { batchType: 'type1', batchId: '123', transactionId: '79' },
  { batchType: 'type1', batchId: '312', transactionId: '79' },
  { batchType: 'type3', batchId: '425', transactionId: '1555645' },
  { batchType: 'type1', batchId: '123', transactionId: 'fg5' },
  { batchType: 'type1', batchId: '123', transactionId: 'jkh5' },
  { batchType: 'type1', batchId: '312', transactionId: '53j' },
  { batchType: 'type1', batchId: '111', transactionId: '4545' },
  { batchType: 'type2', batchId: '111', transactionId: '534l' },
  { batchType: 'type1', batchId: '111', transactionId: 'jkg435' },
  { batchType: 'type1', batchId: '111', transactionId: 'gfxg23' },
  { batchType: 'type1', batchId: '111', transactionId: '7asdt' },
  { batchType: 'type1', batchId: '222', transactionId: 'jdsa7' },
  { batchType: 'type3', batchId: '663', transactionId: '12423445' },
  { batchType: 'type1', batchId: '111', transactionId: '89saf6' },
  { batchType: 'type1', batchId: '111', transactionId: '12h3g' },
  { batchType: 'type1', batchId: '111', transactionId: '4h3k2hj' },
  { batchType: 'type3', batchId: '663', transactionId: '145' }
];

And an example of the output I need is (if I want 5 transactions from the array):

[{ batchType: 'type1', batchId: '123', transactionId: '534' },
 { batchType: 'type1', batchId: '312', transactionId: '86' },
 { batchType: 'type2', batchId: '111', transactionId: '97' },
 { batchType: 'type2', batchId: '111', transactionId: '534l' },
 { batchType: 'type3', batchId: '663', transactionId: '145' }
]

The criteria for sorting transactionIds would be random, there is no specific order to meet.

I was trying some lodash functions like groupBy and sortBy but no luck yet.

Here is a jsfiddle were I was playing with this: https://jsfiddle.net/20jh3ze7/

I really appreciate suggestions.


Solution

  • You can do this in steps:

    • first add unique batchType elements (this meets the first criterion)
    • then add unique batchId elements (this meets the second criterion)
    • fill the array with remaining elements if there's still some space left (to go up to N elements)

    To check for uniqueness, you can use a Set. Since you said you're using lodash, you can use _.groupBy to group your array by batchItem and batchId to get unique elements from each group.

    let batchTransactionsArray = [
      { batchType: 'type1', batchId: '123', transactionId: 'ffasf23' },
      { batchType: 'type1', batchId: '312', transactionId: '423' },
      { batchType: 'type1', batchId: '123', transactionId: '534' },
      { batchType: 'type1', batchId: '312', transactionId: '86' },
      { batchType: 'type2', batchId: '111', transactionId: '97' },
      { batchType: 'type1', batchId: '312', transactionId: '1945' },
      { batchType: 'type1', batchId: '123', transactionId: '79' },
      { batchType: 'type1', batchId: '312', transactionId: '79' },
      { batchType: 'type3', batchId: '425', transactionId: '1555645' },
      { batchType: 'type1', batchId: '123', transactionId: 'fg5' },
      { batchType: 'type1', batchId: '123', transactionId: 'jkh5' },
      { batchType: 'type1', batchId: '312', transactionId: '53j' },
      { batchType: 'type1', batchId: '111', transactionId: '4545' },
      { batchType: 'type2', batchId: '111', transactionId: '534l' },
      { batchType: 'type1', batchId: '111', transactionId: 'jkg435' },
      { batchType: 'type1', batchId: '111', transactionId: 'gfxg23' },
      { batchType: 'type1', batchId: '111', transactionId: '7asdt' },
      { batchType: 'type1', batchId: '222', transactionId: 'jdsa7' },
      { batchType: 'type3', batchId: '663', transactionId: '12423445' },
      { batchType: 'type1', batchId: '111', transactionId: '89saf6' },
      { batchType: 'type1', batchId: '111', transactionId: '12h3g' },
      { batchType: 'type1', batchId: '111', transactionId: '4h3k2hj' },
      { batchType: 'type3', batchId: '663', transactionId: '145' }
    ];
    
    function getNItems(array, N = 5) {
      if (N >= array.length) return array;
      let batchTypeGroups = _.groupBy(array, e => e.batchType),
          res  = new Set(), batchIds = new Set();
      
      // add unique batchTypes
      for (let [batchType, elements] of Object.entries(batchTypeGroups)) {
        if (res.size === N) break;
        let d = elements.find(e => !batchIds.has(e.batchId));
        if (d) {
          res.add(d);
          batchIds.add(d.batchId);
        } else {
          res.add(elements[0]);
        }
      }
      
      // add remaining unique batchIds
      let batchIdGroups = _.groupBy(array.filter(e => !batchIds.has(e.batchId)), e => e.batchId);
      for (let [batchId, elements] of Object.entries(batchIdGroups)) {
        if (res.size === N) break;
        res.add(elements[0]);
      }
      
      // add any remaining elements until we have N elements
      for (let e of array) {
        if (res.size === N) break;
        res.add(e);
      }
      
      return Array.from(res);
    }
    
    console.log(getNItems(batchTransactionsArray, 5));
    <script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.min.js"></script>