Compare two array of objects to find distinct values by key number
Suppose the old object consists of
oldChoices = [{"number": 1, "text": "abc" }, {"number": 2, "text": "pqr" }]
and new object consists of
newChoices = [{"number": 1, "text": "abc" }, {"number": 2, "text": "pqr" }, {"number": 3, "text": "xyz" }]
So need to get:
[{"number": 3, "text": "xyz" }]
Note:
1. Values populate in the newChoices array on the keypress event of the textbox.
2. newChoices can get value at the start as well.
Attempt 1:
var uniqueTemp = [];
$.each(oldChoices, function(x, e1){
$.each(newChoices, function(y, e2){
if(e1.number != e2.number){
uniqueTemp.push(e2);
}
});
})
Attempt 2:
var uniqueTemp = [];
oldChoices.filter(function(x){
if(newChoices.indexOf(x.number) === -1){
uniqueTemp.push(x);
return true;
}else{
return false;
}
});
Expected:
[{"number": 3, "text": "xyz" }]
You could take a Set and filter the new array.
var oldChoices = [{ number: 1, text: "abc" }, { number: 2, text: "pqr" }],
newChoices = [{ number: 1, text: "abc" }, { number: 2, text: "pqr" }, { number: 3, text: "xyz" }],
old = new Set(oldChoices.map(({ number }) => number)),
result = newChoices.filter(({ number }) => !old.has(number));
console.log(result);
Your second attempt is close, just change to:
newChoices.filter((x) => {
return (!oldChoices.find((choice) => choice.number === x.number));
});
Here is your solution . Simple use the flag for it .
in arr you will have a unique object as expected .
var oldChoices = [{"number": 1, "text": "abc" }, {"number": 2, "text": "pqr" }]
var newChoices = [{"number": 1, "text": "abc" }, {"number": 2, "text": "pqr" }, {"number": 3, "text": "xyz" }];
var arr = []
var flag = 0;
newChoices.forEach(function(newChoice){
oldChoices.forEach(function(oldChoice){
if(oldChoice.number == newChoice.number){
flag = 1;
}
});
if(flag != 1){
arr.push(newChoice);
}
flag = 0;
});
console.log(arr);
This is a generic function that calculates the difference of two arrays:
let arrayDifference = (v1, v2, cmp = null) =>
[...v1.filter(o1 => !v2.some(o2 => cmp ? cmp(o1, o2) : o1 === o2)),
...v2.filter(o1 => !v1.some(o2 => cmp ? cmp(o1, o2) : o1 === o2))]
Than you can invoke it with the right comparison function:
arrayDifference(
oldChoices,
newChoices,
(o1, o2) => o1.number === o2.number
)
This function finds the unique objects that occurs both in oldChoices and in newChoices.
Related
arr1 = [
{
"levelNumber": "2",
"name": "abc",
},
{
"levelNumber": "3",
"name": "abc"
},
{
"levelNumber": "3",
"name": "raks",
}
]
my result array should have objects with max levelNumber i.e 3 in this case.
it should look like:
resultArr = [
{
"levelNumber": "3",
"name": "abc"
},
{
"levelNumber": "3",
"name": "raks",
}
]
note that here levelNumber can be anything..
please help me with the generic nodejs code to get duplicate max value objects
You can first find the max level of all the objects in the array and then filter the array
arr1 = [
{
"levelNumber": "2",
"name": "abc",
},
{
"levelNumber": "3",
"name": "abc"
},
{
"levelNumber": "3",
"name": "raks",
}
]
const maxLevel = String(Math.max(...arr1.map(obj => Number(obj.levelNumber))))
const maxLevelObjects = arr1.filter(obj => obj.levelNumber === maxLevel)
console.log(maxLevelObjects);
const data = [
{
"levelNumber": "2",
"name": "abc",
},
{
"levelNumber": "3",
"name": "abc"
},
{
"levelNumber": "3",
"name": "raks",
}
];
const levelNumbers = data.map((item) => parseInt(item.levelNumber));
const maxLevelNumber = Math.max(...levelNumbers).toString();
const highestLevelItems = data.filter((item) => item.levelNumber == maxLevelNumber);
console.log(highestLevelItems);
/* output
[
{ levelNumber: '3', name: 'abc' },
{ levelNumber: '3', name: 'raks' }
]
*/
EDIT
As #nat mentioned in comment:
if I add one more object in the array, with name = 'raks & levelNumber = '4' then it should display maximum levelNumber wrt that particular name. i.e.
{ "levelNumber": "3", "name": "abc" }, { "levelNumber": "4", "name": "raks" }
To achieve this, you have to:
make a Set of names
make a separate empty array to hold final result
repeat the above process for each name and add result in the array
return complete result
const data = [
{
"levelNumber": "2",
"name": "abc",
},
{
"levelNumber": "3",
"name": "abc"
},
{
"levelNumber": "3",
"name": "raks",
},
{
"levelNumber": "4",
"name": "raks",
},
{
"levelNumber": "5",
"name": "raks",
}
];
// 1.
const names = new Set(data.map((item) => item.name)); // Set is used to get only unique items
// 2.
const result = []; // For normal JS
// const result: Array<{levelNumber: string, name: string}> = []; // For TS
// 3.
names.forEach((name) => {
/* minify data (filter items with only particular name) e.g. [{levelNumber: '2', name: 'abc'}, {levelNumber: '3', name: 'abc'}] */
const minifiedData = data.filter((item) => item.name === name);
/* same process, now for minified array */
const levelNumbers = minifiedData.map((item) => parseInt(item.levelNumber));
const maxLevelNumber = Math.max(...levelNumbers).toString();
minifiedData.forEach((item) => {
if (item.levelNumber == maxLevelNumber)
result.push(item); // push every matching item (item with highest level) in final result
});
});
// 4.
console.log(result);
const arr1 = [
{
levelNumber: '2',
name: 'abc',
},
{
levelNumber: '3',
name: 'abc',
},
{
levelNumber: '3',
name: 'raks',
},
];
const getHighLevelElements = (array) => {
if (array.length === 0) return null;
array.sort((elem1, elem2) => {
if (Number(elem1.levelNumber) < Number(elem2.levelNumber)) {
return 1;
}
if (Number(elem1.levelNumber) > Number(elem2.levelNumber)) {
return -1;
}
return 0;
});
return array.filter((elem) => elem.levelNumber === array[0].levelNumber);
};
const resultArr = getHighLevelElements([...arr1]);
console.log(resultArr);
I would first have a variable called highestLevel to store the highest level number found in the array of objects (will be used later while looping), loop through the whole array and checking every key levelNumber and storing that number IF highestLevel is lower than the value of the current object levelNumber.
After I've looped through the array and got the actual highestLevel number, I would loop through again and only get the objects that are equivalent to my variable highestLevel
You can just iterate one time over arr1 with Array.prototype.reduce()
Code:
const arr1 = [{levelNumber: '2',name: 'abc',},{levelNumber: '3',name: 'abc',},{levelNumber: '3',name: 'raks'}]
const result = arr1.reduce((a, c) => !a.length || +c.levelNumber === +a[0].levelNumber
? [...a, c]
: +c.levelNumber > +a[0].levelNumber
? [c]
: a,
[])
console.log(result)
Is it possible to filter an array of objects by multiple values?
E.g in the sample below can I filter it by the term_ids 5 and 6 and type car at the same time?
[
{
"id":1,
"term_id":5,
"type":"car"
},
{
"id":2,
"term_id":3,
"type":"bike"
},
{
"id":3,
"term_id":6,
"type":"car"
}
]
Definitely up for using a library if it makes it easier.
You can do it with Array.filter
var data = [{
"id": 1,
"term_id": 5,
"type": "car"
},
{
"id": 2,
"term_id": 3,
"type": "bike"
},
{
"id": 3,
"term_id": 6,
"type": "car"
}
];
var result = data.filter(function(v, i) {
return ((v["term_id"] == 5 || v["term_id"] == 6) && v.type == "car");
})
console.log(result)
The following function will help you out.
nestedFilter = (targetArray, filters) => {
var filterKeys = Object.keys(filters);
return targetArray.filter(function (eachObj) {
return filterKeys.every(function (eachKey) {
if (!filters[eachKey].length) {
return true;
}
return filters[eachKey].includes(eachObj[eachKey]);
});
});
};
Use this function with filters described as below:
var filters = {
"id": ["3"],
"term_id": ["6"],
"type": ["car","bike"]
}
Dont pass empty array. If there are no values in the array, skip that property in the filters.
The result will be filtered array.
You can do this with plain js filter() method and use && to test for both conditions.
var data = [{"id":1,"term_id":5,"type":"car"},{"id":2,"term_id":3,"type":"bike"},{"id":3,"term_id":6,"type":"car"}];
var result = data.filter(function(e) {
return [5, 6].includes(e.term_id) && e.type == 'car'
});
console.log(result);
Another way to do it is to use lodash filter + reduce.
const arr = [{"id":1,"term_id":5,"type":"car"},{"id":2,"term_id":3,"type":"bike"},{"id":3,"term_id":6,"type":"car"}];
const result = [
{term_id: 5, type: 'car'},
{term_id: 6, type: 'car'},
].reduce((prev, orCondition) => prev.concat(_.filter(arr, orCondition)), []);
console.log(result);
<script src="https://cdn.jsdelivr.net/npm/lodash#4.17.21/lodash.min.js"></script>
I'm facing a problem with filter method. On my page there's an input to search matches by team names. Filter value is being stored to React state. Matches object looks like this:
[
{
"id": 4,
"teamBlue": {
"id": 36,
"name": "nameForTeamBlue",
"playerList": [
{
[...]
}
]
},
"teamRed": {
"id": 37,
"name": "nameForTeamRed",
"playerList": [
{
[...]
}
]
},
"localDate": "2020-01-01",
"localTime": "00:00:00",
"referee": null,
"commentator1": null,
"commentator2": null,
"streamer": null,
"stage": {
"id": 2,
"name": "GROUPSTAGE"
},
"onLive": true,
"finished": false
},
]
I tried tons of methods to filter matches by team name, for example:
let criteria = {
teamBlue: {
name: this.state.filter
},
teamRed: {
name: this.state.filter
}
};
let filteredMatches = this.state.matches.filter(function(item) {
for (let key in criteria) {
if (item[key] === undefined || item[key] !== criteria[key])
return false;
}
return true;
});
console.log(filteredMatches);
but none of them worked.
Is there any way to filter these matches so when I type "blue" into my input, it will show all matches where team name contains "blue"?
Thanks in advance!
Try updating the condition to:
if (!item[key] || item[key].name !== criteria[key].name)
let filteredMatches = this.state.matches.filter(function(item) {
let flag = true;
for (let key in criteria) {
// update this to
if (!item[key] || item[key].name !== criteria[key].name)
flag = false;
}
return flag;
});
The name property is missing :
if (key in item && item[key].name !== criteria[key].name)
You're comparing objects with === which will return false. You either need to use a deep comparison method from a library, or implement it yourself like below:
const matches = [ {"id": 4,
"teamBlue": {
"id": 36,
"name": "nameForTeamBlue",
"playerList": []
},
"teamRed": {
"id": 37,
"name": "nameForTeamRed",
"playerList": []
},
}, {"id": 4,
"teamBlue": {
"id": 36,
"name": "nameForTeamBlue",
"playerList": []
},
"teamRed": {
"id": 37,
"name": "nameForTeamRead",
"playerList": []
},
}]
const criteria = {
teamBlue: {
name: 'nameForTeamBlue',
},
teamRed: {
name: 'nameForTeamRed',
}
}
const filteredMatches = matches.filter((item) => {
const allCriteriaMatched = Object.entries(criteria)
.every(([key, value]) => {
const matched = Object.entries(value).every(([criteriaKey, criteriaValue]) => {
const itemValue = item[key][criteriaKey]
const matched = itemValue == criteriaValue
if (!matched) console.log('Item %s does not matched criteria %s. Item\'s value is %s, but criteria value is %s', item[key]['id'], criteriaKey, itemValue, criteriaValue, criteriaValue)
return matched
})
if (!matched) return false
return true
}, {})
return allCriteriaMatched
})
console.log(filteredMatches);
Basically, you just need to go 1 level deeper :D if your criteria can have multiple nested objects, then there's no point doing it manually. You can try to map criteria to run against matches so that you don't use === on objects, but only primitives.
Get the corresponding type in the object, and then traverse the array of push objects, but I can't think of a better way to solve the desired result below.
I want a good return as follows:
[{
"id": 1,
"type": "one",
"name": ["apple","apricot"]
},
{
"id": 3,
"type": "two",
"name": ["avocado"]
}]
var result = [{
"id": 1,
"type": "one",
"name": "apple"
}, {
"id": 2,
"type": "one",
"name": "apricot"
},
{
"id": 3,
"type": "two",
"name": "avocado"
}
]
Array.prototype.unique = function() {
var hash = {},
len = this.length,
result = [];
for (var i = 0; i < len; i++) {
if (!hash[this[i].type]) {
result.push(this[i].type);
hash[this[i].type] = true;
}
}
return result;
}
console.log(result)
console.log(result.unique())
var cArr = result.unique()
var arr = []
cArr.forEach(function(prop) {
result.map(function(item) {
if (prop == item.type) {
console.log(item)
arr.push({
...item,
[`user_${item.id}`]: item.user,
})
}
})
})
console.log(arr)
You can do this with reduce quite easily:
var input = [
{ id: 1, type: "one", name: "apple"},
{ id: 2, type: "one", name: "apricot" },
{ id: 3, type: "two", name: "avocado" }
];
// Make sure `unique` doesn't already exist on the Array prototype
if (!('unique' in Array.prototype)) {
Array.prototype.unique = function () {
// iterate over the array
const temp = this.reduce((acc, current) => {
// Desstructure the id, type, and name from the current object
const { id, type, name } = current;
// If an key with the value of `type` doesn't exist
// on the accumulator, add a new object with name set
// to an empty array
acc[type] = acc[type] || { id, type, name: [] };
// Push the name in the current object to the name array
acc[type].name.push(name);
// Return the accumulator for the next iteration
return acc;
// Note: the initial accumulator value is an object
}, {});
// Then simply return the values from the accumulated object
return Object.values(temp);
}
}
console.log(input.unique())
I have 2 List Objects, and need to remove all items from ListA that contains ListB and return the remaining. My approach is the following:
LIST A
[{
"id": 1,
"name": "ant"
}, {
"id": 2,
"name": "ant2"
}, {
"id": 3,
"name": "ant3"
}, {
"id": 4,
"name": "ant3"
}, {
"id": 5,
"name": "ant3"
}]
LIST B
[{
"id": 1,
"name": "ant"
}, {
"id": 4,
"name": "ant4"
}, {
"id": 3,
"name": "ant3"
} ]
What I have tried:
const xxx = this.listA.filter(x => !listB.includes(x) != null);
Note (Alternative scenario): When there are 2 List that are identical, the expected result is a []. However, in my case its same as a single List.
Here you are :)
const listBSerialized = listB.map(x => JSON.stringify(x))
const xxx = listA.filter(x => !listBSerialized.includes(JSON.stringify(x)));
You'll have the following result:
[ { id: 2, name: 'ant2' },
{ id: 4, name: 'ant3' },
{ id: 5, name: 'ant3' } ]
You can also use models ever you need properties order guarantee on the serialization, sometimes the objects can have the same properties but in a different order (it depends on where is the object coming from):
function Ant(id, name) {
this.id = id
this.name = name
}
listA = [
new Ant(1, "ant"),
new Ant(2, "ant2"),
new Ant(3, "ant3"),
new Ant(4, "ant3"),
new Ant(5, "ant3")
]
listB = [
new Ant(1, "ant"),
new Ant(4, "ant4"),
new Ant(3, "ant3")
]
const listBSerialized = listB.map(x => JSON.stringify(x))
const xxx = listA.filter(x => !listBSerialized.includes(JSON.stringify(x)));
Here is a simple one liner that will handle this task:
const xxx = listA.filter(base => ! listB.some(f => f.id === base.id && f.name === base.name))
where a is ListA and b is ListB array of objects. Basically what you would do is to filter the a array with a condition that there is no object that has the same id and name in b array.