Common values in array of arrays - lodash - javascript

I have an array that looks like this:
const myArray = [
[
{id: 1, name: 'Liam'},
{id: 2, name: 'Oliver'},
{id: 3, name: 'Jake'},
],
[
{id: 1, name: 'Liam'},
{id: 2, name: 'Oliver'},
{id: 4, name: 'Joe'},
],
]
I need to find common elements by id, and return them in an array that would look something like this:
[
{id: 1, name: 'Liam'},
{id: 2, name: 'Oliver'},
]
If there isn't any way to do it with lodash, just JS could work too.
Note that I do not know how many of these arrays I will have inside, so it should work for any number.

You can use lodash's _.intersectionBy(). You'll need to spread myArray because _intersectionBy() expect arrays as arguments, and not a single array of array:
const myArray = [[{"id":1,"name":"Liam"},{"id":2,"name":"Oliver"},{"id":3,"name":"Jake"}],[{"id":1,"name":"Liam"},{"id":2,"name":"Oliver"},{"id":4,"name":"Joe"}]]
const result = _.intersectionBy(...myArray, 'id')
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.20/lodash.min.js" integrity="sha512-90vH1Z83AJY9DmlWa8WkjkV79yfS2n2Oxhsi2dZbIv0nC4E6m5AbH8Nh156kkM7JePmqD6tcZsfad1ueoaovww==" crossorigin="anonymous"></script>

A vanilla solution can be as simple as a filter() call on the first element of the array checking to see that every() subsequent element contains some() elements that match.
const [srcElement, ...compArray] = [...myArray];
const intersection = srcElement.filter(o => (
compArray.every(arr => arr.some(p => p.id === o.id)))
);
console.log(intersection)
.as-console-wrapper { max-height: 100% !important; top: 0; }
<script>
const myArray = [
[{ id: 1, name: 'Liam' }, { id: 2, name: 'Oliver' }, { id: 3, name: 'Jake' }],
[{ id: 1, name: 'Liam' }, { id: 2, name: 'Oliver' }, { id: 4, name: 'Joe' }],
[{ id: 1, name: 'Liam' }, { id: 2, name: 'Oliver' }, { id: 5, name: 'Dean' }, { id: 6, name: 'Mara' }]
]
</script>

Use nested forEach loops and Set. Go over each sub-array and find out the common items so far.
const intersection = ([firstArr, ...restArr]) => {
let common = new Set(firstArr.map(({ id }) => id));
restArr.forEach((arr) => {
const newCommon = new Set();
arr.forEach(({ id }) => common.has(id) && newCommon.add(id));
common = newCommon;
});
return firstArr.filter(({ id }) => common.has(id));
};
const myArray = [
[
{ id: 1, name: "Liam" },
{ id: 2, name: "Oliver" },
{ id: 3, name: "Jake" },
],
[
{ id: 1, name: "Liam" },
{ id: 2, name: "Oliver" },
{ id: 4, name: "Joe" },
],
[
{ id: 2, name: "Oliver" },
{ id: 4, name: "Joe" },
],
];
console.log(intersection(myArray));

Nowadays vanilla ES is pretty powerful to work with collections in a functional way even without the help of utility libraries.
You can use regular Array's methods to get a pure JS solution.
I've created two examples with pure JS.
Of course, there could be more approaches as well. And if you already use Lodash in your application, probably it would be better to just use its high-level implementation in form of _.intersectionBy() proposed above to reduce the code complexity.
const myArray = [
[
{id: 1, name: 'Liam'},
{id: 2, name: 'Oliver'},
{id: 3, name: 'Jake'},
],
[
{id: 1, name: 'Liam'},
{id: 2, name: 'Oliver'},
{id: 4, name: 'Joe'},
],
];
// Regular functional filter-reduce
const reducer = (accum, x) => {
return accum.findIndex(y => x.id == y.id) < 0
? [...accum, x]
: accum;
};
const resultFilterReduce = myArray
.flat()
.filter(x => myArray.every(y => y.findIndex(obj => obj.id === x.id) > -1))
.reduce(reducer, []);
console.log(resultFilterReduce);
// Filter-reduce with using of "HashMap" to remove duplicates
const resultWithHashMap = Object.values(
myArray
.flat()
.filter(x => myArray.every(y => y.findIndex(obj => obj.id === x.id) > -1))
.reduce((accum, x) => {
accum[x.id] = x;
return accum;
}, {})
);
console.log(resultWithHashMap);

Related

How group and count elements by lodash?

My data:
[{
id: 1,
name: 'foo'
}, {
id: 2,
name: 'bar'
}, {
id: 1,
name: 'foo'
}
];
I want to do something like this:
[
{
name: "foo",
id: 1,
count: 2
},
{
name: "bar",
id: 2,
count: 1
}
]
Now i'm grouping the elements with groupBy by name.
Thanks for the help!
var source = [{
id: 1,
name: 'foo'
}, {
id: 2,
name: 'bar'
}, {
id: 1,
name: 'foo'
}
];
var result = _(source)
.groupBy('name')
.map(function(items, name) {
return {
name: name,
count: items.length
}
}).value();
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.js"></script>
It's not exactly what you asked for, but here is a simple method without using lodash. Here I'm using the ID of each element, but you can use anything that uniquely identifies it.
const objArray = [{
id: 1,
name: 'foo'
}, {
id: 2,
name: 'bar'
}, {
id: 1,
name: 'foo'
}
];
const reduceAndCount = (arr) => {
const newMap = {};
arr.forEach(el => {
if (!newMap[el.id]) {
el.count = 1
return newMap[el.id] = el
}
newMap[el.id].count = newMap[el.id].count + 1
})
return Object.values(newMap)
}
const result = reduceAndCount(objArray)
console.log(result)
With deference to the answer provided here which uses 'lodash', as requested in the above question, the below points are observed:
the 'groupBy' is fixed (ie, the 'name' field/column/prop) is used
the result given includes the name & count, but the expected result needs the 'id' as well.
EDIT:
Adding solution using lodash,
const arrayRaw = [{
id: 1,
name: 'foo'
}, {
id: 2,
name: 'bar'
}, {
id: 1,
name: 'foo'
}];
const groupBy = (col = 'name', arr = arrayRaw) => (
_(arr).groupBy(col).map(it => ({
...it[0],
count: it.length
})).value()
);
console.log(groupBy());
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/1.2.1/lodash.min.js"></script>
In case, someone requires a solution without 'lodash', the below should be one possible implementation:
const arrayRaw = [{
id: 1,
name: 'foo'
}, {
id: 2,
name: 'bar'
}, {
id: 1,
name: 'foo'
}];
const groupBy = (col = 'name', arr = arrayRaw) => (
Object.entries(arr.reduce((fin, itm, idx) => ({
...fin,
[itm[col]]: (fin[itm[col]] || []).concat([idx])
}), {})).map(([k, v]) => ({
...arr[v[0]],
count: v.length
}))
);
console.log(groupBy());
Approach / Explanation
It is self-explanatory, however, should anyone require one - please comment and I shall add.

How to remove duplicate objects from arrays based on association records in JavaScript

I have to Arrays one is users and another one assignments and users is multidimensional array like below
users = [
{id: 1, name: 'john'},
{id: 2, name: 'john'},
{
id: 3,
name: 'john',
assignments: [
{id: 1, userId: 3, postId: 2}
]
},
]
assignments = [
{id: 1, userId: 3, postId: 2}
]
the I tried
const usersIDs = new Set(users.map(({ id }) => id))
const combined = [
...users,
...assignments.filter(({ userId }) => !usersIDs.has(userId))
]
I want to remove the object from users where id & users.assignments.postId match with assignments table.
I have tried with several ways that find over Google but not luck.
Whats should I do now? How could I achieve that?
Thanks
Please use the below code to acheive your output
assignments.forEach(asgn => {
if (users.some(user => user.id === asgn.postId)) {
const index = users.findIndex(user => user.id === asgn.postId);
users.splice(index, 1);
}
});
console.log(users);
This should work for you.
const users = [
{ id: 1, name: 'john' },
{ id: 2, name: 'john' },
{
id: 3, name: 'john',
assignments: [
{ id: 1, userId: 3, postId: 2 }
]
},
]
const assignments = [
{ id: 1, userId: 3, postId: 2 }
]
assignments.forEach(assignment => {
const index = users.findIndex(
user => user.assignments ? user.assignments.findIndex(a => a.id === assignment.id && a.userId === assignment.userId && a.postId === assignment.postId) : -1);
if (index > -1) {
users.splice(index, 1);
}
});
console.log(users);
Please try this code hope this will work for you.
users = [
{id: 1, name: 'john'},
{id: 2, name: 'john'},
{id: 3, name: 'john',
assignments: [
{id: 1, userId: 3, postId: 2}
]
},
]
assignments = [
{id: 1, userId: 3, postId: 2}
]
users.forEach(matchValueFunction);
function matchValueFunction(item,index) {
if(item.assignments){
for(var i=0; i< assignments.length; i++){
if(item.assignments[0].id == assignments[i].id && item.assignments[0].postId == assignments[i].postId){
users.splice(index);
}
}
}
}
console.log(users);

