How to concat two arrays and remove duplicates in javascript - javascript

I want concat following two arrays by removing duplicates without assigning to third variable:
var arr1=[{id:1,name:'AB'},{id:2,name:'CD'}];
var arr2=[{id:3,name:'EF'},{id:2,name:'CD'}];
I want arr1 like:
[{id:1,name:'AB'},{id:2,name:'CD'},{id:3,name:'EF'}]
arr1.concat(arr2);

First merge two arrays then put array into a map with their ids. Then create array from map values.
var arr1=[{id:1,name:'AB'},{id:2,name:'CD'}];
var arr2=[{id:3,name:'EF'},{id:2,name:'CD'}];
arr1 = arr1.concat(arr2) // merge two arrays
let foo = new Map();
for(const tag of arr1) {
foo.set(tag.id, tag);
}
let final = [...foo.values()]
console.log(final)

Can use Array reduce and findIndex to achieve what you want.
var arr1=[{id:1,name:'AB'},{id:2,name:'CD'}];
var arr2=[{id:3,name:'EF'},{id:2,name:'CD'}];
// loop over arr2, add the elements of array2 if it doesn't exist in array1
var newArr = arr2.reduce((acc, eachArr2Elem) => {
if (arr1.findIndex((eachArr1Elem) => eachArr1Elem.id === eachArr2Elem.id && eachArr1Elem.name === eachArr2Elem.name) === -1) {
acc.push(eachArr2Elem)
}
return acc
}, [...arr1]); // initialize the new Array with the contents of array1
console.log(newArr)

using the spread operator you can flatten any amount of arrays passed to the combineAndDeDup method, i have also split out some logic methods for (hopefully) more readable code. i hope this helps.
const arr1 = [{id:1,name:'AB'}, {id:2,name:'CD'}]
const arr2 = [{id:3,name:'EF'}, {id:2,name:'CD'}]
const flatten = a => [].concat.apply([], a)
const noDuplicateProps = (a, b) => Object.keys(a).some(k => a[k] === b[k])
const combineAndDeDup = (...arrs) => {
return flatten(arrs).reduce((acc, item) => {
const uniqueItem = acc.findIndex(i => noDuplicateProps(i, item)) === -1
if (uniqueItem) return acc.concat([ item ])
return acc
}, [])
}
const deDuped = combineAndDeDup(arr1, arr2)
const megaDeDuped = combineAndDeDup(arr1, arr2, arr1, arr1, arr2, arr1)
console.log(deDuped)
console.log(megaDeDuped)

If you like clean ES6 try this:
Happy code :)
function arrayWithNoDuplicates(array, field) {
const arrayWithoutNoDuplicates = array.filter((value, index, self) =>
index === self.findIndex((t) => (
t[field] === value[field]
))
)
return arrayWithoutNoDuplicates
}
const arr1 = [{id:1,name:'AB'}, {id:2,name:'CD'}]
const arr2 = [{id:3,name:'EF'}, {id:2,name:'CD'}]
// The first param is the two merged arrays and the second the field you
// to filter by
console.log(arrayWithNoDuplicates([...arr1, ...arr2], 'id'))

By using lodash _.uniqWith(array, [comparator])
var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }, { 'x': 1, 'y': 2 }];
_.uniqWith(objects, _.isEqual);
// => [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }]

var arr1 = [{ id: 1, name: 'AB' }, { id: 2, name: 'CD' }];
var arr2 = [{ id: 3, name: 'EF' }, { id: 3, name: 'XX' }];
arr1.forEach(element => {
arr2.forEach((el, idx) => {
if (element.id === el.id || element.name === el.name) {
delete arr2[idx]
}
});
});
let data = arr1.concat(arr2)
// or arr1 = arr1.concat(arr2)
// then your arr1 contains your unique array
data variable contains your unique array

Here is the one liner that compares by id.
let result = arr1.concat( arr2.filter( i2 => !arr1.find( i1 => i1.id == i2.id ) ) );
I have modified elements to show what happens for different objects with the same id. Swap arr1 with arr2 for which array you want to keep your preferred components. There seems to be no simple way to compare objects in JavaScript, you may use JSON.stringify but that depends on elements order. You can try it out at https://playcode.io/new/ :
var arr1=[{id:1,name:'AB'},{id:2,name:'CD'}];
var arr2=[{id:3,name:'EF'},{id:2,name:'GH'}];
let result;
result = arr1.concat( arr2.filter( i2 => !arr1.find( i1 => i1.id == i2.id ) ) );
console.log('RESULT: ' + JSON.stringify(result));
result = arr1.concat(
arr2.filter(
i2 => !arr1.find(
i1 => {
console.log('I1: ' + JSON.stringify(i1) + ' I2: ' + JSON.stringify(i2));
return i1.id == i2.id;}
)
)
);
console.log('RESULT: ' + JSON.stringify(result));
result = arr2.concat(
arr1.filter(
i1 => !arr2.find(
i2 => {
console.log('I1: ' + JSON.stringify(i1) + ' I2: ' + JSON.stringify(i2));
return i1.id == i2.id;}
)
)
);
console.log('RESULT: ' + JSON.stringify(result));

Both lodash function unionBy and unionWith can solve your problem.
If your objects has unique key, unionBy is the most elegant way to handle it.
var arr1 = [{id:1,name:'AB'},{id:2,name:'CD'}];
var arr2 = [{id:3,name:'EF'},{id:2,name:'CD'}];
var mergedWithoutDups = _.unionBy(arr1, arr2. 'id')
If your object has no unique key, use unionWith and isEqual instead. This will take deep comparison on all objects to remove duplicate.
var arr1 = [{id:1,name:'AB'},{id:2,name:'CD'}];
var arr2 = [{id:3,name:'EF'},{id:2,name:'CD'}];
var mergedWithoutDups = _.unionWith(arr1, arr2. _.isEqual)

Related

Merge two array and if some value true add property? Javascript

I need loop thought two array and return another array with different values.
Example of two arrays:
let arr1 = ['one' , 'two' , 'three'];
let arr2 = ['four' , 'one' , 'two'];
What do I need?
Loop thought both array and return the same value, I expect new array like:
let res = [
{ name : 'one' , isSame: true },
{ name : 'two' , isSame: true },
{ name : 'three' },
{ name : 'four' }
];
I am removed the duplicate items and add isSame value to true on duplicated values.
One and two are duplicated ( twice ).
What I have tried
let arr3 = arr1.map((item, i) =>
Object.assign({}, item, arr2[i])
);
But I got a splitted array and it's removed duplicated
Reduce to an intermediate object and then map that object's entries:
const arr1 = ['one' , 'two' , 'three'];
const arr2 = ['four' , 'one' , 'two'];
const result = Object.entries([...arr1, ...arr2].reduce(
(a, v) => ({ ...a, [v]: v in a }),
{}
)).map(([name, isSame]) => ({ name, isSame }));
console.log(result);
The spreading behavior in the reduce() callback increases time complexity in favor of being more terse, but can easily be avoided, making this solution O(n):
const arr1 = ['one' , 'two' , 'three'];
const arr2 = ['four' , 'one' , 'two'];
const result = Object.entries([...arr1, ...arr2].reduce(
(a, v) => {
a[v] = v in a;
return a;
},
{}
)).map(([name, isSame]) => ({ name, isSame }));
console.log(result);
I would suggest you to use reduce() to do it
let arr1 = ['one' , 'two' , 'three'];
let arr2 = ['four' , 'one' , 'two'];
let arr3 = arr1.concat(arr2)
let result = arr3.reduce((a,c) =>{
let obj = a.find(i => i.name == c)
if(obj){
obj['isSame'] = true
}else{
a.push({'name':c})
}
return a
},[])
console.log(result)
Update:
solution without reduce(),using set() to remove duplicate elements,and using includes() to find duplicate elements
let arr1 = ['one' , 'two' , 'three'];
let arr2 = ['four' , 'one' , 'two'];
let arr3 = [...new Set(arr1.concat(arr2))]
let result = arr3.map(a =>{
let data = {'name':a}
if(arr1.includes(a) && arr2.includes(a)){
data["isSame"] = true
}
return data
})
console.log(result)
Update: Based on OP's comments,show with opposite result
let arr1 = ['one' , 'two' , 'three'];
let arr2 = ['four' , 'one' , 'two'];
let arr3 = [...new Set(arr1.concat(arr2))]
let result = arr3.map(a =>{
let data = {'name':a}
if(!arr1.includes(a) || !arr2.includes(a)){
data["isSame"] = true
}
return data
})
console.log(result)
Since you don't want reduce, you can loop through each item, and check the next few values.
This is natively faster with less function calls, just two for loops.
let arr1 = ['one', 'two', 'three'];
let arr2 = ['four', 'one', 'two'];
console.log(merge(arr1, arr2));
function merge(a, b) {
const merged = a.concat(b); // combine arrays
const result = [];
let stop = merged.length; // create a variable for when to stop
for(let i = 0; i < stop; i++) {
const current = merged[i];
let same = false;
// look through the rest of the array for indexes
for(let t = i + 1; t < stop; t++) {
if(current === merged[t]) {
same = true;
merged.splice(t, 1); // remove duplicate elements from the array so we don't come across it again
stop--; // we've removed an element from the array, so we have to stop 1 earlier
// we don't break this loop because there may be more than 2 occurences
}
}
const out = { name: current };
if(same) out.isSame = true;
result.push(out);
}
return result;
}
I have used map() function of array.
map() returns new array by performing code logic on each valid entry.
[...new Set([...arr1, ...arr2])], this will return new array with unique values.
I am assuming, that you are checking if value is in both array or not and based upon that we are returning object.
let arr1 = ['one', 'two', 'three'];
let arr2 = ['four', 'one', 'two'];
let mergedArray = [...new Set([...arr1, ...arr2])];
let result = mergedArray.map(value => {
if (arr1.includes(value) && arr2.includes(value)) {
return {
value,
isInBothArray: true
}
} else {
return {
value,
isInBothArray: false
}
}
});
console.log(result);
const arr1 = ['one' , 'two' , 'three'];
const arr2 = ['four' , 'one' , 'two'];
// returns the duplicate values in the two arrays
const findDuplicates = (arr) => {
let sorted_arr = arr.slice().sort();
let results = [];
for (let i = 0; i < sorted_arr.length - 1; i++) {
if (sorted_arr[i + 1] == sorted_arr[i]) {
results.push(sorted_arr[i]);
}
}
return results;
}
let values = ([...new Set([...arr1, ...arr2])]);
let duplicates = findDuplicates(values);
let retval = [];
for(let i = 0; i < values.length; i++) {
let isSame = duplicates.includes(values[i]);
retval.push({name: values[i], isSame: isSame})
}
const arr1 = ['one', 'two', 'three'];
const arr2 = ['four', 'one', 'two'];
const result = Array.from(new Set(arr1.concat(arr2))).reduce((p, c) => {
const obj = { name: c };
if (arr1.concat(arr2).join("").split(c).length - 1 > 1) obj.isSame = true;
p.push(obj)
return p;
}, []);
console.log(result);

Find indices of all duplicate records in js

Suppose I have an array as below:
Arr1 = [12,30,30,60,11,12,30]
I need to find index of elements which are repeated in array e.g.
ans: 0,1,2,5,6
I've tried this code but it is considering just single element to check duplicates.
First get all the duplicates using filter() and then using reduce() get he indexes of only those elements of array which are in dups
const arr = [12,30,30,60,11,12,30];
const dups = arr.filter(x => arr.indexOf(x) !== arr.lastIndexOf(x));
const res = arr.reduce((ac, a, i) => {
if(dups.includes(a)){
ac.push(i)
}
return ac;
}, []);
console.log(res)
The time complexity of above algorithm is O(n^2). If you want O(n) you can use below way
const arr = [12,30,30,60,11,12,30];
const dups = arr.reduce((ac, a) => (ac[a] = (ac[a] || 0) + 1, ac), {})
const res = arr.reduce((ac, a, i) => {
if(dups[a] !== 1){
ac.push(i)
}
return ac;
}, []);
console.log(res)
You could use simple indexOf and the loop to get the duplicate indexes.
let arr = [12,30,30,60,11,12,30]
let duplicate = new Set();
for(let i = 0; i < arr.length; i++){
let index = arr.indexOf(arr[i], i + 1);
if(index != -1) {
duplicate.add(i);
duplicate.add(index);
}
}
console.log(Array.from(duplicate).sort().toString());
A slightly different approach with an object as closure for seen items which holds an array of index and the first array, in which later comes the index and a necessary flattening of the values.
This answer is based on the question how is it possible to insert a value into an already mapped value.
This is only possible by using an object reference which is saved at the moment where a value appears and which is not seen before.
Example of unfinished result
[
[0],
[1],
2,
[],
[],
5,
6
]
The final Array#flat removes the covering array and shows only the index, or nothing, if the array remains empty.
[0, 1, 2, 5, 6]
var array = [12, 30, 30, 60, 11, 12, 30],
indices = array
.map((o => (v, i) => {
if (o[v]) { // if is duplicate
o[v][1][0] = o[v][0]; // take the first index as well
return i; // return index
}
o[v] = [i, []]; // save index
return o[v][1]; // return empty array
})({}))
.flat() // remove [] and move values out of array
console.log(indices);
You could use Array#reduce method
loop the array with reduce.At the time find the index of argument
And check the arguments exist more than one in the array using Array#filter
Finaly push the index value to new accumulator array.If the index value already exist in accumalator.Then pass the currentIndex curInd of the array to accumulator
const arr = [12, 30, 30, 60, 11, 12, 30];
let res = arr.reduce((acc, b, curInd) => {
let ind = arr.indexOf(b);
if (arr.filter(k => k == b).length > 1) {
if (acc.indexOf(ind) > -1) {
acc.push(curInd)
} else {
acc.push(ind);
}
}
return acc;
}, []);
console.log(res)
Below code will be easiest way to find indexes of duplicate elements
var dupIndex = [];
$.each(Arr1, function(index, value){
if(Arr1.filter(a => a == value).length > 1){ dupIndex.push(index); }
});
This should work for you

Find elements in an array not contained in another array of objects

I have an array arr1 = [1,2,3,4,5]
There is another array of objects arr2 = [{'id':2, 'name':'A'},{'id':4, 'name':'B'}]
I am looking for find elements in arr1 which are not in arr2. The expected output is [1,3,5]
I tried the following but it doesn't work.
const arr = arr1.filter(i => arr2.includes(i.id));
Can you please help?
A solution with O(arr2.length) + O(arr1.length) complexity in Vanilla JS
var arr1= [1,2,3,4,5];
var arr2 = [{'id':2, 'name':'A'},{'id':4, 'name':'B'}];
var tmp = arr2.reduce(function (acc, obj) {
acc[obj['id']] = true;
return acc;
}, {});
var result = arr1.filter(function(nr) {
return !tmp.hasOwnProperty(nr);
})
arr2 is an array of objects, so arr2.includes(i.id) doesn't work because i (an item from arr1) is a number, which doesn't have an id property, and because arr2 is an array of objects.
Turn arr2's ids into a Set first, then check whether the set contains the item being iterated over:
const arr1 = [1,2,3,4,5];
const arr2 = [{'id':2, 'name':'A'},{'id':4, 'name':'B'}];
const ids = new Set(arr2.map(({ id }) => id));
const filtered = arr1.filter(num => !ids.has(num));
console.log(filtered);
You can try with Array.prototype.some():
The some() method tests whether at least one element in the array passes the test implemented by the provided function. It returns a Boolean value.
const arr1 = [1,2,3,4,5]
const arr2 = [{'id':2, 'name':'A'},{'id':4, 'name':'B'}]
const arr = arr1.filter(i => !arr2.some(j => j.id == i));
console.log(arr);
We can use the filter method like below to check the condition required
var arr1 = [1, 2, 3, 4, 5]
var arr2 = [{ 'id': 2, 'name': 'A' }, { 'id': 4, 'name': 'B' }]
var ids = [];
arr2.forEach(element => {
ids.push(element['id'])
});
var result = arr1.filter(s => ids.indexOf(s) < 0)
console.log(result)
let arr1= [1,2,3,4,5];
let arr2 = [{'id':2, 'name':'A'},{'id':4, 'name':'B'}]
let arr2Ids=arr2.map(item=>item.id);
let result=arr1.filter(n => !arr2Ids.includes(n));
You can use find on arr2 instead of includes since arr2 is composed of object
const arr = arr1.filter(i => !arr2.find(e => e.id===i));

How to group by two array by index in javascript with es6 or lodash?

We have same two arrays to groupby theme by index.
Two arrays with same length and different value like blow.
How to groupby two array with their index by ES6 reduce or lodash?
array1 = [1,2,3,4] OR [{a:1},{b:2},{c:3},{d:4}]
array2 = [5,6,7,8] OR [{e:5},{f:6},{g:7},{h:8}]
finalArray = [[1,5],[2,6],[3,7],[4,8]]
I'm trying with different ways like group by with reduce in es6 or lodash concat but i can't find best solution for my problems.
Try this:
let array1 = [1, 2, 3, 4];
let array2 = [5, 6, 7, 8];
let res = array1.map((value, index) => {
return [value, array2[index]]
})
console.log(res);
If it is array of objects
let array1 = [{a:1},{b:2},{c:3},{d:4}];
let array2 = [{e:5},{f:6},{g:7},{h:8}];
let res = array1.map((value, index) => {
return [Object.values(value)[0],Object.values(array2[index])[0]]
})
console.log(res)
Use lodashes zip function
// _ is lodash
const array1 = [1,2,3,4]
const array2 = [5,6,7,8]
console.log(_.zip(array1, array2))
result
[ [ 1, 5 ], [ 2, 6 ], [ 3, 7 ], [ 4, 8 ] ]
If you are working with the array of objects. Get just the values using Object.values and grab the 0th element.
const array3 = [{a:1},{b:2},{c:3},{d:4}];
const array4 = [{e:5},{f:6},{g:7},{h:8}];
function firstval(ob){
return Object.values(ob)[0]
}
console.log(_.zip(array3.map(firstval), array4.map(firstval)))
You can also write your own zip. This is a limited version. That handles only 2 elements, doesn't accept or return generators etc.
It could easily be extended to take a spread operator and therefore any number of arguments. You don't seem to need that level of flexibility though.
function zip(a, b) {
const num = Math.min(a.length, b.length);
const result = [];
for(i = 0; i < num; i++) result.push([a[i], b[i]]);
return result;
}
Following code works under these assumptions:
all input arrays have same length
if array element is object, it has only one property
function getValue(element) {
if (typeof element === 'object') {
return Object.values(element).pop()
} else {
return element
}
}
function zipArrays(arr1, arr2) {
return arr1.reduce((acc, elm1, index) => {
const elm2 = arr2[index]
const elm = [getValue(elm1), getValue(elm2)]
acc.push(elm)
return acc
}, [])
}
// usage:
const array1 = [1,2,3,4] // OR [{a:1},{b:2},{c:3},{d:4}]
const array2 = [5,6,7,8] // OR [{e:5},{f:6},{g:7},{h:8}]
const finalArray = zipArrays(array1, array2)

What is the most elegant way to insert objects between array elements?

I'm sure there are many ways to achieve that but I'm looking for something "elegant".
a = [
'a',
'b',
'c'
];
magicArrayJoin(a, {value: 255} ); // insert the same object between each item
result == [
'a',
{value: 255},
'b',
{value: 255}
'c'
];
All proposals are welcome. :)
One-liner using plain ES6:
const interleave = (arr, thing) => [].concat(...arr.map(n => [n, thing])).slice(0, -1)
Usage:
interleave(['foo', 'bar', 'baz'], 'avocado')
Prints:
> ["foo", "avocado", "bar", "avocado", "baz"]
You can do it with flatMap. It can be found from lodash for example
_.flatMap([1,2,3,4], (value, index, array) =>
array.length -1 !== index // check for the last item
? [value, "s"]
: value
);
ouputs
[1, "s", 2, "s", 3, "s", 4]
Update
Array#flatMap proposal is in the works so in future this should work:
[1, 2, 3, 4].flatMap(
(value, index, array) =>
array.length - 1 !== index // check for the last item
? [value, "s"]
: value,
);
In my opinion the most elegant way to do this is the following one:
ES6 syntax version
const insertIntoArray = (arr, value) => {
return arr.reduce((result, element, index, array) => {
result.push(element);
if (index < array.length - 1) {
result.push(value);
}
return result;
}, []);
};
Usage:
insertIntoArray([1, 2, 3], 'x'); // => [1, 'x', 2, 'x', 3]
An ordinary loop seems to be the best:
function intersperse(arr, el) {
var res = [], i=0;
if (i < arr.length)
res.push(arr[i++]);
while (i < arr.length)
res.push(el, arr[i++]);
return res;
}
If you're looking for something elegant, it would probably have to use some kind of concatMap, as in
function concatMap(arr, fn) { return [].concat.apply([], arr.map(fn)); }
function intersperse(arr, el) { return concatMap(arr, x => [el, x]).slice(1); }
Use ES6 flatMap function.
const insertBetween = (ele, array) => {
return array.flatMap((x) => [ele, x]).slice(1);
};
insertBetween('+', [1, 2, 3]);
Immutable solution
When reducing an array the reduce function should not mutate the array but return a new value (in this case a new array). That way the changes will be only applied to the returned array and not the original one and side effects will be avoided.
const insertBetween = (insertee, array) => array.reduce(
(acc, item, i, { length }) => {
if (i && i < length) {
return [...acc, insertee, item];
}
return [...acc, item];
},
[]
);
Ramda has intersperse method that:
Creates a new list with the separator interposed between elements.
Code:
R.intersperse({name: 'separator'}, ['one', 'two', 'three']);
Result:
[
'one',
{name: 'separator'},
'two',
{name: 'separator'},
'three'
]
This worked for me:
a.map(val = [val, {value: 255}]).flat()
You can achieve this using reduce (it is also immutable).
const insertBetween = (insertion, array) =>
array.reduce(
(newArray, member, i, array) =>
i < array.length - 1
? newArray.concat(member, insertion)
: newArray.concat(member),
[]
);
const result = insertBetween('and', [1, 2, 3]);
console.log(result);
// outputs;
// [
// 1,
// 'and',
// 2,
// 'and',
// 3
// ]
Or in older JS syntax;
function insertBetween(insertion, array) {
const indexOfLastItem = array.length - 1;
return array.reduce(withInsertion, []);
function withInsertion(newArray, item, index, array) {
return index < indexOfLastItem
? newArray.concat(item, insertion)
: newArray.concat(item);
}
}
const result = insertBetween('and', [1, 2, 3]);
console.log(result);
// outputs;
// [
// 1,
// 'and',
// 2,
// 'and',
// 3
// ]
I really in favor of #Vidul 's comments, which is very logical and concise! I myself also came up with splice(), but missed %. However, most of the braces seem unnecessary as an oneliner. It can be further simplified as
for (var i = 0; i < a.length; i++) if (i % 2) a.splice(i, 0, {value: 255});
function insertObject(arr, obj) {
var result = [];
function insert(element, index) {
result.push(element);
if (index + 1 < arr.length) {
result.push(obj);
}
}
arr.forEach(insert);
return result;
}
var a = [1, 2, 3, 4];
insertObject(a, {
test: 'test'
});
Using splice as Kamen suggests, you could do something like:
const numSeparators = arr.length - 1;
for (let i = 1; i <= numSeparators; i++) {
const index = (i * 2) - 1;
arr.splice(index, 0, { value: 255 });
}
for a simple purely functional way I suggest doing it this way:
const magicArrayJoin = (array, el) =>
array.length ?
array.slice(1).reduce((acc, cur) => acc.concat([el, cur]), [array[0]]) :
[]
p.n. this way is not the most performant one in javascript
ES6:
const arrayWithSeparator = array.reduce((a, i) => a.length ? a.push(separator) && a.push(i) && a : a.push(u) && a, [])
Array.splice() should do the job like so:
a.splice(1, 0, {value : 255})
The first argument is the position at which you want to delete or insert elements, the second is the delete count, the third (optional) is the new element[s] you want to insert.

Categories

Resources