简体   繁体   中英

Javascript - Group and Sort Objects

I received a challenge and have done, but I think not a good way. I think there is a short way to do the same thing.

I look for MAP, REDUCE and FILTERS, but not discover a nice way.

The objectives was:

  1. Find Duplicate Transactions.
  2. Group similars
  3. Sort the results

Instructions : Sometimes when a customer is charged, there is a duplicate transaction created. We need to find those transactions so that they can be dealt with. Everything about the transaction should be identical, except the transaction id and the time at which it occurred, as there can be up to a minute delay.

findDuplicateTransactions(transactions)

Find all transactions that have the same sourceAccount, targetAccount, category, amount, and the time difference between each consecutive transaction is less than 1 minute.

Input You can assume that all parameters will always be present and valid. However, the incoming transactions are not guaranteed to be in any particular order.

list of transactions (Transaction[]) Output list of all the duplicate transaction groups, ordered by time ascending (Transaction[][]) The groups should be sorted in ascending order of the first transaction in the group. Example The input:

[
  {
    id: 3,
    sourceAccount: 'A',
    targetAccount: 'B',
    amount: 100,
    category: 'eating_out',
    time: '2018-03-02T10:34:30.000Z'
  },
  {
    id: 1,
    sourceAccount: 'A',
    targetAccount: 'B',
    amount: 100,
    category: 'eating_out',
    time: '2018-03-02T10:33:00.000Z'
  },
  {
    id: 6,
    sourceAccount: 'A',
    targetAccount: 'C',
    amount: 250,
    category: 'other',
    time: '2018-03-02T10:33:05.000Z'
  },
  {
    id: 4,
    sourceAccount: 'A',
    targetAccount: 'B',
    amount: 100,
    category: 'eating_out',
    time: '2018-03-02T10:36:00.000Z'
  },
  {
    id: 2,
    sourceAccount: 'A',
    targetAccount: 'B',
    amount: 100,
    category: 'eating_out',
    time: '2018-03-02T10:33:50.000Z'
  },
  {
    id: 5,
    sourceAccount: 'A',
    targetAccount: 'C',
    amount: 250,
    category: 'other',
    time: '2018-03-02T10:33:00.000Z'
  }
];

Output expect:

[
  [
    {
      id: 1,
      sourceAccount: "A",
      targetAccount: "B",
      amount: 100,
      category: "eating_out",
      time: "2018-03-02T10:33:00.000Z"
    },
    {
      id: 2,
      sourceAccount: "A",
      targetAccount: "B",
      amount: 100,
      category: "eating_out",
      time: "2018-03-02T10:33:50.000Z"
    },
    {
      id: 3,
      sourceAccount: "A",
      targetAccount: "B",
      amount: 100,
      category: "eating_out",
      time: "2018-03-02T10:34:30.000Z"
    }
  ],
  [
    {
      id: 5,
      sourceAccount: "A",
      targetAccount: "C",
      amount: 250,
      category: "other",
      time: "2018-03-02T10:33:00.000Z"
    },
    {
      id: 6,
      sourceAccount: "A",
      targetAccount: "C",
      amount: 250,
      category: "other",
      time: "2018-03-02T10:33:05.000Z"
    }
  ]
];

This is my code, but I did not like it. There are some nice way to do?

function findDuplicateTransactions (transactions = []) {
    var result = [];

    console.info("total itens :" + transactions.length);

      //sort
      transactions = transactions.sort((a,b)=> a.time.localeCompare(b.time))

      //remove itens not duplicated
      result = removeItens(transactions);


      //group
      result = groupBy(result, function(item){
          return [item.sourceAccount, item.targetAccount, item.amount, item.category];
      });

  console.info(result);

      //remove UniqueElements
      result = removeUniqueElements(result);



      return result;
  }

  function removeUniqueElements(array){
      var filtered = array.filter(function(value, index, arr){
          return value.length >= 2;
      });

      return filtered;
  }

  function removeItens(array){
      var itensToBeRemoved = [];
      for (var index = 0; index < array.length; index++) {
          const element1 = array[index];
          var cont = 0;

          console.info("============== looking for: " + element1.id);

          for (var index2 = 0; index2 < array.length; index2++) {
              const element2 = array[index2];

              if(element1.id != element2.id){


                var date1 = new Date(element1.time);
                var date2 = new Date(element2.time);

                var timeDiff = Math.abs(date2.getTime() - date1.getTime());

                console.info("comparing :" + element1.id + "<->" + element2.id + " diff: " + timeDiff);


                if( timeDiff < 60000) {
                    //keep it - is similar
                    console.info("find one duplicated: " + element2.id);
                    break;
                }else{
                    cont++;
                }
              }
          }

          //console.info("cont: " + cont)

          if(cont == array.length-1){
            //array.splice(index, 1);
            console.info("possible duplicated: " + element1.id);
            itensToBeRemoved.push(element1.id);
          }
      }

      var filtered = [];

      for(var i=0; i<itensToBeRemoved.length; i++){
          console.info("remove item: " + itensToBeRemoved[i]);
          array = arrayRemove(array, itensToBeRemoved[i]);
      }

    return array;
  }

function arrayRemove(arr, value) {
   return arr.filter(function(ele){
       console.info("watching: " + ele.id);
       console.info("index: " + value);
       return ele.id != value;
   });
}

  function groupBy( array , f ){
      var lists = {};
      array.forEach( function( o ){
          var list = JSON.stringify( f(o) );
          lists[list] = lists[list] || [];
          lists[list].push( o );  
      });
      return Object.keys(lists).map( function( list ){
          return lists[list]; 
      })
  }

