Filterring an Array of Objects - javascript

Im trying to do a check if one of the objects in the Array has the id of 2 if so remove that object. list.filter(e => e.id === 2) returns[ { name: 'bread', id: 2 } ] which is the part i want to remove but if i check if it is in the array by doing if(list.indexOf(list.filter(e => e.id === 2)) != -1) it returns -1 saying its not in the list. Any help would be apreciated!
var list = new Array();
list.push({name: 'apple', id: 1})
list.push({name: 'bread', id: 2})
console.log(list.filter(e => e.id === 2));
console.log(list);
if(list.indexOf(list.filter(e => e.id === 2)) != -1) {
list.splice(list.indexOf(list.filter(e => e.name === 2)));
console.log(list);
} else {
console.log('The id of 2 has not been found');
}

Then just use !== instead ===.
But you can use find method.
var elem = list.find(e => e.id === 2);
if(elem)
list = list.filter(e => e.id !== 2);
else
console.log('The id of 2 has not been found');

You are using filter, like it filters out elements, while it actually keeps elements that fit the condition. Change your condition to e.id !== 2 and it keeps all elements with ids not equal to 2:
var list = new Array();
list.push({name: 'apple', id: 1})
list.push({name: 'bread', id: 2})
list.push({name: 'milk', id: 3})
list.push({name: 'butter', id: 4})
console.log(list.filter(e => e.id !== 2));
.as-console-wrapper { max-height: 100% !important; top: 0; }

You need to invert the logic of the filter predicate function.
list.filter(e => e.id !== 2);
filter returns only the items that match the predicate - in your case it will return a list of only one element which has the ID of 2.

Testing with indexOf you're searching for an array with the element that you've found.
filter returns an array with the results. You should use find instead which returns a single element:
var list = new Array();
list.push({name: 'apple', id: 1})
list.push({name: 'bread', id: 2})
var index = list.indexOf(list.find(e => e.id === 2));
var result = index !== -1;
console.log('index', index);
console.log('result', result);

When you use list.filter(e => e.name === 2). It will return an array include the object , not the object itself. So it will return -1. You can use the spread syntax to extract the object out of the array contain it:
list.indexOf(...list.filter(e => e.id === 2))

indexOf won't work as you will be searching for an object and object comparison in JavaScript is tricky (a custom evaluater is needed to check sameness). The good news is you may not need it.
As noted in comments and elsewhere, filter was behaving opposite to what you desired. Changing the comparison operator from === to !== is one way to solve that issue.
In the code below, I've included some other goodies you may find value, such as:
the Array.of()
shorthand property names (e.g., { foo } === {'foo': foo})
series execution using the comma operator (i.e. ,) especially to avoid curly-braces
... as a rest operator for function arguments
and others
let search_id = 2; // id to filter out
let list = Array.of(
{name: 'apple', id: 1},
{name: 'bread', id: 2},
{name: 'orange', id: 3},
{name: 'bagel', id: 4}
)
log({list});
let filtered = list.filter(e => e.id !== search_id)
log({filtered});
if (filtered.length)
log(`Found ${filtered.length} matches!`);
else
log(`The id of ${search_id} has not been found`);
// Simple logger so label is on its own line
function log(){
let [param1,...args]=arguments;
switch (typeof param1){
case 'string':
if (args.length)
console.log(`${param1}:`), // comma intentional
console.log(args.pop())
else
console.log(param1);
break;
case 'object':
for (let key in param1)
console.log(`${key}:`), // comma intentional
console.log(param1[key])
break;
default:
console.log('Error in passed arguments. Type not recognized.')
}
}

Related

Delete an item from a JavaScript set

How to delete an item from a set with condition
if(item.id === 2){
mySet.delete(item)
}
I can't use mySet.has(item) because only common thing in the objects is the id.
Unless you already have a reference to the object, you'll have to iterate through the Set's values to .find the matching object, then call .delete with it:
const set = new Set([
{ id: 1 },
{ id: 2 },
]);
const obj = [...set].find(obj => obj.id === 2);
set.delete(obj);
console.log([...set]);
You can create array of remaining items like
var resultArr = [];
yourobj.forEach((item,index)=>{
if(item.id === 2)
{
// nothing
}
else
{
resultArr.push(item);
}
})
You can use a for ... of statement to locate and delete matching items from a set according to your conditions. And in case you are concerned, it's ok to delete an item from a set while iterating.
let mySet = new Set([
{ id: 1 },
{ id: 2 },
{ id: 3 }
]);
for (const item of mySet) if (item.id == 2) { mySet.delete(item); /*optional*/ break; }
console.log([...mySet]);
If there are multiple matching items, you can omit the break in this example.
similar immutable way to do this in one line:
const result = mySet.filter(x => x.id === 2);

Get an item inside an array by its property

I would like to assign the output from Array.filter to an existing array.
I'm working on nodejs socket service, and I have an array structured like this:
GROUPS = [{id: 1, members: ['user1', 'user2', ... ]}];
I would like to remove a user doing a map on GROUPS, with this:
GROUPS.map((group) => {
group.members.map((member) => {
const connectedClients = Array.from(wss.clients);
const userIsConnected = connectedClients.some(existingMember => existingMember.id === member.id);
if (!userIsConnected) {
console.log('member: ', member.userID); // userid: 12345678
console.log('group_id: ', group.id); // groupId: 123456677
console.log('1. ', GROUPS[group.id]); // undefined
GROUPS[group.id].members = group.members.filter(memberInGroup => memberInGroup !== member.id);
console.log('group: ', GROUPS[group.id].members.length);
}
});
});
How can I do that association?
GROUPS[group.id].members = group.members.filter(memberInGroup => memberInGroup !== member.id);
Thank you!
MDN docs on map
arr.map(function callback(currentValue[, index[, array]]) {
// Return element for new_array
}[, thisArg])
Your issue is, you're using the group.id as index, but basic knowledge about iterations is that first elements index is 0, whilst your IDs value is 1. Having an array with 10 elements, last elements index is 9, arr[9], trying to call arr[10] will result in error, like in your example.
You can use forEach cuz you don't seem to need the array output
GROUPS.forEach((group, index, arr) => {
arr[index] = "..";
});

how to reduce a list of objects to duplicates

