1

I need to solve the following problem with a function that takes an array of objects as an argument and solves all three cases. Given an array of objects, how do I group them into sub-arrays based on several conditions? I am looking for errors in the payment system and want to receive an array of duplicated transactions (sorted by transaction time ascending).

Transactions are considered duplicates when: manufacturer, amount, category are exactly the same AND time between transactions is less than 45 seconds.

I am looking for an ES6 solution and I am sure that it would include .reduce method.

I tried working on it, by following reduce gives me an object based on manufacturer key, and this is not a result I would like to achieve as I need sub-arrays instead of objects and need more conditions than just a manufacturer.

let groupedArr = data.reduce((accumulator, currentValue) => {
 accumulator[currentValue.manufacturer] = [...accumulator[currentValue.manufacturer] || [], currentValue];
 return accumulator;
}, {});

Case 1:

INPUT:

const data = [{
    id: 3,
    manufacturer: 'audi',
    amount: 40,
    category: 'leasing',
    transaction: '2020-03-02T10:34:30.000Z'
  },
  {
    id: 4,
    manufacturer: 'audi',
    amount: 40,
    category: 'leasing',
    transaction: '2020-03-02T10:34:38.000Z'
  },
  {
    id: 1,
    manufacturer: 'mercedes',
    amount: 20,
    category: 'leasing',
    transaction: '2020-03-05T12:00:00.000Z'
  },
  {
    id: 7,
    manufacturer: 'audi',
    amount: 40,
    category: 'leasing',
    transaction: '2020-03-20T11:00:00.000Z'
  },
  {
    id: 6,
    manufacturer: 'mercedes',
    amount: 20,
    category: 'leasing',
    transaction: '2020-03-05T12:00:44.000Z'
  },
  {
    id: 2,
    manufacturer: 'volkswagen',
    amount: 2,
    category: 'credit',
    transaction: '2020-03-05T12:00:45.000Z'
  },
  {
    id: 5,
    manufacturer: 'audi',
    amount: 40,
    category: 'leasing',
    transaction: '2020-03-02T10:35:17.000Z'
  },
]

Expected output:

[[{
    id: 3,
    manufacturer: 'audi',
    amount: 40,
    category: 'leasing',
    transaction: '2020-03-02T10:34:30.000Z'
  },
  {
    id: 4,
    manufacturer: 'audi',
    amount: 40,
    category: 'leasing',
    transaction: '2020-03-02T10:34:38.000Z'
  },
  {
    id: 5,
    manufacturer: 'audi',
    amount: 40,
    category: 'leasing',
    transaction: '2020-03-02T10:35:17.000Z'
  }],
  [{
    id: 1,
    manufacturer: 'mercedes',
    amount: 20,
    category: 'leasing',
    transaction: '2020-03-05T12:00:00.000Z'
  },
  {
    id: 6,
    manufacturer: 'mercedes',
    amount: 20,
    category: 'leasing',
    transaction: '2020-03-05T12:00:44.000Z'
  }]
]

Case 2:

INPUT:

const data = [{
    id: 2,
    manufacturer: 'audi',
    amount: 40,
    category: 'leasing',
    transaction: '2020-03-02T10:34:30.000Z'
  },
  {
    id: 7,
    manufacturer: 'audi',
    amount: 40,
    category: 'leasing',
    transaction: '2020-03-20T11:00:00.000Z'
  }]

Expected output:

[]

Explanation: More than 45 seconds between transactions should output an empty array.

Case 3:

INPUT:

const data = [{
    id: 2,
    manufacturer: 'audi',
    amount: 40,
    category: 'leasing',
    transaction: '2020-03-02T10:34:30.000Z'
  },
  {
    id: 1,
    manufacturer: 'audi',
    amount: 40,
    category: 'credit',
    transaction: '2020-03-02T10:34:40.000Z'
  }]

Expected output:

[]

Explanation: Less than 45 seconds, but category is different so it's not considered a duplicate.

4
  • "need more conditions than just a manufacturer" - Then don't only use the manufacturer key as the property name. "I need sub-arrays instead of objects" - Just convert it after you've grouped the entries. Commented May 25, 2020 at 10:34
  • Why would id=2 (volkswagen, credit) not be included in output for case 1? See my answer for case 1, I don't understand why you want to exclude that. Also, when there is a duplicate (within 40 seconds, which one is kept - the one which was "earlier" in time?). Please try to explain that and make your question a bit clearer where possible. Commented May 25, 2020 at 10:37
  • Will duplicates always have the same id? (As here there are two entries for Audi with id=5 and they are within 40 seconds) - Bad practice to use the same id twice, but if that is what signifies a duplicate that is also easy to solve. Commented May 25, 2020 at 10:43
  • perfect, my answer should give you what you expect now. Commented May 25, 2020 at 11:10