You could sort the array in advance and reduce the array by looking for a group with the same category.

 var data = [{ id: 3, sourceAccount: 'A', targetAccount: 'B', amount: 100, category: 'eating_out', time: '2018-03-02T10:34:30.000Z' }, { id: 1, sourceAccount: 'A', targetAccount: 'B', amount: 100, category: 'eating_out', time: '2018-03-02T10:33:00.000Z' }, { id: 6, sourceAccount: 'A', targetAccount: 'C', amount: 250, category: 'other', time: '2018-03-02T10:33:05.000Z' }, { id: 4, sourceAccount: 'A', targetAccount: 'B', amount: 100, category: 'eating_out', time: '2018-03-02T10:36:00.000Z' }, { id: 2, sourceAccount: 'A', targetAccount: 'B', amount: 100, category: 'eating_out', time: '2018-03-02T10:33:50.000Z' }, { id: 5, sourceAccount: 'A', targetAccount: 'C', amount: 250, category: 'other', time: '2018-03-02T10:33:00.000Z' }], result = data .sort(({ time: a }, { time: b }) => a.localeCompare(b)) .reduce((r, o) => { var temp = r.find(([{ category }]) => category === o.category); if (!temp) r.push(temp = []); temp.push(o); return r; }, []); console.log(result); 
 .as-console-wrapper { max-height: 100% !important; top: 0; } 

With a hash table.

 const delta = (t1, t2) => Math.abs(new Date(t1) - new Date(t2)); var data = [{ id: 3, sourceAccount: 'A', targetAccount: 'B', amount: 100, category: 'eating_out', time: '2018-03-02T10:34:30.000Z' }, { id: 1, sourceAccount: 'A', targetAccount: 'B', amount: 100, category: 'eating_out', time: '2018-03-02T10:33:00.000Z' }, { id: 6, sourceAccount: 'A', targetAccount: 'C', amount: 250, category: 'other', time: '2018-03-02T10:33:05.000Z' }, { id: 4, sourceAccount: 'A', targetAccount: 'B', amount: 100, category: 'eating_out', time: '2018-03-02T10:36:00.000Z' }, { id: 2, sourceAccount: 'A', targetAccount: 'B', amount: 100, category: 'eating_out', time: '2018-03-02T10:33:50.000Z' }, { id: 5, sourceAccount: 'A', targetAccount: 'C', amount: 250, category: 'other', time: '2018-03-02T10:33:00.000Z' }], keys = ['sourceAccount', 'targetAccount', 'amount', 'category'], result = Object.values(data .sort(({ time: a }, { time: b }) => a.localeCompare(b)) .filter((o, i, a) => { while (a[--i] && delta(o.time, a[i].time) < 60000) { if (keys.every(k => o[k] === a[i][k])) return; } return true; }) .reduce((r, o) => ((r[o.category] = r[o.category] || []).push(o), r), {}) ); console.log(result); 
 .as-console-wrapper { max-height: 100% !important; top: 0; } 

You can sort first based on time and than reduce

 let data = [{id: 3,sourceAccount: 'A',targetAccount: 'B',amount: 100,category: 'eating_out',time: '2018-03-02T10:34:30.000Z'},{id: 1,sourceAccount: 'A',targetAccount: 'B',amount: 100,category: 'eating_out',time: '2018-03-02T10:33:00.000Z'}, {id: 6,sourceAccount: 'A',targetAccount: 'C',amount: 250,category: 'other',time: '2018-03-02T10:33:05.000Z'}, {id: 4,sourceAccount: 'A',targetAccount: 'B',amount: 100,category: 'eating_out',time: '2018-03-02T10:36:00.000Z'}, {id: 2,sourceAccount: 'A',targetAccount: 'B',amount: 100,category: 'eating_out',time: '2018-03-02T10:33:50.000Z'},{id: 5,sourceAccount: 'A',targetAccount: 'C',amount: 250,category: 'other',time: '2018-03-02T10:33:00.000Z'}]; let sortedData = data.sort((a,b)=> a.time.localeCompare(b.time)) let keyTrack = sortedData[0].sourceAccount + sortedData[0].targetAccount let temp = [] let final = sortedData.reduce((op,inp) => { let key = inp.sourceAccount + inp.targetAccount if(keyTrack !== key){ keyTrack = key op.push(temp) temp = [] } temp.push(inp) return op },[]) if(temp.length){ final.push(temp) } console.log(final) 

The way you were doing was the best way but using some unnecessary code, please have a look at how I reduced your code and removed a few bugs eg while removing we must check sourceAccount target account and etc as mentioned with time gaps as well.

function findDuplicateTransactions (transactions = []) {
    var result = [];
    //just for the result should be in ascending order
      result = transactions.sort((a,b)=> a.time.localeCompare(b.time));
      //remove unique transactions
      result = removeUniqueTransactions(result);
      //group with category
      result = groupItems(result);
      // returning result
      return result;
  }
  function removeUniqueTransactions (transactions = []) {
    var result = [];
    for (const item of transactions) {
      let lengthOfcheckInArray = 0;
      for(const item2 of transactions) {

        if(item.id !== item2.id) {
          if(
            Math.abs(new Date(item.time) - new Date(item2.time)) < 60000 && 
            item.amount === item2.amount && 
            item.sourceAccount === item2.sourceAccount && 
            item.targetAccount === item2.targetAccount && 
            item.category === item2.category
            ) {
              break;
          }else{
            lengthOfcheckInArray ++;
          }
        }
      }
      // just either unique in entire length of array or not
      if (lengthOfcheckInArray === transactions.length - 1) {
        result.push(item);
      }
    }
    return transactions.filter(trans => !result.includes(trans));
  }
  function groupItems (transactions = []) {
    var result = [];

    var group = transactions.reduce((r, a) => {
      r[a.category] = [...r[a.category] || [], a];
      return r;
    }, {});

    result = Object.values(group);

    return result;
  }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM