Javascript - Counting duplicates in array of object with many fields in es6 - javascript

I have object array like this.
const array = [ { x: 1, y: 2 }, { x: 3, y: 4 }, { x: 1, y: 2 }, { x: 3, y: 4 }, { x: 1, y: 2 }, { x: 3, y: 12 } ]
I want to count duplicates objects and store the count as new object field.
I found this snippet and it work great but it not exactly what i need.
const names = [{ _id: 1 }, { _id: 1}, { _id: 2}, { _id: 1}]
const result = [...names.reduce( (mp, o) => {
if (!mp.has(o._id)) mp.set(o._id, Object.assign({ count: 0 }, o));
mp.get(o._id).count++;
return mp;
}, new Map).values()];
console.log(result);
It works with object with one field _id. In my case there are two, x and y
How should I modify that code?
In brief...I would like to receive the result:
result = [ { x: 1, y: 2, count:3 }, { x: 3, y: 4, count:2 }, { x: 3, y: 12, count:1 } ]

You can use Object.values() and reduce() methods to return new array of objects.
const array = [ { x: 1, y: 2 }, { x: 3, y: 4 }, { x: 1, y: 2 }, { x: 3, y: 4 }, { x: 1, y: 2 }, { x: 3, y: 12 } ]
const result = Object.values(array.reduce((r, e) => {
let k = `${e.x}|${e.y}`;
if(!r[k]) r[k] = {...e, count: 1}
else r[k].count += 1;
return r;
}, {}))
console.log(result)
Here is the solution with Map and spread syntax ...
const array = [ { x: 1, y: 2 }, { x: 3, y: 4 }, { x: 1, y: 2 }, { x: 3, y: 4 }, { x: 1, y: 2 }, { x: 3, y: 12 } ]
const result = [...array.reduce((r, e) => {
let k = `${e.x}|${e.y}`;
if(!r.has(k)) r.set(k, {...e, count: 1})
else r.get(k).count++
return r;
}, new Map).values()]
console.log(result)

One way to do it would be to create an index mapping both x and y to the result entry:
let index = { };
let result = [ ];
const array = [ { x: 1, y: 2 }, { x: 3, y: 4 }, { x: 1, y: 2 }, { x: 3, y: 4 }, { x: 1, y: 2 }, { x: 3, y: 12 } ];
array.forEach(point => {
let key = '' + point.x + '||' + point.y;
if (key in index) {
index[key].count++;
} else {
let newEntry = { x: point.x, y: point.y, count: 1 };
index[key] = newEntry;
result.push(newEntry);
}
});
console.log(result);

Related

How to count duplicate object in JS

im trying to add counting number for duplicate in JS.
and i am completely stack in this case below.
i need to compare objects with two value (x, y) and if there are same values of (x, y) add count 1 on new objects.
is there any way to convert data to newData such as below?
const data = [
{id: 1, x: 1, y: 1},
{id: 2, x: 2, y: 2},
{id: 3, x: 1, y: 1},
]
const newData = [
{x: 1, y:1 ,count:2}
{x: 2, y:2 ,count:1}
]
use .reduce() function
const data = [
{id: 1, x: 1, y: 1},
{id: 2, x: 2, y: 2},
{id: 3, x: 1, y: 1},
]
const output = data.reduce((acc, curr) => {
curr.count = 1;
const exists = acc.find(o => o.x === curr.x && o.y === curr.y);
exists ? exists.count++ : acc.push(({ x, y, count } = curr));
return acc;
}, []);
console.log(output);
.as-console-wrapper { max-height: 100% !important; top: 0; }
One way of doing so, is to create a map with the x and y values, and increment the count accordingly, then convert the map into an array:
const data = [
{id: 1, x: 1, y: 1},
{id: 2, x: 2, y: 2},
{id: 3, x: 1, y: 1},
]
const makeXYMap = (data) => data.reduce((acc, cur) => {
const { x, y } = cur;
const entry = acc[`${x}_${y}`];
if (entry) {
acc[`${x}_${y}`] = {...entry, count: entry.count + 1};
} else {
acc[`${x}_${y}`] = { x, y, count: 1 };
}
return acc;
}, {});
const makeArray = (XYMap) => Object.values(XYMap);
console.log(makeArray(makeXYMap(data)));
Note that complexity wise, this solution is a O(N).
https://jsfiddle.net/9o35neg7/
const data = [
{ id: 1, x: 1, y: 1 },
{ id: 2, x: 2, y: 2 },
{ id: 3, x: 1, y: 1 },
// .. so on ..
];
const countedData = data.reduce((acc, { x, y }, index, array) => {
acc[`x${x}y${y}`] = {
x,
y,
count: (acc[`x${x}y${y}`] ? acc[`x${x}y${y}`].count : 0) + 1
};
return index === (array.length - 1) ? Object.values(acc) : acc;
}, {});
console.log(countedData);
Use forEach and build an object with key (made of x, y) and values (aggregate count). Get the Object.values to get the results as array.
const data = [
{id: 1, x: 1, y: 1},
{id: 2, x: 2, y: 2},
{id: 3, x: 1, y: 1},
]
const counts = (arr, res = {}) => {
arr.forEach(({x , y}) =>
res[`${x}-${y}`] = { x, y, count: (res[`${x}-${y}`]?.count ?? 0) + 1 })
return Object.values(res);
}
console.log(counts(data))

