I have an array of objects
list = [{x:1,y:2}, {x:3,y:4}, {x:5,y:6}, {x:1,y:2}]
And I'm looking for an efficient way (if possible O(log(n))) to remove duplicates and to end up with
list = [{x:1,y:2}, {x:3,y:4}, {x:5,y:6}]
I've tried _.uniq or even _.contains but couldn't find a satisfying solution.
Thanks!
Edit : The question has been identified as a duplicate of another one. I saw this question before posting but it didn't answer my question since it's an array of object (and not a 2-dim array, thanks Aaron), or at least the solutions on the other question weren't working in my case.
Plain javascript (ES2015), using Set
const list = [{ x: 1, y: 2 }, { x: 3, y: 4 }, { x: 5, y: 6 }, { x: 1, y: 2 }];
const uniq = new Set(list.map(e => JSON.stringify(e)));
const res = Array.from(uniq).map(e => JSON.parse(e));
document.write(JSON.stringify(res));
Try using the following:
list = list.filter((elem, index, self) => self.findIndex(
(t) => {return (t.x === elem.x && t.y === elem.y)}) === index)
Vanilla JS version:
const list = [{x:1,y:2}, {x:3,y:4}, {x:5,y:6}, {x:1,y:2}];
function dedupe(arr) {
return arr.reduce(function(p, c) {
// create an identifying id from the object values
var id = [c.x, c.y].join('|');
// if the id is not found in the temp array
// add the object to the output array
// and add the key to the temp array
if (p.temp.indexOf(id) === -1) {
p.out.push(c);
p.temp.push(id);
}
return p;
// return the deduped array
}, {
temp: [],
out: []
}).out;
}
console.log(dedupe(list));
I would use a combination of Arrayr.prototype.reduce and Arrayr.prototype.some methods with spread operator.
1. Explicit solution. Based on complete knowledge of the array object contains.
list = list.reduce((r, i) =>
!r.some(j => i.x === j.x && i.y === j.y) ? [...r, i] : r
, [])
Here we have strict limitation on compared objects structure: {x: N, y: M}. And [{x:1, y:2}, {x:1, y:2, z:3}] will be filtered to [{x:1, y:2}].
2. Generic solution, JSON.stringify(). The compared objects could have any number of any properties.
list = list.reduce((r, i) =>
!r.some(j => JSON.stringify(i) === JSON.stringify(j)) ? [...r, i] : r
, [])
This approach has a limitation on properties order, so [{x:1, y:2}, {y:2, x:1}] won't be filtered.
3. Generic solution, Object.keys(). The order doesn't matter.
list = list.reduce((r, i) =>
!r.some(j => !Object.keys(i).some(k => i[k] !== j[k])) ? [...r, i] : r
, [])
This approach has another limitation: compared objects must have the same list of keys.
So [{x:1, y:2}, {x:1}] would be filtered despite the obvious difference.
4. Generic solution, Object.keys() + .length.
list = list.reduce((r, i) =>
!r.some(j => Object.keys(i).length === Object.keys(j).length
&& !Object.keys(i).some(k => i[k] !== j[k])) ? [...r, i] : r
, [])
With the last approach objects are being compared by the number of keys, by keys itself and by key values.
I created a Plunker to play with it.
One liners for ES6+
If you want to find uniq by x and y:
arr.filter((v,i,a)=>a.findIndex(t=>(t.x === v.x && t.y===v.y))===i)
If you want to find uniques by all properties:
arr.filter((v,i,a)=>a.findIndex(t=>(JSON.stringify(t) === JSON.stringify(v)))===i)
The following will work:
var a = [{x:1,y:2}, {x:3,y:4}, {x:5,y:6}, {x:1,y:2}];
var b = _.uniq(a, function(v) {
return v.x && v.y;
})
console.log(b); // [ { x: 1, y: 2 }, { x: 3, y: 4 }, { x: 5, y: 6 } ]
Filter the array after checking if already in a temorary object in O(n).
var list = [{ x: 1, y: 2 }, { x: 3, y: 4 }, { x: 5, y: 6 }, { x: 1, y: 2 }],
filtered = function (array) {
var o = {};
return array.filter(function (a) {
var k = a.x + '|' + a.y;
if (!o[k]) {
o[k] = true;
return true;
}
});
}(list);
document.write('<pre>' + JSON.stringify(filtered, 0, 4) + '</pre>');
No libraries, and works with any depth
Limitation:
You must provide only string or Number properties as hash objects otherwise you'll get inconsistent results
/**
* Implementation, you can convert this function to the prototype pattern to allow
* usage like `myArray.unique(...)`
*/
function unique(array, f) {
return Object.values(
array.reduce((acc, item) => ({ ...acc, [f(item).join(``)]: item }), {})
);
}
const list = [{ x: 1, y: 2}, {x: 3, y: 4}, { x: 5, y: 6}, { x: 1, y: 2}];
// Usage
const result = unique(list, item => [item.x, item.y]);
// Output: [{ x: 1, y: 2}, {x: 3, y: 4}, { x: 5, y: 6}]
console.log(result);
Snippet Sample
// Implementation
function unique(array, f) {
return Object.values(
array.reduce((acc, item) => ({ ...acc, [f(item).join(``)]: item }), {})
);
}
// Your object list
const list = [{ x: 1, y: 2}, {x: 3, y: 4}, { x: 5, y: 6}, { x: 1, y: 2}];
// Usage
const result = unique(list, item => [item.x, item.y]);
// Add result to DOM
document.querySelector(`p`).textContent = JSON.stringify(result, null, 2);
<p></p>
With Underscore's _.uniq and the standard JSON.stringify it is a oneliner:
var list = [{x:1,y:2}, {x:3,y:4}, {x:5,y:6}, {x:1,y:2}];
var deduped = _.uniq(list, JSON.stringify);
console.log(deduped);
<script src="https://underscorejs.org/underscore-umd-min.js"></script>
However, this presumes that the keys are always specified in the same order. By sophisticating the iteratee, we can make the solution work even if the order of the keys varies. This problem as well as the solution also apply to other answers that involve JSON.stringify.
var list = [{x:1,y:2}, {x:3,y:4}, {x:5,y:6}, {y:2, x:1}];
// Ensure that objects are always stringified
// with the keys in alphabetical order.
function replacer(key, value) {
if (!_.isObject(value)) return value;
var sortedKeys = _.keys(value).sort();
return _.pick(value, sortedKeys);
}
// Create a modified JSON.stringify that always
// uses the above replacer.
var stringify = _.partial(JSON.stringify, _, replacer, null);
var deduped = _.uniq(list, stringify);
console.log(deduped);
<script src="https://underscorejs.org/underscore-umd-min.js"></script>
For Lodash 4, use _.uniqBy instead of _.uniq.
Using lodash you can use this one-liner:
_.uniqBy(list, e => { return e.x && e.y })
Related
I have a two sets of elements, one holds list of numbers and second of names.
something like this.
A: 4,
B: 3,
C: 2,
A: 5,
C: 3,
And my task is to find elements with smallest value and highest value.
I know that i could create array of objects and sort it with map [{A: 4},{C:2}....]
But i was wondering is there any efficient ways of doing it.
Instead of creating a have object, and use three loops.
Would it be possible to replace it with something more efficient.
Like set or something where i could just call set.getItemWithMinValue, set.getItemWithMaxValue
and return would be : C:2, A:5
Sorry for silly quesition, i am still learning.
You are going to have to loop, parse the object into its values, and check if the value is greater or less.
var data = [
{ A: 4 },
{ B: 3 },
{ C: 2 },
{ A: 5 },
{ C: 3 },
];
const results = data.reduce((minMax, item) => {
const value = Object.values(item)[0];
if (!minMax) {
minMax = {
min: { value, item },
max: { value, item },
}
} else if (minMax.min.value > value) {
minMax.min = { value, item };
} else if (minMax.max.value < value) {
minMax.max = { value, item };
}
return minMax;
}, null);
console.log(results.min.item);
console.log(results.max.item);
This would be one way of doing it. Caution: the array will be changed (sorted) in the course of the script.
const arr=[{A: 4},{B: 3},{C: 2},{A: 5},{C: 3}],
val=o=>Object.values(o)[0];
arr.sort((a,b)=>val(a)-val(b));
console.log(arr[0],arr[arr.length-1])
if you have this data
const arr = [{A:2},{A: 4},{B: 3},{C: 2},{A: 5},{C: 3}];
you can iterate over it even if you don't know the property of the objects like this.รง
const arr = [{A:2},{A: 4},{B: 3},{C: 2},{A: 5},{C: 3}];
const result = arr.sort((prev, next) => {
const prevProp = Object.getOwnPropertyNames(prev);
const nextProp = Object.getOwnPropertyNames(next);
return prev[prevProp] - next[nextProp]
});
console.log('max',result[0]);
console.log('min',result[result.length - 1]);
You could take a single loop with getting the entries from the object.
This approach expects only a singl min and max value.
const
array = [{ A: 4 }, { B: 3 }, { C: 2 }, { A: 5 }, { C: 3 }];
let min, max;
for (const object of array) {
const [[k, v]] = Object.entries(object);
if (!min || min[1] > v) min = [k, v];
if (!max || max[1] < v) max = [k, v];
}
console.log('min', Object.fromEntries([min]));
console.log('max', Object.fromEntries([max]));
This approach respects more than one name with same min or max value.
const
array = [{ A: 4 }, { B: 3 }, { C: 2 }, { A: 5 }, { C: 3 }, { F: 2 }];
let min, max;
for (const object of array) {
const v = Object.values(object)[0];
if (!min || min[1] > v) min = [[object], v];
else if (min[1] === v) min[0].push(object);
if (!max || max[1] < v) max = [[object], v];
else if (max[1] === v) max[0].push(object);
}
console.log('min', min[0]);
console.log('max', max[0]);
This is probably premature optimisation but I'm going to leave it here in case it's useful to anyone. When you need both the minimum and the maximum, you can save one comparison for every two objects (that is, cut it down from two comparisons per object to three comparison per two objects) by taking the objects in pairs, comparing the pair of objects with each other (one comparison) and then comparing only the larger one with the accumulated maximum and only the smaller one with the accumulated minimum.
To start the procedure, you have the options of initialising both the maximum and the minimum with the first element, or of initialising the maximum as the larger of the first two elements and the minimum as the smaller of the two. If you know in advance how many elements there are, you can choose one or the other of these depending on whether the number of objects to scan is odd or even, so that the scan will always be over complete pairs.
The code is slightly more complicated, so it's only worth doing if the performance benefit is actually significant in your application. Or, I suppose, as a learning exercise.
You can use Array.prototype.reduce():
const array = [{ A: 4 }, { B: 3 }, { C: 2 }, { A: 5 }, { C: 3 }]
const result = array.reduce((a, c) => {
const [v, max, min] = [c, a.max, a.min].map((o) => Object.values(o)[0])
a.max = max > v ? a.max : c
a.min = min < v ? a.min : c
return a
},
{ max: {}, min: {} })
console.log(result)
Or you can use Array.prototype.sort():
const array = [{ A: 4 }, { B: 3 }, { C: 2 }, { A: 5 }, { C: 3 }]
const arraySorted = array.sort((a, b) => {
const [aValue, bValue] = [a, b].map((o) => Object.values(o)[0])
return bValue - aValue
})
const result = {
max: arraySorted[0],
min: arraySorted[arraySorted.length - 1],
}
console.log(result)
I have an array like this
let data = [{x:1,y:2,z:3},{x:1,y:2,z:3},{x:1,y:2,z:4},{x:11,y:2,z:3}]
Now I want to get only those items whose x,y,z values are the same.so expected output should be
{x:1,y:2,z:3}
Because {x:1,y:2,z:3} has duplicate values but rest of them not so I don't want to get rest of them because they do not have any duplicates. Please tell me how can I achieve this?
For lodash 4.17.15,
You can first use _.uniqWith and _.isEqual to find the unique values.
_.uniqWith(data, _.isEqual); // [{x:1,y:2,z:3},{x:1,y:2,z:4},{x:11,y:2,z:3}]
Then use _.difference to remove the unique values from the array, leaving just the duplicates
_.difference(data, _.uniqWith(data, _.isEqual)); // [{x:1,y:2,z:3}]
let data = [{x:1,y:2,z:3},{x:1,y:2,z:3},{x:1,y:2,z:4},{x:11,y:2,z:3},{x:11,y:2,z:3}]
function filterDuplicates(data) {
let dic = {};
data.forEach(obj => {
let strObj = JSON.stringify(obj, Object.keys(obj).sort());
if(strObj in dic) {
++dic[strObj];
return;
}
dic[strObj] = 0;
})
return Object.entries(dic).filter(([key, value]) => value > 0).map(([el]) => JSON.parse(el));
}
console.log(filterDuplicates(data));
Build an object to track the duplicates and use Object.values and filter
let data = [
{ x: 1, y: 2, z: 3 },
{ x: 1, y: 2, z: 3 },
{ x: 1, y: 2, z: 4 },
{ x: 11, y: 2, z: 3 },
];
const all = {};
data.forEach(({ x, y, z }) => {
const key = `x${x}y${y}z${z}`;
all[key] = key in all ? { x, y, z } : null;
});
const res = Object.values(all).filter(Boolean);
console.log(res);
Array of dictionaries should be converted simpler form.
data = [{A:1},{B:2},{C:3}]
data = {A: 1, B: 2}
data = ["0":{ A : 1, B : 2 , C : 3}]
Both are completely different datasets. I'm trying to map it also like below format.
The above should become like
data = [
{
name: "A",
y: 1
},
{
name: "B",
y: 2
},
{
name: "C",
y: 3
}
];
I tried this following approach but it's wrong
name = {}
data.forEach(function(k,x){
return name['name'] = k , name["y"] = x
})
Please suggest me a better approach.
map each object's entries to extract the key and the value, and return an object with name and y keys:
const data = [{A:1},{B:2},{C:3}]
const output = data.map(item => {
const [name, y] = Object.entries(item)[0];
return { name, y };
});
console.log(output);
If the keys (A, B, etc) are guaranteed to be unique throughout the array, then everything becomes simpler.
const data = [{A:1},{B:2},{C:3}];
const merged = Object.assign({}, ...data);
const newData = Object.entries(merged)
.map(([name, y]) => ({ name, y }));
console.log(newData);
However, if the keys aren't guaranteed unique, then refer to CertainPerformance's answer.
you can implement like this
var data = [{A:1},{B:2},{C:3}];
var reformattedArra = data.map(obj => {
let val = {};
val.name = Object.keys(obj)[0];
val.y = obj[Object.keys(obj)[0]];
return val;
})
console.log(JSON.stringify(reformattedArra));
I would say, use Object.keys() which is widly supported
let data = [{A:1},{B:2},{C:3}];
data = Object.assign({}, ...data);
data = Object.keys(data).map(key => ({ name: key, y: data[key] }));
console.log(data);
You yould could chekc the data format and if it is not an array, build one and reduce the array by taking the objetcs and create for each key/value a new object for the result set.
function simple(data) {
return (Array.isArray(data) ? data : [data]).reduce((r, o) => [...r, ...Object.entries(o).map(([name, y]) => ({ name, y }))], []);
}
console.log(simple([{ A: 1 }, { B: 2 }, { C: 3, D: 4 }]));
console.log(simple({ A: 1, B: 2 }));
.as-console-wrapper { max-height: 100% !important; top: 0; }
Would like to merge an array of objects resulting in an object of unique keys and array of values (duplication of values is ok). Solutions in vanilla JS or lodash preferred.
eg - from this:
[{
a: 1,
b: 2
}, {
a: 1,
c: 3
}]
to this:
{
a: [1, 1],
b: [2],
c: [3]
}
You can use _.mergeWith() with the spread syntax to combine the objects:
const data = [{"a":1,"b":2},{"a":1,"c":3}];
const result = _.mergeWith({}, ...data, (v1 = [], v2) => [...v1, v2]);
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>
ES6 variant:
const a = [{
a: 1,
b: 2
}, {
a: 1,
c: 3
}]
const b = a.reduce((acc, cur) => Object.assign(acc,
...Object.keys(cur).map(key => ({ [key]: (acc[key] || []).concat(cur[key]) })))
, {})
console.log(b)
without loadash:
var t = [{
a: 1,
b: 2
}, {
a: 1,
c: 3
}];
var result = {};
debugger;
for(var i=0; i<t.length; i++){
for(var j in t[i]){
if(result.hasOwnProperty(j)){
result[j].push(t[i][j]);
}else{
result[j] = [t[i][j]];
}
}
}
console.log(result);
A quick search here in stack reveals that #elclanrs already wrote code for that here However based on the comments, it needs a little tweaking to accept an array of objects, so I added a bit of change to the original code itself.
so basically it boils to the function call:
var merge = function() {
return [].reduce.call(arguments, function(acc, x) {
for(i=0;i<x.length;i++){
Object.keys(x[i]).forEach(function(k) {
acc[k] = (acc[k]||[]).concat([x[i][k]])
});
}
return acc
},{})
}
}
Here's a snippet using the function call (with a bit of small change I put) in that post:
var x = [{a: 1, b: 2}, {a: 1,c: 3}]
var merge = function() {
return [].reduce.call(arguments, function(acc, x) {
for(i=0;i<x.length;i++){
Object.keys(x[i]).forEach(function(k) {
acc[k] = (acc[k]||[]).concat([x[i][k]])
});
}
return acc
},{})
}
y = merge(x);
alert(JSON.stringify(y));
You can use lodash#mergeWith wrapped in a lodash#spread to make lodash#mergeWith treat an array as a list of arguments. We use lodash#concat as a supporting function to concatenate an empty object (to avoid mutating the objects in the collection), the collection, and the customizer function that merges the entire collection. The customizer is composed using lodash#flow, wherein its first argument is lodash#concat that only accepts an arity of 2 using lodash#ary and the second argument uses lodash#compact -- It removes all undefined values in an array.
var result = _.spread(_.mergeWith)(
_.concat({}, data, _.flow(_.ary(_.concat, 2), _.compact))
);
var data = [{
"a": 1,
"b": 2
}, {
"a": 1,
"c": 3
}];
var result = _.spread(_.mergeWith)(
_.concat({}, data, _.flow(_.ary(_.concat, 2), _.compact))
);
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>
So I've recently come across a problem I can't seem to wrap my head around.
Let's say I've defined an array of objects in javascript, and want the user to be able to choose what value to sort that array by.
I have no problem sorting the array when I know the depth, as it would be something along the lines of
array = array.sort(function (a, b) {
return b["foo"]["bar"] - a["foo"]["bar"];
});
but I don't exactly know how to go about doing this when the depth is unknown. I've attempted putting the keys in a string and using eval(), but that does not seem to work.
I've set up a quick example on JSFiddle to better demonstrate what I mean
http://jsfiddle.net/DakotaSv/c35bj02w/2/
If anyone could think of a solution, I'd be grateful!
(Thanks to PatrickD, here is the working JSFiddle for anyone who may find it useful!)
Here is a working solution. It uses ES6-Syntax, but this should not be a problem:
'use strict'
var users = [
{'Name' : 'John', 'Attributes' : {'Age' : 5, 'Height' : 1.5, 'Clothes' : {'Shirts' : 5, 'Pants' : 8}}},
{'Name' : 'Andrew', 'Attributes' : {'Age' : 9, 'Height' : 1.8, 'Clothes' : {'Shirts' : 2, 'Pants' : 5}}},
{'Name' : 'Lucifer', 'Attributes' : {'Age' : 11, 'Height' : 1.3, 'Clothes' : {'Shirts' : 9, 'Pants' : 4}}}
];
function sort(valuePath, array){
let path = valuePath.split('.')
return array.sort((a, b) => {
return getValue(b,path) - getValue(a,path)
});
function getValue(obj, path){
path.forEach(path => obj = obj[path])
return obj;
}
}
console.log(sort('Attributes.Height', users))
console.log(sort('Attributes.Clothes.Shirts', users))
The output is correct.
Maybe this is a solution for variable sorting scheme. The sort attribute is just given, like ['Attributes', 'Height']. This uses the properties Attributes and Height.
It features a temporary storage for faster sorting.
function sort(a, by) {
return a.map(function (el, i) {
return {
index: i,
value: by.reduce(function (obj, property) { return obj[property]; }, el)
};
}).sort(function (a, b) {
return a.value - b.value;
}).map(function (el) {
return a[el.index];
});
}
var users = [{ 'Name': 'John', 'Attributes': { 'Age': 5, 'Height': 1.5, 'Clothes': { 'Shirts': 5, 'Pants': 8 } } }, { 'Name': 'Andrew', 'Attributes': { 'Age': 9, 'Height': 1.8, 'Clothes': { 'Shirts': 2, 'Pants': 5 } } }, { 'Name': 'Lucifer', 'Attributes': { 'Age': 11, 'Height': 1.3, 'Clothes': { 'Shirts': 9, 'Pants': 4 } } }];
document.write('<pre>' + JSON.stringify(sort(users, ['Attributes', 'Height']), 0, 4) + '</pre>');
document.write('<pre>' + JSON.stringify(sort(users, ['Attributes', 'Clothes', 'Shirts']), 0, 4) + '</pre>');
If I understand your question correctly, you want the user to chose which attribute to sort the array by.
Looking at your fiddle I think that what you need is accessing an attribute specified by the user, fortunately it's possible to specify a variable inside the brackets. Something like:
var obj = {name: 'john'}
var attr = "name";
console.log(obj[attr]); // prints 'john'
Here's your modified fiddle: https://jsfiddle.net/9s5bnfh5/1/
You would need:
a way to represent how to access the sort key given an object
a function that, given a sort key representation and an object, queries the object and produces the key
The manner of representation can be arbitrarily selected, so let's say we decide to encode the access someObject.foo.bar as the string "foo.bar". Then the key producing function would be (adapted from my answer here):
function produceKey(target, path) {
var parts = path.split('.');
while(parts.length) {
var branch = parts.shift();
if (typeof target[branch] === 'undefined') {
return undefined;
}
target = target[branch];
}
return target;
}
which you could then use as:
function produceKey(target, path) {
var parts = path.split('.');
while(parts.length) {
var branch = parts.shift();
if (typeof target[branch] === 'undefined') {
return undefined;
}
target = target[branch];
}
return target;
}
var obj = { foo: { bar: 1, baz: 2 }, arr: [1, 2, 3] };
$(function() {
$("#trigger").click(function() {
$(".result").text(JSON.stringify(produceKey(obj, $("input").val())));
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
Targe object:
<pre>
{ foo: { bar: 1, baz: 2 }, arr: [1, 2, 3] }
</pre>
Property path: <input name="path" />
<button id="trigger">Produce value</button>
<div class="result"></div>
The cleanest way of doing this is passing a callback function to your sort algorithm that is executed to retrieve the value to compare on from the object:
function cbSort(toSort, callback)
{
return toSort.sort(function(a, b)
{
return callback(a) - callback(b);
}
}
// usage
var data = [ { x: 1, y: 2 }, {x: 3, y: 1}];
var sorted = cbSort(data, function(item) {
return item.x; // make this as complex as you like
});