I have an array of object literals like this:
var myArr = [];
myArr[0] = {
'score': 4,
'name': 'foo'
}
myArr[1] = {
'score': 1,
'name': 'bar'
}
myArr[2] = {
'score': 3,
'name': 'foobar'
}
How would I sort the array so it ascends by the 'score' parameter such that it would change to:
myArr[0] = {
'score': 1,
'name': 'bar'
}
myArr[1] = {
'score': 3,
'name': 'foobar'
}
myArr[2] = {
'score': 4,
'name': 'foo'
}
Thanks in advance.
Try myArr.sort(function (a, b) {return a.score - b.score});
The way the array elements are sorted depends on what number the function passed in returns:
< 0 (negative number): a goes ahead of b
> 0 (positive number): b goes ahead of a
0: In this cases the two numbers will be adjacent in the sorted list. However, the sort is not guaranteed to be stable: the order of a and b relative to each other may change.
You could have a look at the Array.sort documentation on MDN. Specifically at the documentation about providing a custom compareFunction
const myArray = [
{
'score': 4,
'name': 'foo'
},{
'score': 1,
'name': 'bar'
},{
'score': 3,
'name': 'foobar'
}
]
const myOrderedArray = _.sortBy(myArray, o => o.name);
console.log(myOrderedArray);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.5/lodash.js"></script>
lodash sortBy
Related
I have an array of objects.
For each object, there's one key I need to use to search in an set of arrays of similar objects to see if they have a value for one key in my object, and if all those similar arrays have the SAME value, I want to set it as the value for the object wit
Is there a better way to find if all the searchable arrays. There's gotta be some awesome, elegant lodash function...right?
myValues[] = [{'id': 7, 'score': null}, {'id': 3, 'score': null}];
const searchme[] = /* data fetch*/;
// sample searchme[] =[
// [{'id': 1, 'score': 1}, {'id': 2, 'score': 1}, {'id': 3, 'score': 1}],
// [{'id': 1, 'score': 2}, {'id': 2, 'score': 1}, {'id': 4, 'score': 3}],
// [{'id': 1, 'score': 1}, {'id': 2, 'score': 1}, {'id': 3, 'score': 1}],
// ];
// searchme[] contains 1-5 arrays like myValues[];
// so searchme[0] = [{'id': 2, 'score': 2}, {'id': 7, 'score': 2}], and so on
// theoretically, each searchme[] array has all the ids all the others do
// - but a few might be missing
myValues.forEach(v => {
let score: number = null;
let started: boolean = false;
let fail: boolean = false;
searchme.forEach( anArray => {
found = anArray.find( search => search.id = v.id);
if (found) {
if (!started) { started = true; score = found.score; }
else { if (score != found.score) { fail = true; }
} else {
fail = true;
}
});
if (!fail) { v.score = score; }
});
This does work, but it seems super inefficient. Whaddaya think? Got some lodash chops to slap me with? :)
One idea is to flatten the searchme array, index it by ids.
(groupBy produces an object, keyed by the property name whose values are arrays of original objects with that key).
Not sure how to handle instances with the same id and different scores. For this, uniq() forces uniqueness on a given property.
let searchme = [
[{
'id': 1,
'score': 1
}, {
'id': 2,
'score': 1
}, {
'id': 3,
'score': 1
}],
[{
'id': 1,
'score': 2
}, {
'id': 2,
'score': 1
}, {
'id': 4,
'score': 3
}],
[{
'id': 1,
'score': 1
}, {
'id': 2,
'score': 1
}, {
'id': 3,
'score': 1
}],
];
let grouped = _.groupBy(_.flatten(searchme), 'id')
// now grouped is:
// { 1: [ {...} ], // the one object with id==1
// 2: [ {...}, {...} ], // the two objects with id==2
// etc
function scoresForId(id) {
let matches = grouped[id]
return _.uniq(matches, 'score')
}
console.log(scoresForId(2))
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.9.1/underscore-min.js"></script>
I got and object of objects like this:
var obj = {
0:{id: 1, name: 'one'},
1:{id: 2, name: 'two'},
2:{id: 3, name: 'three'},
3:{id: 4, name: 'four'}
};
I need to move an item under the key 1 from its current position to position 4 (where an item with id: 4 is), so it should look like the following:
var obj = {
0:{id: 1, name: 'one'},
1:{id: 3, name: 'three'},
2:{id: 4, name: 'four'},
3:{id: 2, name: 'two'},
};
The problem is that it is an object of objects, not an array. If it were an array I could do it with the help of the following function:
function array_move(arr, old_index, new_index) {
if (new_index >= arr.length) {
var k = new_index - arr.length + 1;
while (k--) {
arr.push(undefined);
}
}
arr.splice(new_index, 0, arr.splice(old_index, 1)[0]);
};
But in this case I get the following error:
Uncaught TypeError: arr.splice is not a function
Any ideas how to fix it would be welcome. Thank you.
Create an array of keys and values and then order them:
var keys = Object.keys(obj);
var values = Object.values(obj);
var newObj = {};
values = array_move(values);
keys.forEach(function(el, i){
newObj[el] = values[i];
});
Might be something super simple that I am over looking.
I have an array of objects that contains some info. Based on user action, I'd like to filter out some objects;
let arr = [
{'num': 1, 'name': 'joe'},
{'num': 2, 'name': 'jane'},
{'num': 3, 'name': 'john'},
{'num': 3, 'name': 'johnny'},
]
what I've tried
function filterArr(arr) {
return arr.filter(obj => {
if(obj.num && obj.num !== 1) {
return obj;
}
if(obj.num && obj.num !== 3) {
return obj;
}
})
}
when i run filterArr, I am returned back the original array, but the expected result should be just {'num': 2, 'name': 'jane'},,. I can console.log inside both of the nested condition.
Any help of filtering array based on 1 of 2 conditions being true would be appreciated.
Every single num will pass either of your if conditions because there isn't a number in the universe which is equal to 1 and 3 at the same time**. You can just chain the && condition inside the filter to check for all of them:
let arr = [
{'num': 1, 'name': 'joe'},
{'num': 2, 'name': 'jane'},
{'num': 3, 'name': 'john'},
{'num': 3, 'name': 'johnny'},
]
let filtered = arr.filter(a => a.num && a.num !== 3 && a.num !== 1)
console.log(filtered)
(**Unless it's 0, null, undefined or any of the falsy values of course. In which case they will fail the obj.num condition of both ifs)
Let's understand the problem first.
so you had two conditions one checks for not equal to 1 and other checks for not equal to 3 so for example if num = 1 first one fails but second one still pass so eventually you're returning true in every case.
You need to check both the condition in single if statement.
let arr = [{'num': 1, 'name': 'joe'},{'num': 2, 'name': 'jane'},{'num': 3, 'name': 'john'},{'num': 3, 'name': 'johnny'},]
function filterArr(arr) {
return arr.filter(obj => obj.num !== 1 && obj.num!=3)
}
console.log(filterArr(arr))
Assume we have the following arrays of objects to be compared based on property id:
a = [{'id':'1', 'name':'a1'}, {'id':'2', 'name':'a2'}, {'id':'3', 'name':'a3'}]
and
b = [[{'id':'2', 'name':'a2'}, ]
How can I subtract b from a? So that we have c = a - b which should be equal to [ {'id':'1', 'name':'a1'}, {'id':'3', 'name':'a3'}].
I have tried using this:
var c= a.filter(function(item) {
return !b.includes(item.id);
});
but still not working.
How about this solution? It assumes that 'b' is also an array so for each element of 'a' you check if there is a matching object in 'b'. If there is a matching object then return a false in the filter function so that that element is discarded.
var a = [{
'id': '1',
'name': 'a1'
}, {
'id': '2',
'name': 'a2'
}, {
'id': '3',
'name': 'a3'
}]
var b = [{
'id': '2',
'name': 'a2'
}]
var c = a.filter(function(objFromA) {
return !b.find(function(objFromB) {
return objFromA.id === objFromB.id
})
})
console.log(c)
Here is a nice one line answer :)
Basically, you can filter, as you were trying to do already. Then you can also filter b for each a element and if the length of the filtered b is zero, then you return true because that means the a element is unique to a.
var a = [{
'id': '1',
'name': 'a1'
}, {
'id': '2',
'name': 'a2'
}, {
'id': '3',
'name': 'a3'
}];
var b = [{
'id': '2',
'name': 'a2'
}];
c = a.filter( x => !b.filter( y => y.id === x.id).length);
console.log(c);
Easy with new ES6 Syntax
Second and Third way are more performant i guess....
a.filter(i => !b.filter(y => y.id === i.id).length); // One Way
a.filter(i => !b.find(f => f.id === i.id)); // Second Way
a.filter(i => b.findIndex(f => f.id === i.id)) // Third Way
First, you build just a map of the ids you want to delete.
Then, you filter your first array with it, like that:
var a = [{
'id': '1',
'name': 'a1'
}, {
'id': '2',
'name': 'a2'
}, {
'id': '3',
'name': 'a3'
}];
var b = [{
'id': '2',
'name': 'a2'
}];
var idsToDelete = b.map(function(elt) {return elt.id;});
var result = a.filter(function(elt) {return idsToDelete.indexOf(elt.id) === -1;});
console.log(result)
Suppose I have following arrays of objects
var firstDataSet = [
{'id': 123, 'name': 'ABC'},
{'id': 456, 'name': 'DEF'},
{'id': 789, 'name': 'GHI'},
{'id': 101, 'name': 'JKL'}
];
var secondDataSet = [
{'id': 123, 'name': 'ABC', 'xProp': '1q'},
{'id': 156, 'name': 'MNO', 'xProp': '2w'},
{'id': 789, 'name': 'GHI', 'xProp': '3e'},
{'id': 111, 'name': 'PQR', 'xProp': '4r'}
];
Now I want to collect array with unique objects (matching id and name)i.e.
var firstDataSet = [
{'id': 123, 'name': 'ABC', 'xProp': '1q'},
{'id': 456, 'name': 'DEF'},
{'id': 789, 'name': 'GHI', 'xProp': '3e'},
{'id': 101, 'name': 'JKL'},
{'id': 156, 'name': 'MNO', 'xProp': '2w'},
{'id': 111, 'name': 'PQR', 'xProp': '4r'}
];
I am able to collect ALL with
Array.prototype.unshift.apply(firstDataSet , secondDataSet );
But not sure how I can filter out duplicates. Any suggestion?
Edit: My object on two different array are not same. At least based on number of properties.
To Remove Duplicates With All Identical Properties
This was the original question.
Use a Set:
The Set object lets you store unique values of any type, whether primitive values or object references.
You can also use object literals.
var list = [JSON.stringify({id: 123, 'name': 'ABC'}), JSON.stringify({id: 123, 'name': 'ABC'})];
var unique_list = new Set(list); // returns Set {"{'id': 123, 'name': 'ABC'}"}
var list = Array.from(unique_list); // converts back to an array, and you can unstringify the results accordingly.
For more ways to construct a set back to an array, you can follow instructions here.
If you can't use ES6 (which is what defines Set), there's a polyfill for older browsers.
To Remove Objects with Duplicate Subset of Properties
Unfortunately, these objects are no longer strictly duplicates and cannot be tackled in a friendly way using Set, for instance.
The easiest way to approach this type of problem is to iterate through the array of objects, identify those with repeated property values, and eliminate in place using splice, for example.
This can be achieved By extending Set class Like below
var firstDataSet = [
{'id': 123, 'name': 'ABC'},
{'id': 456, 'name': 'DEF'},
{'id': 789, 'name': 'GHI'},
{'id': 101, 'name': 'JKL'}
];
var secondDataSet = [
{'id': 123, 'name': 'ABC', 'xProp': '1q'},
{'id': 156, 'name': 'MNO', 'xProp': '2w'},
{'id': 789, 'name': 'GHI', 'xProp': '3e'},
{'id': 111, 'name': 'PQR', 'xProp': '4r'}
];
Array.prototype.unshift.apply(firstDataSet , secondDataSet );
//console.log(firstDataSet)
class UniqueSet extends Set {
constructor(values) {
super(values);
const data = [];
for (let value of this) {
if (data.includes(JSON.parse(value.id))) {
this.delete(value);
} else {
data.push(value.id);
}
}
}
}
console.log(new UniqueSet(firstDataSet))
Working link
We'll combine the two arrays using concat, then filter the resulting array using filter. For each element, we'll find the index of the first element with the same id and name, using findIndex. If that index is the same as the current index, it means this is the first occurrence of that id and name, so we just let it pass through. Otherwise, we'll add in new fields to the first occurrence, and filter it out.
function combine(a1, a2) {
function match(e1, e2) { return e1.id === e2.id && e1.name === e2.name); }
return a1.concat(a2) . filter((e1, i, a) => {
let firstIndex = a.findIndex(e2 => match(e1, e2));
if (i === firstIndex) return true; // this is the first occurrence
a[firstIndex].xProp = e2.xProp; // copy over property
return false; // filter out
});
}
If you want to handle arbitrary properties, instead of just xProp, then change the relevant line to something like
a[firstIndex] = Object.assign(e2, a[firstIndex]);
That will replace the first occurrence with the result of copying all its properties on top of the current occurrence including whatever additional properties it may have.
Mandatory disclaimer: As always, depending on your environment, you may not have arrow functions, or Array#findIndex, or Object.assign. In such cases, rewrite/polyfill/transpile as necessary.
This may not be the most efficient solution, but assuming that id is always unique, it should work.
var firstDataSet = [
{'id': 123, 'name': 'ABC'},
{'id': 456, 'name': 'DEF'},
{'id': 789, 'name': 'GHI'},
{'id': 101, 'name': 'JKL'}
];
var secondDataSet = [
{'id': 123, 'name': 'ABC', 'xProp': '1q'},
{'id': 156, 'name': 'MNO', 'xProp': '2w'},
{'id': 789, 'name': 'GHI', 'xProp': '3e'},
{'id': 111, 'name': 'PQR', 'xProp': '4r'}
];
Array.prototype.unique = function() {
var o = {}, i, l = this.length, r = [];
for(i=0; i<l;i+=1) o[this[i]] = this[i];
for(i in o) r.push(o[i]);
return r;
};
function concatUnique(a, b, property) {
var arr = a.concat(b);
arr.sort(function(a,b) {
return Object.keys(b).length - Object.keys(a).length;
});
var ids = arr.map(function(obj){ return obj[property] }).unique();
return arr.filter(function(obj) {
if(ids.indexOf(obj[property]) > -1) {
ids.splice( ids.indexOf(obj[property]) , 1);
return true;
} else {
return false
}
});
}
var newArray = concatUnique(firstDataSet, secondDataSet, 'id');