get duplicates in array of objects

I have array of objects.
let coordinates = [
{ x: 8, y: 1 },
{ x: 8, y: 3 },
{ x: 6, y: 5 },
{ x: 4, y: 6 },
{ x: 3, y: 7 },
{ x: 6, y: 5 },
{ x: 3, y: 3 },
{ x: 1, y: 4 },
{ x: 3, y: 3 }
]
I stumbled upon this blog and this stackoverflow question but it only lets me find duplicates based on one property but I want to get the duplicate objects based on x and y properties like this:
[
{ x: 6, y: 5 },
{ x: 3, y: 3 }
]
We apply filter to check the uniquness of index, say if element is duplicate it will count the initial index and the current index will be filtered out.
var coordinates = [ { x: 8, y: 1 }, { x: 6, y: 5 }, { x: 4, y: 6 }, { x: 3, y: 7 }, { x: 6, y: 5 }, { x: 3, y: 3 }, { x: 1, y: 4 }, { x: 3, y: 3 }];
var result = coordinates.filter((val, i, self)=>self.findIndex(k=>k.x==val.x && k.y == val.y)!=i);
console.log(result)
UPDATE
coordinates = [ { x: 6, y: 5 }, { x: 6, y: 5 }, { x: 6, y: 5 },{ x: 4, y: 6 }, { x: 3, y: 7 }, { x: 6, y: 5 }, { x: 3, y: 3 }, { x: 3, y: 3 }, { x: 1, y: 4 }, { x: 3, y: 3 },{ x: 6, y: 5 }];
result = coordinates.reduce((acc, elem)=>{
key = Object.values(elem).join('|');
acc.unique[key] = acc.unique[key] || [];
acc.unique[key].length >0 ? acc.duplicate[key] = elem : acc.unique[key].push(elem);
return acc;
},{unique:{},duplicate:{}});
duplicate = Object.values(result.duplicate);
unique = Object.values(result.unique);
console.log(duplicate);
console.log(unique);
All the answers are not fully correct, because they don't apply to arrays with more than 2 duplicates of the same value, i.e.:
var coordinates = [
{ x: 8, y: 1 },
{ x: 8, y: 1 },
{ x: 8, y: 1 }
]
I used JSON.stringify() and Set structure to get unique duplicates from an array. And for the output I parsed strings back to the objects.
var coordinates = [
{ x: 8, y: 1 },
{ x: 6, y: 5 },
{ x: 4, y: 6 },
{ x: 3, y: 7 },
{ x: 6, y: 5 },
{ x: 3, y: 3 },
{ x: 1, y: 4 },
{ x: 3, y: 3 },
{ x: 3, y: 3 },
]
const duplicates = new Set();
const reducer = (set, val, index, arr) => arr.findIndex(k => k.x == val.x && k.y == val.y) != index ? set.add(JSON.stringify(val)) : set
coordinates.reduce(reducer, duplicates)
console.log(Array.from(duplicates).map(el => JSON.parse(el)))
You can use reduce and another array. Inside reduce callback use x & y to create an object key and check if that key exist in accumulator object. If it exist then push tyhe value to the dupArray
let coordinates = [
{ x: 8, y: 1 },
{ x: 8, y: 3 },
{ x: 6, y: 5 },
{ x: 4, y: 6 },
{ x: 3, y: 7 },
{ x: 6, y: 5 },
{ x: 3, y: 3 },
{ x: 1, y: 4 },
{ x: 3, y: 3 }
];
let dupArray = [];
let dups = coordinates.reduce((acc, curr) => {
const crtKey = '' + curr.x + '' + curr.y;
if (!acc[crtKey]) {
acc[crtKey] = 1;
} else {
dupArray.push(curr);
}
return acc;
}, {});
console.log(dupArray)
this filters only the duplicate array Objects by iterating all the Array items and for each item, iterating the Array again, using some to check if that specific item was anywhere in the array (arr) in a location before the current one (idx1) and also comparing the current item with the checked items in the some iterator by casting both to "string"
let coordinates = [
{ x: 8, y: 1 },
{ x: 8, y: 3 },
{ x: 6, y: 5 },
{ x: 4, y: 6 },
{ x: 3, y: 7 },
{ x: 6, y: 5 },
{ x: 3, y: 3 },
{ x: 1, y: 4 },
{ x: 3, y: 3 }
]
const dups = coordinates.filter(({x, y}, idx1, arr) =>
arr.some(({x:x2,y:y2}, idx2) => idx2 < idx1 && ""+x+y == ""+x2+y2 )
)
console.log( dups )
To make the check more robust, by allowing comparison of any key, what-so-ever JSON.stringify can be used to compare the arrays (assuming all array items are Objects:
const dups = coordinates.filter((item, idx1, arr) =>
arr.some((item2, idx2) => idx2 < idx1 && JSON.stringify(item) == JSON.stringify(item2))

How to update values not present in one array of objects from another array of objects?

I have two arrays of objects which contain a huge amount of data.
The structure of these two arrays goes something like this.
arr1 = [
{x: 1, y: '2018-01-01'},
{x: 2, y: '2018-01-02'},
{x: 3, y: '2018-01-03'},
{x: 5, y: '2018-01-05'},
....
]
arr2 = [
{x: 1, y: '2018-01-01'},
{x: 2, y: '2018-01-02'},
{x: 3, y: '2018-01-03'},
{x: 4, y: '2018-01-04'},
{x: 5, y: '2018-01-05'},
{x: 6, y: '2018-01-08'}
]
I want to update arr2 in such a way that it updates the array of objects with values that are only present in arr1 and drop any values not present in arr1. Note, I want to update the original arr2 and not return a new array.
I tried iterating through individual arrays and remove values not present but not luck.
You could get a map and iterate from the end for splicing unknown items or update changed values.
var arr1 = [{ x: 1, y: '2018-01-01x' }, { x: 2, y: '2018-01-02' }, { x: 3, y: '2018-01-03' }, { x: 5, y: '2018-01-05' }],
arr2 = [{ x: 1, y: '2018-01-01' }, { x: 2, y: '2018-01-02' }, { x: 3, y: '2018-01-03' }, { x: 4, y: '2018-01-04' }, { x: 5, y: '2018-01-05' }, { x: 6, y: '2018-01-08' }],
map = arr1.reduce((m, { x, y }) => m.set(x, y), new Map),
i = arr2.length;
while (i--) {
if (map.has(arr2[i].x)) {
if (map.get(arr2[i].x) !== arr2[i].y) {
arr2[i].y = map.get(arr2[i].x);
}
} else {
arr2.splice(i, 1);
}
}
console.log(arr2);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Get every fourth of array and check between

I have array like this:
let arr = [
{ x: 31, y: 8 }, // get 1
{ x: 32, y: 8, monster: { is: true, id: '19216' } }, // get special
{ x: 32, y: 9 },
{ x: 32, y: 10 },
{ x: 32, y: 11 }, // get 4
{ x: 32, y: 12 },
{ x: 32, y: 13 },
{ x: 32, y: 14 },
{ x: 32, y: 15 }, // get 8
{ x: 32, y: 16 } // get last
];
what I want to achieve is to get every fourth, get special one (the one with monster object) and also last one. So the output would be
[
{x: 31, y: 8},
{x: 32, y: 8, monster: { is: true, id: '19216' } },
{x: 32, y: 11},
{x: 32, y: 15},
{x: 32, y: 16}
]
It was easy to get every fourth and last one like this:
let arrThinned = [];
for (let i = 0; i < arr.length; i = i + 4) {
arrThinned.push({
x: arr[i].x,
y: arr[i].y,
});
}
if((arr.length - 1) % 4 !== 0) {
/* add also last one if not already added */
arrThinned.push({
x: arr[arr.length - 1].x,
y: arr[arr.length - 1].y
});
};
but I can't figure out how to additionally check if there is this special one beetwen every fourth and also add it to thinnedArr array. I need to keep the order. Demo of above code.
Here, use filter
let arr = [
{ x: 31, y: 8 }, // get 1
{ x: 32, y: 8, monster: { is: true, id: '19216' } }, // get special
{ x: 32, y: 9 },
{ x: 32, y: 10 },
{ x: 32, y: 11 }, // get 4
{ x: 32, y: 12 },
{ x: 32, y: 13 },
{ x: 32, y: 14 },
{ x: 32, y: 15 }, // get 8
{ x: 32, y: 16 } // get last
];
let newArr = arr.filter((e,i,ar) => (i%4 === 0 || e.monster || i===ar.length-1));
console.log(newArr);
Used .flatMap() to sort out all required objects. A custom function based on this answer counts how many keys each object had so that if an object had more than 2 keys it was considered special.The version in demo is streamlined into one line.
/** objSize(object)
Utility that returns a given Object's number of non-enumerated property keys it
has (String and Symbol).
*/
const objSize = obj => {
let strings = Object.getOwnPropertyNames(obj).length;
let symbols = Object.getOwnPropertySymbols(obj).length;
return strings + symbols;
}
Further details about .flatMap() are commented in demo.
let array = [
{ x: 31, y: 8 }, // get 1
{ x: 32, y: 8, z: { is: true, id: '19216' } },
{ x: 32, y: 9 },
{ x: 32, y: 10 },
{ x: 32, y: 11 }, // get 4
{ x: 32, y: 12 },
{ x: 32, y: 13 },
{ x: 32, y: 14 },
{ x: 32, y: 15 }, // get 8
{ x: 32, y: 16 } // get last
];
const objSize = obj => Object.getOwnPropertyNames(obj).length + Object.getOwnPropertySymbols(obj).length;
/*
.flatMap() is an array method that is basically a
combonation of `.map()` and `.flat()`. Here it is running
a function of 4 ternary controls:
1. if current index is 0:
(idx === 0)
return [xy]
2. if current index is a factor of 4:
(idx % 4 === 0)
return [xy]
3. if current xy has more than 2 keys:
(objSize(xy) > 2)
return [xy]
4. if current index is the last:
(idx === array.length - 1)
return [xy]
5. otherwise return []
each return is an array which is flattened when the final
array is returned. Therefore an empty array is a clean
removal which means no nulls, spaces, or empty values.
*/
let result = array.flatMap((xy, idx) => xy === 0 ? [xy] : idx % 4 === 0 ? [xy] : objSize(xy) > 2 ? [xy] : idx === array.length - 1 ? [xy] : []);
console.log(JSON.stringify(result));

two array input into object in nodejs

I know this has been asked before but I can't seem to find the answer, i want to get no_anggota, nama_lahir and tgl_lahir from array isiuser, and i want to insert to ZZZ .
isiseat = [
[{ x: 0, y: 0, val: 1 },
{ x: 1, y: 0, val: 1 },
{ x: 2, y: 0, val: 1 }]
[{ x: 0, y: 1, val: 1 },
{ x: 1, y: 1, val: 1 },
{ x: 2, y: 1, val: 1 }]
[{ x: 0, y: 2, val: 1 },
{ x: 1, y: 2, val: 1 },
{ x: 2, y: 2, val: 1 }]
];
isiuser = [ { no_anggota: '12345', nama_lahir: ' Agus dwi', tgl_lahir: '04-09-2018'},
{ no_anggota: '12345', nama_lahir: 'Septano', tgl_lahir: '15-08-2018'},
{ no_anggota: '12345', nama_lahir: 'Septian putra', tgl_lahir: '15-08-2018'}
];
isiseat.forEach((item1) => {
item1.forEach((item2) => {
var detaildata = {};
detaildata.x = item2.x
detaildata.y = item2.y
detaildata.val = item2.val
detaildata.no_anggota = 'ZZZ'
detaildata.nama_lahir = 'ZZZ'
detaildata.tgl_lahir = 'ZZZ'
detaildata.jadwal = req.body.jadwal
detaildata.section = req.body.section
var savedata = new DetailBooking(detaildata);
savedata.save()
})
});
You can use bulk insert bulk insert
var dataArray = [];
var isiseat = [
[
{ x: 0, y: 0, val: 1 },
{ x: 1, y: 0, val: 1 },
{ x: 2, y: 0, val: 1 }
],
[
{ x: 0, y: 1, val: 1 },
{ x: 1, y: 1, val: 1 },
{ x: 2, y: 1, val: 1 }
],
[
{ x: 0, y: 2, val: 1 },
{ x: 1, y: 2, val: 1 },
{ x: 2, y: 2, val: 1 }
]
]
var bulk = db.items.initializeUnorderedBulkOp();
isiseat.forEach((item1) => {
item1.forEach((item2) => {
var detaildata = {};
detaildata.x = item2.x
detaildata.y = item2.y
detaildata.val = item2.val
detaildata.no_anggota = 'ZZZ'
detaildata.nama_lahir = 'ZZZ'
detaildata.tgl_lahir = 'ZZZ'
detaildata.jadwal = 'sssss'
detaildata.section = 'sssssss'
bulk.insert(detaildata);
})
});
bulk.execute();
my saved data look like this
{
"_id" : ObjectId("5b61aab063727f69dc5ad1b8"),
"x" : 0.0,
"y" : 0.0,
"val" : 1.0,
"no_anggota" : "ZZZ",
"nama_lahir" : "ZZZ",
"tgl_lahir" : "ZZZ",
"jadwal" : "sssss",
"section" : "sssssss"
}
{
"_id" : ObjectId("5b61aab063727f69dc5ad1b9"),
"x" : 1.0,
"y" : 0.0,
"val" : 1.0,
"no_anggota" : "ZZZ",
"nama_lahir" : "ZZZ",
"tgl_lahir" : "ZZZ",
"jadwal" : "sssss",
"section" : "sssssss"
}
If both isiseat and isiuser arrays are of the same length, you can pass the index parameter to the forEach function. (See "Loop through an two arrays simultaneously")
Therefore you could pass the index to your first forEach function:
// Index passed here
isiseat.forEach((item1, index) => {
item1.forEach((item2) => {
var detaildata = {
x: item2.x,
y: item2.y,
val: item2.val,
// ...And then access the `isiuser` array accordingly
no_anggota: isiuser[index].no_anggota,
nama_lahir: isiuser[index].nama_lahir,
tgl_lahir: isiuser[index].tgl_lahir,
jadwal: req.body.jadwal,
section: req.body.section
};
// console.log to see results
console.log(detaildata)
var savedata = new DetailBooking(detaildata);
savedata.save()
})
});

Categories

Resources