1 Answer 1

1

Case 1:

function example1(initData, fieldsArr){  
  
  const output = data.reduce((aggObj, item) => {
    const stringId = fieldsArr.map(key => item[key]).join('_');
    
    if (aggObj[stringId]){
      aggObj[stringId].push(item);
    }
    else {
      aggObj[stringId] = [item];
    }

    return aggObj;
  }, {})
  
  const outputNoDups = Object.values(output).map(group => {
  
    const sorted = group.sort((a,b) => new Date(a.transaction) < new Date(b.transaction) ? -1 : 1);
    
    return sorted.filter((a, i) => {
      if (i == 0) return true;

      if (a.amount == sorted[i - 1].amount &&
          new Date(a.transaction) - new Date(sorted[i - 1].transaction) <= 45000){
        return true;
      }
      
      return false;
    });
  });
  
  return outputNoDups.filter(a => a.length > 1);
}  

console.log(example1(data, ['manufacturer', 'category']));
.as-console-wrapper { max-height: 100% !important; top: 0; }
<script id="initData">
const data = [{
    id: 3,
    manufacturer: 'audi',
    amount: 40,
    category: 'leasing',
    transaction: '2020-03-02T10:34:30.000Z'
  },
  {
    id: 4,
    manufacturer: 'audi',
    amount: 40,
    category: 'leasing',
    transaction: '2020-03-02T10:34:38.000Z'
  },
  {
    id: 1,
    manufacturer: 'mercedes',
    amount: 20,
    category: 'leasing',
    transaction: '2020-03-05T12:00:00.000Z'
  },
  {
    id: 7,
    manufacturer: 'audi',
    amount: 40,
    category: 'leasing',
    transaction: '2020-03-20T11:00:00.000Z'
  },
  {
    id: 6,
    manufacturer: 'mercedes',
    amount: 20,
    category: 'leasing',
    transaction: '2020-03-05T12:00:44.000Z'
  },
  {
    id: 2,
    manufacturer: 'volkswagen',
    amount: 2,
    category: 'credit',
    transaction: '2020-03-05T12:00:45.000Z'
  },
  {
    id: 5,
    manufacturer: 'audi',
    amount: 40,
    category: 'leasing',
    transaction: '2020-03-02T10:35:17.000Z'
  },
];
</script>

OUTPUT (Case 1):

[
  [
    {
      "id": 3,
      "manufacturer": "audi",
      "amount": 40,
      "category": "leasing",
      "transaction": "2020-03-02T10:34:30.000Z"
    },
    {
      "id": 4,
      "manufacturer": "audi",
      "amount": 40,
      "category": "leasing",
      "transaction": "2020-03-02T10:34:38.000Z"
    },
    {
      "id": 5,
      "manufacturer": "audi",
      "amount": 40,
      "category": "leasing",
      "transaction": "2020-03-02T10:35:17.000Z"
    }
  ],
  [
    {
      "id": 1,
      "manufacturer": "mercedes",
      "amount": 20,
      "category": "leasing",
      "transaction": "2020-03-05T12:00:00.000Z"
    },
    {
      "id": 6,
      "manufacturer": "mercedes",
      "amount": 20,
      "category": "leasing",
      "transaction": "2020-03-05T12:00:44.000Z"
    }
  ]
]

Case 2:

function example1(initData, fieldsArr){  
  
  const output = data.reduce((aggObj, item) => {
    const stringId = fieldsArr.map(key => item[key]).join('_');
    
    if (aggObj[stringId]){
      aggObj[stringId].push(item);
    }
    else {
      aggObj[stringId] = [item];
    }

    return aggObj;
  }, {})
  
  const outputNoDups = Object.values(output).map(group => {
  
    const sorted = group.sort((a,b) => new Date(a.transaction) < new Date(b.transaction) ? -1 : 1);
    
    return sorted.filter((a, i) => {
      if (i == 0) return true;

      if (a.amount == sorted[i - 1].amount &&
          new Date(a.transaction) - new Date(sorted[i - 1].transaction) <= 45000){
        return true;
      }
      
      return false;
    });
  });
  
  return outputNoDups.filter(a => a.length > 1);
}  

console.log(example1(data, ['manufacturer', 'category']));
.as-console-wrapper { max-height: 100% !important; top: 0; }
<script id="initData">
const data = [{
    id: 2,
    manufacturer: 'audi',
    amount: 40,
    category: 'leasing',
    transaction: '2020-03-02T10:34:30.000Z'
  },
  {
    id: 7,
    manufacturer: 'audi',
    amount: 40,
    category: 'leasing',
    transaction: '2020-03-20T11:00:00.000Z'
  }]
</script>

Case 3:

function example1(initData, fieldsArr){  
  
  const output = data.reduce((aggObj, item) => {
    const stringId = fieldsArr.map(key => item[key]).join('_');
    
    if (aggObj[stringId]){
      aggObj[stringId].push(item);
    }
    else {
      aggObj[stringId] = [item];
    }

    return aggObj;
  }, {})
  
  const outputNoDups = Object.values(output).map(group => {
  
    const sorted = group.sort((a,b) => new Date(a.transaction) < new Date(b.transaction) ? -1 : 1);
    
    return sorted.filter((a, i) => {
      if (i == 0) return true;

      if (a.amount == sorted[i - 1].amount &&
          new Date(a.transaction) - new Date(sorted[i - 1].transaction) <= 45000){
        return true;
      }
      
      return false;
    });
  });
  
  return outputNoDups.filter(a => a.length > 1);
}  

console.log(example1(data, ['manufacturer', 'category']));
.as-console-wrapper { max-height: 100% !important; top: 0; }
<script id="initData">
const data = [{
    id: 2,
    manufacturer: 'audi',
    amount: 40,
    category: 'leasing',
    transaction: '2020-03-02T10:34:30.000Z'
  },
  {
    id: 1,
    manufacturer: 'audi',
    amount: 40,
    category: 'credit',
    transaction: '2020-03-02T10:34:40.000Z'
  }]
</script>

Case 4 (an edge case you hadn't considered - time is less than 45 but amount is different):

function example1(initData, fieldsArr){  
  
  const output = data.reduce((aggObj, item) => {
    const stringId = fieldsArr.map(key => item[key]).join('_');
    
    if (aggObj[stringId]){
      aggObj[stringId].push(item);
    }
    else {
      aggObj[stringId] = [item];
    }

    return aggObj;
  }, {})
  
  const outputNoDups = Object.values(output).map(group => {
  
    const sorted = group.sort((a,b) => new Date(a.transaction) < new Date(b.transaction) ? -1 : 1);
    
    return sorted.filter((a, i) => {
      if (i == 0) return true;

      if (a.amount == sorted[i - 1].amount &&
          new Date(a.transaction) - new Date(sorted[i - 1].transaction) <= 45000){
        return true;
      }
      
      return false;
    });
  });
  
  return outputNoDups.filter(a => a.length > 1);
}  

console.log(example1(data, ['manufacturer', 'category']));
.as-console-wrapper { max-height: 100% !important; top: 0; }
<script id="initData">
const data = [{
    id: 2,
    manufacturer: 'audi',
    amount: 40,
    category: 'leasing',
    transaction: '2020-03-02T10:34:30.000Z'
  },
  {
    id: 1,
    manufacturer: 'audi',
    amount: 30,
    category: 'leasing',
    transaction: '2020-03-02T10:34:40.000Z'
  }]
</script>

Sign up to request clarification or add additional context in comments.

6 Comments

I think your output is inconsistent and unclear. Please clarify and I can easily solve it. Why would id=2 (volkswagen, credit) not be included in output for case 1? I don't understand why you want to exclude that. Also, when there is a duplicate (within 40 seconds, which one is kept - the one which was "earlier" in time?). Please try to explain that and make your question a bit clearer where possible.
When there is only one transaction within specified conditions (in this case Volkswagen does not have similar transaction) it should be excluded from Duplicates array (which by definition should include 2 or more transactions).
All transactions should be kept if the time difference between the last one and following one is less than 45 seconds.
Please also check the entire datestamp. Sometimes the days of the month are different so the object should not be included in an array.
Works fine on case 1, but the same code should also work for case 2 and case 3. Right now it produces same output for all three cases, but in case 2 and 3 should output an empty array.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.