Return array of objects (from array of objects)

I have the following data and I want to return an array (of objects) of years that are distinct.
I tried the following function but I'm getting an array within an array.
const data = [{
id: 1,
name: "test1",
years: [{
id: 1,
name: "year1"
}, {
id: 2,
name: "year2"
}]
},
{
id: 2,
name: "test2",
years: [{
id: 1,
name: "year1"
}]
},
]
let years = data.map((s) => {
return s.years
})
let distinctYears = Array.from(new Set(years.map(c => c.id))).map(id => {
return {
id: id,
name: years.find(c => c.id === id).name,
}
})
console.log(distinctYears);
desired outcome:
[
{id: 1, name: "year1"},
{id: 2, name: "year2"}
]
Since s.years() is an array, and data.map() returns an array of the results, years is necessarily an array of arrays.
Instead of using .map(), use .reduce() to concatenate them.
const data = [{
id: 1,
name: "test1",
years: [{
id: 1,
name: "year1"
}, {
id: 2,
name: "year2"
}]
},
{
id: 2,
name: "test2",
years: [{
id: 1,
name: "year1"
}]
},
];
const years = data.reduce((a, {
years
}) => a.concat(years), []);
let distinctYears = Array.from(new Set(years.map(c => c.id))).map(id => {
return {
id: id,
name: years.find(c => c.id === id).name,
}
});
console.log(distinctYears);
There's so many ways you can go about doing this. Here's one, it's not a one-liner but its broken down to parts to help us understand whats going on.
Your dataset:
let data =
[
{
id: 1,
name: "test1",
years: [{id: 1, name: "year1"}, {id: 2, name: "year2"} ]
},
{
id: 2,
name: "test2",
years: [{id: 1, name: "year1"} ]
},
]
Use .flatMap() to create a one-level array with all items:
let allItems = data.flatMap((item) => {
return item.years.map((year) => {
return year
})
})
Getting distinct items:
let distinct = []
allItems.forEach((item) => {
let matchingItem = distinct.find((match) => match.id == item.id && match.name == item.name)
if(!matchingItem){
distinct.push(item)
}
})
In Practice:
let data = [{
id: 1,
name: "test1",
years: [{
id: 1,
name: "year1"
}, {
id: 2,
name: "year2"
}]
},
{
id: 2,
name: "test2",
years: [{
id: 1,
name: "year1"
}]
},
]
let allItems = data.flatMap((item) => {
return item.years.map((year) => {
return year
})
})
let distinct = []
allItems.forEach((item) => {
let matchingItem = distinct.find((match) => match.id == item.id && match.name == item.name)
if (!matchingItem) {
distinct.push(item)
}
})
console.log(distinct)

How can I remove the name field from a json array?

I have a json array like this:
(3) [{…}, {…}, {…}]
0: {Id: 1, Name: "bask"}
1: {Id: 2, Name: "voll"}
2: {Id: 3, Name: "badminton"}
I want to turn it into something like this:
{1:"bask",2:"voll",3:"badminton"}
You can use reduce to loop through array and build a object of desired key/value pair
let data = [{Id: 1, Name: "bask"},{Id: 2, Name: "voll"},{Id: 3, Name: "badminton"}]
let output = data.reduce((op, {Id, Name}) => {
op[Id] = Name
return op
},{})
console.log(output)
You could take Object.fromEntries with the maped key/value pairs.
var array = [{ Id: 1, Name: "bask" }, { Id: 2, Name: "voll" }, { Id: 3, Name: "badminton" }],
object = Object.fromEntries(array.map(({ Id, Name }) => [Id, Name]));
console.log(object);
You can check out the reduce() function!
let array = [
{Id: 1, Name: "bask"},
{Id: 2, Name: "voll"},
{Id: 3, Name: "badminton"}
];
console.log(_.reduce(array, function(result, obj){
result[obj.Id] = obj.Name;
return result;
}, {}));
You can checkout lodash an awesome library with many other such utilities!
You can do this with reduce():
var a = [
{Id: 1, Name: "bask"},
{Id: 2, Name: "voll"},
{Id: 3, Name: "badminton"}
]
b = a.reduce((acc, item) => {
acc[item.Id] = item.Name;
return acc;
}
console.log(b);
You can do it in different ways, here one of them.
let dataArray = [
{id: 1, name: 'bask'},
{id: 2, name: 'voll'},
{id: 3, name: 'badminton'}
]
let ouputObject = {}
dataArray.map(data => {
ouputObject[`${data.id}`] = data.name
})
console.log(ouputObject)
outputObject will be
Object {
1: "bask",
2: "voll",
3: "badminton"
}
Using Array.reduce() :
var arr = [{
Id: 1,
Name: "bask"
}, {
Id: 2,
Name: "voll"
}, {
Id: 3,
Name: "badminton"
}];
var reduceObj = arr.reduce(function(result, currentElement) {
result[currentElement.Id] = currentElement.Name;
return result;
}, {});
console.log(reduceObj);
Using Array.map() :
var arr = [{
Id: 1,
Name: "bask"
}, {
Id: 2,
Name: "voll"
}, {
Id: 3,
Name: "badminton"
}];
var mapObject = {}
arr.map(obj => {
mapObject[obj.Id] = obj.Name
})
console.log(mapObject);

Why does map function return undefined but console.log logs out?

I want to return matching proprieties of two arrays of objects. But I got undefined from map function.
let fruits1 = [
{id: 1, name: "apple"},
{id: 2, name: "dragon fruit"},
{id: 3, name: "banana"},
{id: 4, name: "kiwi"},
{id: 5, name: "pineapple"},
{id: 6, name: "watermelon"},
{id: 7, name: "pear"},
]
let fruits2 = [
{id: 7, name: "pear"},
{id: 10, name: "avocado"},
{id: 5, name: "pineapple"},
]
fruits1.forEach((fruit1) => {
fruits2.filter((fruit2) => {
return fruit1.name === fruit2.name;
}).map((newFruit) => {
//console.log(newFruit.name);
return newFruit.name;
})
})
What are you looking for is an array intersection:
// Generic helper function that can be used for the three operations:
const operation = (list1, list2, isUnion = false) =>
list1.filter( a => isUnion === list2.some( b => a.name === b.name ) );
// Following functions are to be used:
const inBoth = (list1, list2) => operation(list1, list2, true),
inFirstOnly = operation,
inSecondOnly = (list1, list2) => inFirstOnly(list2, list1);
Usage:
console.log('inBoth:', inBoth(list1, list2));
Working Example:
// Generic helper function that can be used for the three operations:
const operation = (list1, list2, isUnion = false) =>
list1.filter( a => isUnion === list2.some( b => a.name === b.name ) );
// Following functions are to be used:
const inBoth = (list1, list2) => operation(list1, list2, true),
inFirstOnly = operation,
inSecondOnly = (list1, list2) => inFirstOnly(list2, list1);
let fruits1 = [
{id: 1, name: "apple"},
{id: 2, name: "dragon fruit"},
{id: 3, name: "banana"},
{id: 4, name: "kiwi"},
{id: 5, name: "pineapple"},
{id: 6, name: "watermelon"},
{id: 7, name: "pear"},
]
let fruits2 = [
{id: 7, name: "pear"},
{id: 10, name: "avocado"},
{id: 5, name: "pineapple"},
]
console.log('inBoth:', inBoth(fruits1, fruits2));
You could use a Set and filter the names.
const names = ({ name }) => name;
var fruits1 = [{ id: 1, name: "apple" }, { id: 2, name: "dragon fruit" }, { id: 3, name: "banana" }, { id: 4, name: "kiwi" }, { id: 5, name: "pineapple" }, { id: 6, name: "watermelon" }, { id: 7, name: "pear" }],
fruits2 = [{ id: 7, name: "pear" }, { id: 10, name: "avocado" }, { id: 5, name: "pineapple" }],
common = fruits1
.map(names)
.filter(Set.prototype.has, new Set(fruits2.map(names)));
console.log(common);
What you want to do is this:
/* first we filter fruits1 (arbitrary) */
let matchingFruits = fruits1.filter(f1 => {
/* then we filter the frut if it exists in frtuis2 */
return fruits2.find(f2 => f2.name === f1.name)
}).map(fruit => fruit.name) // and now we map if we only want the name strings
If you're not using a polyfill Array.find will not work in IE. The alternative would be using Array.indexOf (thanks for pointing this out #JakobE).
Be aware that Array.forEach return value is undefined and that, in order to actually use the Array.map correctly, one has to consume the returned value somehow or assign it to a variable, as we just did with matchingFruits.

Categories

Resources