Reputation: 300
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.
i need to Find all transactions that have the same sourceAccount, targetAccount, category, amount, and the time difference between each consecutive transaction is less than 1 minute.
I'd been trying looping the array and using map to create a new array, but i don't know how to match the array without providing a reference for the value, due the values in the array are dynamical i can't know the value.
var transac = [
{
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'
}
];
expected:
[
[
{
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"
}
]
];
Upvotes: 7
Views: 1304
Reputation: 350270
I would create a composed key for the key fields and translate the time to a number of milliseconds and then sort by those two elements. In a second step group those entries that have the same key and are at most 1 minute apart:
var transac = [{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'}];
const result = transac.map(t => ({
key: JSON.stringify([t.sourceAccount, t.targetAccount, t.amount, t.category]),
epoch: Date.parse(t.time),
t
})).sort((a,b) =>
a.key.localeCompare(b.key) || a.epoch - b.epoch || a.t.id - b.t.id
).reduce(([acc, prev], curr) => {
if (!prev || curr.key != prev.key || curr.epoch - prev.epoch > 60000) acc.push([]);
acc[acc.length-1].push(curr.t);
return [acc, curr];
}, [[]])[0];
console.log(result);
Following up on a comment below, the above result includes all transactions. The ones that have "duplicates" (according to the definition in the question) are grouped together with their duplicates in sub arrays; those that do not have such duplicates appear alone in their own sub arrays.
So to only get the duplicates, add the appropriate filter:
const duplicates = result.filter(a => a.length > 1);
Upvotes: 7
Reputation: 18619
You can use something like this:
var found=false, output=[], transac = [ { 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' } ];
for(i=0;i<transac.length;i++){
j_for:for(j=0;j<output.length;j++){
for(k=0;k<output[j].length;k++){
if(transac[i].sourceAccount==output[j][k].sourceAccount&&transac[i].targetAccount==output[j][k].targetAccount&&transac[i].category==output[j][k].category&&transac[i].amount==output[j][k].amount&&new Date(transac[i].time).getTime()+60000>new Date(output[j][k].time).getTime()&&new Date(transac[i].time).getTime()-60000<new Date(output[j][k].time).getTime()){output[j].push(transac[i]);found=true;break j_for}
}
}
if(!found){output.push([transac[i]])}
found=false
}
console.log(output)
I hope that this will help you!
Upvotes: 1
Reputation: 305
Solution 1 Solution 1 naive approach - Data is sorted by date and all duplicates are one after the other.
var first = reducedTransac.shift();
if (!first) {
return [];
}
var reducedTransac = transac.reduce(function(approvedTransac, currentTrans) {
var lastTrans = approvedTransac[approvedTransac.length - 1];
//You'll need to write timeDiff yourself :-)
var isTimeDiffLessThanSecond = timeDiff(lastTrans.date, currentTrans.time) >= 1;
// Also, this can be done in many other ways, I've taken the check out of the if for code clarity, if performance is important move them inside the if...
var isSameSourceAccount = lastTrans.sourceAccount === currentTrans.sourceAccount;
var isSameTargetAccount = lastTrans.targetAccount === currentTrans.targetAccount;
var isSameCategory = lastTrans.category === currentTrans.category;
var isSameAmount = lastTrans.amount === currentTrans.amount;
if (isTimeDiffLessThanSecond && isSameSourceAccount && isSameTargetAccount && isSameCategory && isSameAmount) {
return approvedTransac;
}
approvedTransac.push(currentTrans);
return approvedTransac;
}, [first]);
Solution 2 No knowledge of input order
var first = reducedTransac.shift();
if(!first) {
return [];
}
var transacByTimeGroupedBy = transac.reduce(function(transGroupedByTime, currentTrans) {
var lastTransTimeGB = approvedTransac[approvedTransac.length - 1];
var lastTrans = lastTransTimeGB[lastTransTimeGB.length - 1];
//You'll need to write timeDiff yourself :-)
var isTimeDiffLessThanSecond = timeDiff(lastTrans.date, currentTrans.time) >= 1;
if(isTimeDiffLessThanSecond) {
approvedTransac[approvedTransac.length - 1].push(lastTrans);
} else {
approvedTransac.concat([lastTrans])
}
return approvedTransac;
}, [[first]]);
transacByTimeGroupedBy.map(function(transactions){
var first = transactions.shift();
if(!first) {
return [];
}
return transactions.reduce(function(approvedTransac, currentTrans) {
var lastTrans = approvedTransac[approvedTransac.length - 1];
var isSameSourceAccount = lastTrans.sourceAccount === currentTrans.sourceAccount;
var isSameTargetAccount = lastTrans.targetAccount === currentTrans.targetAccount;
var isSameCategory = lastTrans.category === currentTrans.category;
var isSameAmount = lastTrans.amount === currentTrans.amount;
if(isSameSourceAccount && isSameTargetAccount && isSameCategory && isSameAmount) {
return approvedTransac;
}
approvedTransac.push(currentTrans);
return approvedTransac;
}, [first]);
})
Then you just need to flatten the last array.
The code is not tested by should be very close to what you need.
Upvotes: 1