[edited for clarity]
Using lodash, given an array of objects:
var v = [{'a':1, 'b':1}, {'a':1, 'b':2}, {'a':1, 'c':1}];
how do I return an object which is the intersection of those objects (both key and value)? In this case:
{'a':1}
I am looking for key value pairs which are in every object.
This seems like a task for _.reduce, but I am not sure how to find the object duplicates.
You could reduce the objects by iterating the keys and checking the values. Then build a new object and return.
var array = [{ a: 1, b: 1 }, { a: 1, b: 2 }, { a: 1, c: 1 }],
result = array.reduce(function (a, b) {
return Object.keys(a).reduce(function (o, k) {
if (a[k] === b[k]) {
o[k] = a[k];
}
return o;
}, {});
});
console.log(result);
ES6 with Object.assign
var array = [{ a: 1, b: 1 }, { a: 1, b: 2 }, { a: 1, c: 1 }],
result = array.reduce((a, b) =>
Object.keys(a).reduce((o, k) =>
Object.assign(o, a[k] === b[k] ? { [k]: a[k] } : {}), {}));
console.log(result);
Indeed you can use Array#reduce with a hash object, and Object#keys to get all key:value pairs that appear in all objects.
var v = [{'a':1, 'b':1}, {'a':1, 'b':2}, {'a': 1, 'c':1, 'b': 1}];
var hashCount = {};
var result = v.reduce(function(r, o) {
Object.keys(o).forEach(function(key) { // iterate the object keys
var hash = key + '_' + o[key]; // create the hash from the key:value pair
hashCount[hash] = (hashCount[hash] || 0) + 1; // increment the hash in the hashCount
// add the pair to the result when the hash count number is equal to the length of the array,
if(hashCount[hash] === v.length) {
r[key] = o[key];
}
});
return r;
}, {});
console.log(result);
btw - my original answer was written to cope with the case of a property that appears in at at least two objects. So, if you just want to find a property that appears in 2 objects (or any arbitrary number), change this line if(hashCount[hash] === v.length) { to if(hashCount[hash] === 2) {
Here's one way to do it with loDash
var v = [{'a':1, 'b':1}, {'a':1, 'b':2}, {'c':1}];
let k = _.chain(v).map(Object.entries)
.flatten(true)
.filter((f,u,n) => {
let i = _.findLastIndex(n, z => (_.isEqual(f,z)));
return n.some((g,o) => (_.isEqual(f,g) && u!==o && u===i));
})
.map(x => ({[x[0]]:x[1]}))
.value()
console.log( k )
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>
It maps the objects back split up, then flattens it out, then filters based on object equality, using _.isEqual, not string comparison, then maps back the object and get the value from the chain
Solution using _.reduce:
var r = _.reduce(v.slice(1), function (result, item) {
Object.keys(result).forEach(function (key) {
if (!item.hasOwnProperty(key)) delete result[key];
})
return result;
}, Object.assign({}, v[0]));
The idea is to use one item as the result, which is handed over to reduce() as third parameter (accumulator). That is done by copying the Object, since that is modified within the algorithm, like so: Object.assign({}, v[0]).
Within the reduce function (second parameter) we check if the actual item for each Property of the result. If the item does not have it, we remove it from the result.
Since the first item of the list is already given to the function, it can be excluded from the array to reduce, what is done by v.slice(1).
Why does that work:
Each item in the list can be used as the initial intersection, since we are looking for all properties that exist in all objects, we can safely say that we do not forget to include any other properties from any other object.
If an item does not have any property which is part of the intersection, that property is not part of the intersection and needs to be removed from there.
Note:
One downside of using reduce here is: It iterates over each item in the list, no matter if the intersection is already empty, where the algorithm could stop. So writing a regular function like the one below might be faster for large lists of objects, which are likely to have no intersection:
function intersect (list) {
var
remain = [].concat(list),
result = Object.assign({}, remain.pop()),
keys = Object.keys(result),
item;
while ((item = remain.pop()) != undefined && keys.length > 0) {
keys.forEach(function (key) {
if (!item.hasOwnProperty(key)) delete result[key];
});
keys = Object.keys(result);
}
return result;
}

How to sort in specific order - array prototype(not by value or by name)

I wish to sort an array of three strings in a specific order.
book, car and wheel.
I can receive these strings in any order.
There may be one or more strings - max three
I would like to sort the strings in the following exact order if one or more strings are received.
wheel, book, car
assume the property name is name...
I tried something like this:
myitem.sort((a, b) => {
if(a.name === 'wheel')
return 1;
if(a.name === 'book' && b.name === 'car')
return 1;
return -1;
You could use an object with the order of the values and their order. With a default value, you could move not specified values to start or end of the array.
var order = { wheel: 1, book: 2, car: 3, default: 1000 },
array = ['foo', 'car', 'book', 'wheel'];
array.sort(function (a, b) {
return (order[a] || order.default) - (order[b] || order.default);
});
console.log(array);
Since you expect instances (in myitem) of these three strings only, you can use filter on the ordered array, and test the presence of these input strings with Set#has:
var result = ['wheel', 'book', 'car'].filter(function (s) {
return this.has(s)
}, new Set(myitem));
// Sample data being received
var myitem = ['car', 'wheel'];
var result = ['wheel', 'book', 'car'].filter(function (s) {
return this.has(s)
}, new Set(myitem));
// ordered result
console.log(result);
Your code looks mostly right. I fixed the formatting, etc. and reversed the signs in the sort function.
Of course, this only handles sorting items with those three exact names. If you need to handle other inputs, you'll have to decide what should happen to those.
var myitems = [
{
name: "book",
},
{
name: "wheel",
},
{
name: "car",
},
];
console.log(
myitems.sort((a, b) => {
if (a.name === "wheel") {
return -1;
}
if (a.name === "book" && b.name === "car") {
return -1;
}
return 1;
})
);
Another option: indexOf. Undefined values show at the top of the list, but those don't exist. Probably slower than object with index values, but probably easier to maintain if the order changes, especially of the list gets large.
indexOfSort = (a, b) => {
const order = [
"wheel",
"book",
"car"
];
return order.indexOf(a) - order.indexOf(b);
}
console.log(
['foo', 'car', 'book', 'wheel'].sort(indexOfSort)
);

match an object in an array of objects and remove

After 2 days of fighting this problem I'm hoping someone can help me out.
I have two arrays of objects, like this:
let oldRecords = [
{name: 'john'},
{name: 'ringo'}
];
let newRecords = [
{name: 'paul'},
{name: 'john'},
{name: 'stuart'}
];
I am trying to end up with a function that returns named variables containing a list of data thats been added (exist in newRecords but not in oldRecords) and a list of data that has been removed (exists in oldRecords but not in newRecords).
for example
const analyse = (older, newer) => {
let added, removed;
// ... magic
return {added, removed}
}
const {added, removed} = analyse(oldRecords, newRecords);
I won't post all the code I've tried inside this function as I have tried to map, reduce and loop through both arrays creating temporary arrays for the last 48 hours and I could now fill a book with code I've written and deleted. I have also used underscore.js methods like reject/find/findWhere which all got me close but no cigar.
the main issue I am having is because the arrays contain objects, its super easy if they contain numbers, i.e.
var oldRecords = [1, 3];
var newRecords = [1, 2, 4]
function analyse (old, newer) {
let added = [];
let removed = [];
old.reduce((o) => {
added = added.concat(_.reject(newer, (num) => num === o ));
});
newer.reduce((n) => {
removed = _.reject(old, (num) => num === n );
});
return {added, removed}
}
const {added, removed} = analyse(oldRecords, newRecords);
How can I achieve the above but with objects not arrays?
n.b. I tried modifying the above and using JSON.stringify but it didn't really work.
EDIT: important point I forgot to add, the object structure adn it's keys are dynamic, they come from a database, so any individual checking of a key must also be dynamic, i.e. not a hard coded value
You could use reject and some to check for inclusion in the appropriate sets. The isEqual function is used to check for equality to handle dynamic keys:
const analyse = (older, newer) => {
let added = _.reject(newer, n => _.some(older, o =>_.isEqual(n, o)));
let removed = _.reject(older, o => _.some(newer, n => _.isEqual(n, o)));
return {added, removed}
}
You could try this:
const analyse = (older, newer) => {
let removed = older.filter(newItem => {
return newer.filter(oldItem => {
return _.isEqual(newItem, oldItem);
}).length === 0
});
let added = newer.filter(oldItem => {
return older.filter(newItem => {
return _.isEqual(newItem, oldItem);
}).length === 0
});
return {added, removed};
}
You can first create function to check if two object are equal, end then use filter() and some() to return result.
let oldRecords = [
{name: 'john'},
{name: 'ringo'}
];
let newRecords = [
{name: 'paul'},
{name: 'john'},
{name: 'stuart'}
];
function isEqual(o1, o2) {
return Object.keys(o1).length == Object.keys(o2).length &&
Object.keys(o1).every(function(key) {
return o2.hasOwnProperty(key) && o1[key] == o2[key]
})
}
var result = {}
result.removed = oldRecords.filter(e => !newRecords.some(a => isEqual(e, a)))
result.added = newRecords.filter(e => !oldRecords.some(a => isEqual(e, a)))
console.log(result)

Categories

Resources