JS - Merge arrays that share at least one common value - javascript

If I have the following array of arrays:
var myArr = [[0, 1, 2], [1, 2, 6], [9, 10], [10, 11], [11, 12], [13]];
How can I merge the arrays that share at least one common value to produce the following output?
var myMergedArr = [[0, 1, 2, 6], [9, 10, 11, 12], [13]];
Thanks!
NOTE: They won't always be nicely ordered and the shared values may not always be the starting/ending values when everything is ordered. I have ordered the above values for clarity.

The array can be reduced with an empty array (merged) as the starting value. For each array in myArray, existing is defined as an array of subArrays of merged such that the intersection of each subArray and array is not empty.
If no such array could be found, existing will remain undefined and a new array (contained in another array) is being defined as existing and pushed to merged.
If multiple matches are found (existing.slice(1) is not empty), they need to be merged together: existing[0] acts as the container where all the other sub-arrays (existing[1..]) get merged (without duplicates). Then those further matches need to be found in merged and removed, as they have been merged already. This guarantees multiple arrays to be merged if they belong together, even if they weren’t merged earlier.
Then, each item of array is pushed to existing[0] if it isn’t included yet. Finally, merged is returned. Then the next iteration of reduce can take merged again as the first argument and continue with the next array in myArr.
This is the ES6 code for that. You can transpile and polyfill it to ES5, if you need to.
var myArr = [
[0, 1, 2],
[1, 2, 6],
[9, 10],
[10, 11],
[11, 12],
[13]
],
myMergedArr = myArr.reduce((merged, array) => {
let existing = merged.filter((subArray) => subArray.filter((subItem) => array.includes(subItem)).length);
if (!existing.length) {
existing = [
[]
];
merged.push(existing[0]);
}
else {
existing.slice(1).forEach((furtherArray) => {
furtherArray.forEach((item) => {
if (!existing[0].includes(item)) {
existing[0].push(item);
}
});
merged.splice(merged.findIndex((subArray) => furtherArray == subArray), 1);
});
}
array.forEach((item) => {
if (!existing[0].includes(item)) {
existing[0].push(item);
}
});
return merged;
}, []);
console.log(myMergedArr);
This second snippet is the same code but with a changed array. This is to demonstrate that this script will work, even if the sub-arrays aren’t in order: first [0, 1, 2] is on its own, then [3, 4, 5] is also on its own — both still separated. Only later [2, 3] causes all previous arrays to merge into one.
var myArr = [
[0, 1, 2],
[3, 4, 5],
[2, 3],
[7, 9],
[9, 10],
[13]
],
myMergedArr = myArr.reduce((merged, array) => {
let existing = merged.filter((subArray) => subArray.filter((subItem) => array.includes(subItem)).length);
if (!existing.length) {
existing = [
[]
];
merged.push(existing[0]);
}
else {
existing.slice(1).forEach((furtherArray) => {
furtherArray.forEach((item) => {
if (!existing[0].includes(item)) {
existing[0].push(item);
}
});
merged.splice(merged.findIndex((subArray) => furtherArray == subArray), 1);
});
}
array.forEach((item) => {
if (!existing[0].includes(item)) {
existing[0].push(item);
}
});
return merged;
}, []);
console.log(myMergedArr);

A disjoint-set data structure seems perfect for your case:
function merge(arrays) {
var ds = new DisjointSet();
arrays.forEach(function(array) {
array.reduce(function(prevSet, currentValue) {
var currentSet = ds.getSet(currentValue);
if(prevSet) prevSet.mergeWith(currentSet);
return currentSet;
}, false);
});
return ds.partitions();
}
var myArr = [[0, 1, 2], [1, 2, 6], [9, 10], [10, 11], [11, 12], [13]];
console.log(JSON.stringify(merge(myArr)));
<script> /* DisjointSet library */
class MySet {
constructor(owner) {
this.rank = 0;
this.parent = this;
this.owner = owner;
}
representative() {
var parent = this.parent;
if(this === parent) return this;
while(parent !== (parent = parent.parent));
this.parent = parent; /* Path compression */
return parent;
}
mergeWith(other) {
var r1 = this.representative(),
r2 = other.representative();
if(r1 === r2) return;
if(r1.owner !== r2.owner) throw new Error("Can't merge");
if(r1.rank > r2.rank) { r2.parent = r1; return; }
r1.parent = r2;
if(r1.rank === r2.rank) ++r1.rank;
}
}
class DisjointSet {
constructor() {
this.sets = new Map();
}
getSet(value) {
var sets = this.sets;
var set = sets.get(value);
if(set) return set;
set = new MySet(this);
sets.set(value, set);
return set;
}
partitions() {
var parts = new Map();
for(var [value,set] of this.sets) {
var repre = set.representative();
var arr = parts.get(repre);
if(arr) arr.push(value);
else parts.set(repre, [value]);
}
return [...parts.values()];
}
}
</script>
Assuming constant map operations, the amortized time cost should be only O(n α(n)) ≈ O(n).
the amortized time per operation is only O(α(n)), where α(n) [...]
is less than 5 for all remotely practical values of n. Thus, the
amortized running time per operation is effectively a small constant.
Note I used ES6 maps to be able to associate each value with its set. This is not needed if all values are numbers, you could then store them as object properties. But in partitions you will need to extract the value associated with a set, and storing that data will require more memory.
function merge(arrays) {
var ds = new DisjointSet();
arrays.forEach(function(array) {
array.reduce(function(prevSet, currentValue) {
var currentSet = ds.getSet(currentValue);
if(prevSet) prevSet.mergeWith(currentSet);
return currentSet;
}, false);
});
return ds.partitions();
}
var myArr = [[0, 1, 2], [1, 2, 6], [9, 10], [10, 11], [11, 12], [13]];
console.log(JSON.stringify(merge(myArr)));
<script> /* DisjointSet library */
function MySet(value, owner) {
this.rank = 0;
this.parent = this;
this.value = value;
this.owner = owner;
}
MySet.prototype.representative = function() {
var parent = this.parent;
if(this === parent) return this;
while(parent !== (parent = parent.parent));
this.parent = parent; /* Path compression */
return parent;
};
MySet.prototype.mergeWith = function(other) {
var r1 = this.representative(),
r2 = other.representative();
if(r1 === r2) return;
if(r1.owner !== r2.owner) throw new Error("Can't merge");
if(r1.rank > r2.rank) { r2.parent = r1; return; }
r1.parent = r2;
if(r1.rank === r2.rank) ++r1.rank;
};
function DisjointSet() {
this.sets = Object.create(null);
}
DisjointSet.prototype.getSet = function(value) {
var sets = this.sets;
var set = sets[value];
if(set) return set;
set = new MySet(value, this);
sets[value] = set;
return set;
};
DisjointSet.prototype.partitions = function() {
var parts = [];
var assoc = Object.create(null);
var sets = this.sets;
Object.getOwnPropertyNames(sets).forEach(function(value) {
var set = sets[value];
var repreValue = set.representative().value;
var arr = assoc[repreValue];
if(arr) arr.push(set.value);
else parts.push(assoc[repreValue] = [set.value]);
});
return parts;
};
</script>

I believe the problem can be solved with a fairly simple functional piece of code as follows;
function merger(arr){
return arr.map((e,i,a) => a.slice(i)
.reduce((p,c) => e.some(n => c.includes(n)) ? [...new Set([...p,...c])] : p,[]))
.reduce((r,s) => { var merged = false;
r = r.map(a => a.some(n => s.includes(n)) ? (merged = true, [...new Set([...a,...s])]) : a);
!merged && r.push(s);
return r;
},[]);
}
var arr1 = [[0, 1, 2], [1, 2, 6], [9, 10], [10, 11], [11, 12], [13]],
arr2 = [[0, 1], [2, 3], [1, 2]];
console.log(merger(arr1));
console.log(merger(arr2));
A little explanation on the critical part. Using Set object hand to hand with Array object is very easy with the help of spread operator. So the following piece might look a little confusing but it does an important job.
[...new Set([...a,...s])]
Let's assume a is an array or set and s is another array or set. Then [...a,...s] makes an array of the two merged into. new Set([...a,...s]) makes a new set by removing the dupes in the merged array. [...new Set([...a,...s])] turns our set back into an array of a and b concatenated and dupes removed. Cool..!
a.some(n => s.includes(n))
If array a and array s have at least one common item returns true else false.

Related

How to combine multiple "equal to"'s in javaScript if condition?

If got a number of values which I want to compare (they all have to be the same).
The function e(row,col) returns an attribute of an element in a grid.
Is it possible to write something like…
if(e(1,2)==e(2,2)==e(3,2)==e(4,2)){...}
…instead of …
if(e(1,2)==e(2,2)&&e(2,2)==e(3,2)&&e(3,2)==e(4,2)){…}
?
I'd make an array of parameters to pass to the function, then get the result of the first call, then check that .every one of the other array pairs are equal to the first:
const params = [
[1, 2],
[2, 2],
[3, 2],
[4, 2],
];
const res = e(...params[0]);
if (params.slice(1).every(args => e(...args) === res)) {
// do stuff
}
Or use a loop, if the indicies are this predictable:
const res = e(1, 2);
let allMatch = true;
for (let i = 2; i <= 4; i++) {
if (e(1, i) !== res) {
allMatch = false;
break;
}
}
if (allMatch) {
// do stuff
}

Check array overlapping in JavaScript

I have some arrays like [1,5], [3,6], [2,8],[19,13], [12,15]. When i pass two arrays in the function output will be [1,6], [2,19],[12,15]
i want to remove overlapping numbers from 2 arrays . like on fist and second array 5 and 3 will be overlap between 1 to 6.
I believe this is what you want (you get the min of the first array and the max of the second array):
function removeOverlap(arr1, arr2) {
if (arr1 === undefined) {
return arr2;
}
if (arr2 === undefined) {
return arr1;
}
return [Math.min.apply(null, arr1), Math.max.apply(null, arr2)];
}
// Sample:
var myArrays = [[1,5], [3,6], [2,8], [19,13], [12,15]];
for (var i = 0; i < myArrays.length; i = i + 2) {
console.log(removeOverlap(myArrays[i], myArrays[i + 1]));
}
EDIT: answer with multiple parameters as you requested in your comment:
We could use rest parameters in the answer below, but I will use the arguments object for compatibility with Internet Explorer. If this is not a requirement you can adapt the solution to use the first.
function removeOverlap(arr1, arr2) {
// Converting the arguments object to array:
var argsArray = Array.prototype.slice.call(arguments);
// Removing undefined:
argsArray = argsArray.filter(function(el) {
return el != undefined;
});
// Alternative (not compatible with Internet Explorer):
//argsArray = argsArray.filter(el => el);
// We're looking for the min and max numbers, let's merge the arrays
// e.g. transform [[1, 5], [3, 6], [2, 8]] into [1, 5, 3, 6, 2, 8]
var merged = [].concat.apply([], argsArray);
// Alternative, but it is not compatible with Internet Explorer:
//var merged = Array.flat(argsArray);
return [Math.min.apply(null, merged), Math.max.apply(null, merged)];
}
// Sample:
var myArrays = [[1,5], [3,6], [2,8], [19,13], [12,15]];
for (var i = 0; i < myArrays.length; i = i + 2) {
console.log(removeOverlap(myArrays[i], myArrays[i + 1]));
}
console.log(removeOverlap(myArrays[0], myArrays[1], myArrays[2]));
This can easily be accomplished my finding the min of the current and max of the next item.
let initial = [ [1, 5], [3, 6], [2, 8], [19, 13], [12, 15] ]
let expected = [ [1, 6], [2, 19], [12, 15] ]
let actual = calculateOverlaps(initial);
console.log(JSON.stringify(actual) === JSON.stringify(expected)); // true
function calculateOverlaps(arr) {
let result = [];
for (let i = 0; i < arr.length; i+=2) {
if (i >= arr.length - 1) {
result.push(arr[i]); // If the array has an odd size, get last item
} else {
let curr = arr[i];
let next = arr[i + 1];
result.push([ Math.min(...curr), Math.max(...next) ]);
}
}
return result;
}
Here is a more code-golf oriented function:
const calculateOverlaps1 = (arr) => arr.reduce((r, e, i, a) =>
(i % 2 === 0)
? r.concat([
(i < a.length - 1)
? [ Math.min(...e), Math.max(...a[i+1]) ]
: e
])
: r, []);
And even smaller, at just 101 bytes.
f=a=>a.reduce((r,e,i)=>i%2===0?r.concat([i<a.length-1?[Math.min(...e),Math.max(...a[i+1])]:e]):r,[]);

How can i check if a object is in my array?

I'm developing an annual permit calculator for work. I have to detect legal holidays. So I add all the legal holidays in my array. If date is equal to other array, it should return true. But the code says these two objects below are not equal. I couldn't figured out why.
var legalHolidays = [[1, 1, 2019], [23, 4, 2019], [1, 5, 2019], [19, 5, 2019], [3, 6, 2019]]
var otherArray = [1, 1, 2019]
if (legalHolidays[0] == otherArray) {console.log(true)}else{console.log(false)}
I expect the output of false to be true.
But the code says these two objects below are not equal. I couldn't figured out why.
They are not equal because they are instances of different arrays that happen to have same values. In order to bypass this, one thing you could do is to convert the arrays into json strings and compare them instead:
var legalHolidays = [[1, 1, 2019], [23, 4, 2019], [1, 5, 2019], [19, 5, 2019], [3, 6, 2019]];
var otherArray = [1, 1, 2019];
// the date arrays are converted to JSON strings. This enables the arrays
// to be compared by their respective string representations, avoiding
// the problem of having to deal with the different references
const match = legalHolidays.find(holiday => JSON.stringify(holiday) === JSON.stringify(otherArray));
console.log(`Is found: ${!!match}`);
NOTE:
Converting the arrays to Json strings is not the only way of comparing them. Essentially, you need to reduce the problem to comparing primitives (which are compared by value, rather than by reference as it is done in case of objects).
== is comparing the reference to the actual array instances, not the value of the array. You could create a function that converts to Date strings and compares them:
const otherArray = [1, 1, 2019];
var legalHolidays = [
[1, 1, 2019],
[23, 4, 2019],
[1, 5, 2019],
[19, 5, 2019],
[3, 6, 2019]
]
function isSameDate(holidayItem, comparisonItem) {
const [d1, m1, y1] = holidayItem;
const [d2, m2, y2] = comparisonItem;
if (new Date(y1, (m1 - 1), d1).toString() === new Date(y2, (m2 - 1), d2).toString()) {
return true;
}
return false;
}
legalHolidays.forEach(holiday => {
console.log(`Matches otherDate: ${isSameDate(holiday, otherArray)}`);
});
JavaScript compares arrays by reference when using equality operators, so if they are not the exact same object it will return false. You must either use JSON.stringify() to compare the string versions of the arrays:
if (JSON.stringify(legalHolidays[0]) == JSON.stringify(otherArray)) {
console.log(true);
} else {
console.log(false);
}
or manually compare the arrays at each index. In this example I use the every function:
const isLegal = legalHolidays[0].every((el, idx) => el === otherArray[idx]);
console.log(isLegal);
This is because == and === only compare by reference. What you would need to do is to check every element of otherArray against all the elements in the arrays contained in legalHolidays?
To do so, you can use [].some, which
... tests whether at least one element in the array passes the test implemented by the provided function. It returns a Boolean value.
and a shallow equal function that compares two arrays elementwise. e.g. the one found in this answer
const legalHolidays = [
[1, 1, 2019],
[23, 4, 2019],
[1, 5, 2019],
[19, 5, 2019],
[3, 6, 2019]
]
const otherArray = [1, 1, 2019]
const shallowEqual = (a, b) => {
if (a === b) return true;
if (a == null || b == null) return false;
if (a.length != b.length) return false;
// If you don't care about the order of the elements inside
// the array, you should sort both arrays here.
// Please note that calling sort on an array will modify that array.
// you might want to clone your array first.
for (var i = 0; i < a.length; ++i) {
if (a[i] !== b[i]) return false;
}
return true;
}
const contains = (arrA, arrB) => arrA.some((arr) => {
return shallowEqual(arr, arrB);
});
const contained = contains(legalHolidays, otherArray);
console.log(contained)
Please do not use the shallowEqual function provided in this answer, as it is not a complete implementation. It fails if you pass it something that is not array.
Try this:
var x = legalHolidays.filter(function (elem) {
return elem[0] == otherArray[0] && elem[1] == otherArray[1] && elem[2] == otherArray[2];
});
if(x.length > 0) {
// otherArray is present in legalHolidays
}
Or more efficient,
var x = legalHolidays.find(function (elem) {
return elem[0] == otherArray[0] && elem[1] == otherArray[1] && elem[2] == otherArray[2];
});
if(x) {
// otherArray is present in legalHolidays
}
Or if the dates are in sorted order, try binary search (time complexity O(logn)):
function findDate(date, arrayOfDates) {
var start = 0;
var end = arrayOfDates.length-1;
while(start < end) {
var mid = parseInt(start + (end - start)/2);
var elem = arrayOfDates[mid];
if(elem[0] == otherArray[0] && elem[1] == otherArray[1] && elem[2] == otherArray[2]) {
return mid;
}
if(elem[2] > date[2]) {
start = mid + 1;
}
else if(elem[2] < date[2]) {
end = mid - 1;
}
else if(elem[1] > date[1]) {
start = mid + 1;
}
else if(elem[1] < date[1]) {
end = mid - 1;
}
else if(elem[0] > date[0]) {
start = mid + 1;
}
else {
end = mid - 1;
}
}
}
console.log(findDate(otherArray, legalHolidays));
Little bit of more code, but efficient.
Furthermore, if all dates in legalHolidays are of the same year, you can skip the first 2 ifs in the while loop.
You have an array of tuples. (A tuple is an array of a well-defined size in which each index has a specific meaning.) Each of your tuples has has three elements. This is why you can destrucuture your tuple via const [foo, bar, baz] = myArray;
Then you can find your existing array rather straighforward by comparing each of its values:
const hasDay = (haystack, needle) => {
return haystack.some((holiday) => {
const [day1, month1, year1] = holiday;
const [day2, month2, year2] = needle;
return day1 === day2 && month1 === month2 && year2 === year1;
})
}
const legalHolidays = [[1, 1, 2019], [23, 4, 2019], [1, 5, 2019], [19, 5, 2019], [3, 6, 2019]]
console.log(
hasDay(legalHolidays, [1, 1, 2019]), // will be true
hasDay(legalHolidays, [1, 2, 2019]) // will be false
)

Array of Arrays to an Array of Unique Values Using Reduce/Map

I have an array of arrays, that needs to become 1 array of unique values.
[1, 3, 2], [5, 2, 1, 4], [2, 1]
I want to use reduce/map to solve the problem, but it doesn't seem to be working. I have solved the problem already with nested for loops like so:
function uniteUnique(arr) {
var args = Array.from(arguments);
var arr = [];
for (var i = 0; i < args.length; i++) {
for (var j = 0; j < args[i].length; j++) {
if (!arr.includes(args[i][j])) {
arr.push(args[i][j]);
}
}
}
return arr;
}
Now I tried to solve the problem here using reduce/map, but not getting the correct solution, like so:
function uniteUnique(arr) {
var args = Array.from(arguments);
return args.reduce(
(arr, a) => a.map(n => (!arr.includes(n) ? arr.push(n) : n)),
[]
);
}
console.log(uniteUnique([1, 3, 2], [5, 2, 1, 4], [2, 1]));
I also tried to solve with reduce/map, using the older syntax, like so:
function uniteUnique(arr) {
var args = Array.from(arguments);
return args.reduce(function(arr, a) {
return a.map(function(n) {
if (!arr.includes(n)) {
return arr.push(n);
} else {
return n;
}
});
});
}
My guess is that I'm not doing something right with the return statements in the callback functions. Any help would be appreciated, thanks.
The problem is that:
arr.includes(n)
arr is an array of arrays, includes wont work there. You also never pass arr down the reduce chain.
The easiest to solve would be:
[...new Set(array.reduce((a, b) => a.concat(b), []))]
That just flattens the array, builds a Set for uniqueness and spreads it into an array. Or another elegant solution usong iterators:
function* flatten(arr) {
for(const el of arr) {
if(Array.isArray(el)) {
yield* flatten(el);
} else {
yield el;
}
}
}
const result = [];
for(const el of flatten(array))
if(!result.includes(el)) result.push(el);
Instead of using array#map use array#forEach and push unique number in the accumulator.
function uniteUnique(arr) {
var args = Array.from(arguments);
return args.reduce((arr, a) => {
a.forEach(n => (!arr.includes(n) ? arr.push(n) : n));
return arr
},[]);
}
console.log(uniteUnique([1, 3, 2], [5, 2, 1, 4], [2, 1]));
Alternatively, you can array#concat all the array and then using Set get the unique value.
const arr = [[1, 3, 2], [5, 2, 1, 4], [2, 1]],
unique = [...new Set([].concat(...arr))];
console.log(unique);

Trying to solve symmetric difference using Javascript

I am trying to figure out a solution for symmetric
difference using javascript that accomplishes the following
objectives:
accepts an unspecified number of arrays as arguments
preserves the original order of the numbers in the arrays
does not remove duplicates of numbers in single arrays
removes duplicates occurring across arrays
Thus, for example,
if the input is ([1, 1, 2, 6], [2, 3, 5], [2, 3, 4]),
the solution would be, [1, 1, 6, 5, 4].
I am trying to solve this as challenge given by an online
coding community. The exact instructions of the challenge
state,
Create a function that takes two or more arrays and returns an array
of the symmetric difference of the provided arrays.
The mathematical term symmetric difference refers to the elements in
two sets that are in either the first or second set, but not in both.
Although my solution below finds the numbers that are
unique to each array, it eliminates all numbers occuring
more than once and does not keep the order of the numbers.
My question is very close to the one asked at finding symmetric difference/unique elements in multiple arrays in javascript. However, the solution
does not preserve the original order of the numbers and does not preserve duplicates of unique numbers occurring in single arrays.
function sym(args){
var arr = [];
var result = [];
var units;
var index = {};
for(var i in arguments){
units = arguments[i];
for(var j = 0; j < units.length; j++){
arr.push(units[j]);
}
}
arr.forEach(function(a){
if(!index[a]){
index[a] = 0;
}
index[a]++;
});
for(var l in index){
if(index[l] === 1){
result.push(+l);
}
}
return result;
}
symsym([1, 1, 2, 6], [2, 3, 5], [2, 3, 4]); // => Desired answer: [1, 1, 6. 5. 4]
As with all problems, it's best to start off writing an algorithm:
Concatenate versions of the arrays, where each array is filtered to contain those elements which no array other than the current one contains
Then just write that down in JS:
function sym() {
var arrays = [].slice.apply(arguments);
return [].concat.apply([], // concatenate
arrays.map( // versions of the arrays
function(array, i) { // where each array
return array.filter( // is filtered to contain
function(elt) { // those elements which
return !arrays.some( // no array
function(a, j) { //
return i !== j // other than the current one
&& a.indexOf(elt) >= 0 // contains
;
}
);
}
);
}
)
);
}
Non-commented version, written more succinctly using ES6:
function sym(...arrays) {
return [].concat(arrays .
map((array, i) => array .
filter(elt => !arrays .
some((a, j) => i !== j && a.indexOf(elt) >= 0))));
}
Here's a version that uses the Set object to make for faster lookup. Here's the basic logic:
It puts each array passed as an argument into a separate Set object (to faciliate fast lookup).
Then, it iterates each passed in array and compares it to the other Set objects (the ones not made from the array being iterated).
If the item is not found in any of the other Sets, then it is added to the result.
So, it starts with the first array [1, 1, 2, 6]. Since 1 is not found in either of the other arrays, each of the first two 1 values are added to the result. Then 2 is found in the second set so it is not added to the result. Then 6 is not found in either of the other two sets so it is added to the result. The same process repeats for the second array [2, 3, 5] where 2 and 3 are found in other Sets, but 5 is not so 5 is added to the result. And, for the last array, only 4 is not found in the other Sets. So, the final result is [1,1,6,5,4].
The Set objects are used for convenience and performance. One could use .indexOf() to look them up in each array or one could make your own Set-like lookup with a plain object if you didn't want to rely on the Set object. There's also a partial polyfill for the Set object that would work here in this answer.
function symDiff() {
var sets = [], result = [];
// make copy of arguments into an array
var args = Array.prototype.slice.call(arguments, 0);
// put each array into a set for easy lookup
args.forEach(function(arr) {
sets.push(new Set(arr));
});
// now see which elements in each array are unique
// e.g. not contained in the other sets
args.forEach(function(array, arrayIndex) {
// iterate each item in the array
array.forEach(function(item) {
var found = false;
// iterate each set (use a plain for loop so it's easier to break)
for (var setIndex = 0; setIndex < sets.length; setIndex++) {
// skip the set from our own array
if (setIndex !== arrayIndex) {
if (sets[setIndex].has(item)) {
// if the set has this item
found = true;
break;
}
}
}
if (!found) {
result.push(item);
}
});
});
return result;
}
var r = symDiff([1, 1, 2, 6], [2, 3, 5], [2, 3, 4]);
log(r);
function log(x) {
var d = document.createElement("div");
d.textContent = JSON.stringify(x);
document.body.appendChild(d);
}
One key part of this code is how it compares a given item to the Sets from the other arrays. It just iterates through the list of Set objects, but it skips the Set object that has the same index in the array as the array being iterated. That skips the Set made from this array so it's only looking for items that exist in other arrays. That allows it to retain duplicates that occur in only one array.
Here's a version that uses the Set object if it's present, but inserts a teeny replacement if not (so this will work in more older browsers):
function symDiff() {
var sets = [], result = [], LocalSet;
if (typeof Set === "function") {
try {
// test to see if constructor supports iterable arg
var temp = new Set([1,2,3]);
if (temp.size === 3) {
LocalSet = Set;
}
} catch(e) {}
}
if (!LocalSet) {
// use teeny polyfill for Set
LocalSet = function(arr) {
this.has = function(item) {
return arr.indexOf(item) !== -1;
}
}
}
// make copy of arguments into an array
var args = Array.prototype.slice.call(arguments, 0);
// put each array into a set for easy lookup
args.forEach(function(arr) {
sets.push(new LocalSet(arr));
});
// now see which elements in each array are unique
// e.g. not contained in the other sets
args.forEach(function(array, arrayIndex) {
// iterate each item in the array
array.forEach(function(item) {
var found = false;
// iterate each set (use a plain for loop so it's easier to break)
for (var setIndex = 0; setIndex < sets.length; setIndex++) {
// skip the set from our own array
if (setIndex !== arrayIndex) {
if (sets[setIndex].has(item)) {
// if the set has this item
found = true;
break;
}
}
}
if (!found) {
result.push(item);
}
});
});
return result;
}
var r = symDiff([1, 1, 2, 6], [2, 3, 5], [2, 3, 4]);
log(r);
function log(x) {
var d = document.createElement("div");
d.textContent = JSON.stringify(x);
document.body.appendChild(d);
}
I came across this question in my research of the same coding challenge on FCC. I was able to solve it using for and while loops, but had some trouble solving using the recommended Array.reduce(). After learning a ton about .reduce and other array methods, I thought I'd share my solutions as well.
This is the first way I solved it, without using .reduce.
function sym() {
var arrays = [].slice.call(arguments);
function diff(arr1, arr2) {
var arr = [];
arr1.forEach(function(v) {
if ( !~arr2.indexOf(v) && !~arr.indexOf(v) ) {
arr.push( v );
}
});
arr2.forEach(function(v) {
if ( !~arr1.indexOf(v) && !~arr.indexOf(v) ) {
arr.push( v );
}
});
return arr;
}
var result = diff(arrays.shift(), arrays.shift());
while (arrays.length > 0) {
result = diff(result, arrays.shift());
}
return result;
}
After learning and trying various method combinations, I came up with this that I think is pretty succinct and readable.
function sym() {
var arrays = [].slice.call(arguments);
function diff(arr1, arr2) {
return arr1.filter(function (v) {
return !~arr2.indexOf(v);
});
}
return arrays.reduce(function (accArr, curArr) {
return [].concat( diff(accArr, curArr), diff(curArr, accArr) )
.filter(function (v, i, self) { return self.indexOf(v) === i; });
});
}
That last .filter line I thought was pretty cool to dedup an array. I found it here, but modified it to use the 3rd callback parameter instead of the named array due to the method chaining.
This challenge was a lot of fun!
// Set difference, a.k.a. relative compliment
const diff = (a, b) => a.filter(v => !b.includes(v))
const symDiff = (first, ...rest) =>
rest.reduce(
(acc, x) => [
...diff(acc, x),
...diff(x, acc),
],
first,
)
/* - - - */
console.log(symDiff([1, 3], ['Saluton', 3])) // [1, 'Saluton']
console.log(symDiff([1, 3], [2, 3], [2, 8, 5])) // [1, 8, 5]
Just use _.xor or copy lodash code.
Another simple, yet readable solution:
/*
This filters arr1 and arr2 from elements which are in both arrays
and returns concatenated results from filtering.
*/
function symDiffArray(arr1, arr2) {
return arr1.filter(elem => !arr2.includes(elem))
.concat(arr2.filter(elem => !arr1.includes(elem)));
}
/*
Add and use this if you want to filter more than two arrays at a time.
*/
function symDiffArrays(...arrays) {
return arrays.reduce(symDiffArray, []);
}
console.log(symDiffArray([1, 3], ['Saluton', 3])); // [1, 'Saluton']
console.log(symDiffArrays([1, 3], [2, 3], [2, 8, 5])); // [1, 8, 5]
Used functions: Array.prototype.filter() | Array.prototype.reduce() | Array.prototype.includes()
function sym(arr1, arr2, ...rest) {
//creating a array which has unique numbers from both the arrays
const union = [...new Set([...arr1,...arr2])];
// finding the Symmetric Difference between those two arrays
const diff = union.filter((num)=> !(arr1.includes(num) && arr2.includes(num)))
//if there are more than 2 arrays
if(rest.length){
// recurrsively call till rest become 0
// i.e. diff of 1,2 will be the first parameter so every recurrsive call will reduce // the arrays till diff between all of them are calculated.
return sym(diff, rest[0], ...rest.slice(1))
}
return diff
}
Create a Map with a count of all unique values (across arrays). Then concat all arrays, and filter non unique values using the Map.
const symsym = (...args) => {
// create a Map from the unique value of each array
const m = args.reduce((r, a) => {
// get unique values of array, and add to Map
new Set(a).forEach((n) => r.set(n, (r.get(n) || 0) + 1));
return r;
}, new Map());
// combine all arrays
return [].concat(...args)
// remove all items that appear more than once in the map
.filter((n) => m.get(n) === 1);
};
console.log(symsym([1, 1, 2, 6], [2, 3, 5], [2, 3, 4])); // => Desired answer: [1, 1, 6, 5, 4]
This is the JS code using higher order functions
function sym(args) {
var output;
output = [].slice.apply(arguments).reduce(function(previous, current) {
current.filter(function(value, index, self) { //for unique
return self.indexOf(value) === index;
}).map(function(element) { //pushing array
var loc = previous.indexOf(element);
a = [loc !== -1 ? previous.splice(loc, 1) : previous.push(element)];
});
return previous;
}, []);
document.write(output);
return output;
}
sym([1, 2, 3], [5, 2, 1, 4]);
And it would return the output as: [3,5,4]
Pure javascript solution.
function diff(arr1, arr2) {
var arr3= [];
for(var i = 0; i < arr1.length; i++ ){
var unique = true;
for(var j=0; j < arr2.length; j++){
if(arr1[i] == arr2[j]){
unique = false;
break;
}
}
if(unique){
arr3.push(arr1[i]);}
}
return arr3;
}
function symDiff(arr1, arr2){
return diff(arr1,arr2).concat(diff(arr2,arr1));
}
symDiff([1, "calf", 3, "piglet"], [7, "filly"])
//[1, "calf", 3, "piglet", 7, "filly"]
My short solution. At the end, I removed duplicates by filter().
function sym() {
var args = Array.prototype.slice.call(arguments);
var almost = args.reduce(function(a,b){
return b.filter(function(i) {return a.indexOf(i) < 0;})
.concat(a.filter(function(i){return b.indexOf(i)<0;}));
});
return almost.filter(function(el, pos){return almost.indexOf(el) == pos;});
}
sym([1, 1, 2, 5], [2, 2, 3, 5], [3, 4, 5, 5]);
//Result: [4,5,1]
function sym(args) {
var initialArray = Array.prototype.slice.call(arguments);
var combinedTotalArray = initialArray.reduce(symDiff);
// Iterate each element in array, find values not present in other array and push values in combinedDualArray if value is not there already
// Repeat for the other array (change roles)
function symDiff(arrayOne, arrayTwo){
var combinedDualArray = [];
arrayOne.forEach(function(el, i){
if(!arrayTwo.includes(el) && !combinedDualArray.includes(el)){
combinedDualArray.push(el);
}
});
arrayTwo.forEach(function(el, i){
if(!arrayOne.includes(el) && !combinedDualArray.includes(el)){
combinedDualArray.push(el);
}
});
combinedDualArray.sort();
return combinedDualArray;
}
return combinedTotalArray;
}
console.log(sym([1, 1, 2, 5], [2, 2, 3, 5], [3, 4, 5, 5]));
This function removes duplicates because the original concept of symmetric difference operates over sets. In this example, the function operates on the sets this way: ((A △ B) △ C) △ D ...
function sym(...args) {
return args.reduce((old, cur) => {
let oldSet = [...new Set(old)]
let curSet = [...new Set(cur)]
return [
...oldSet.filter(i => !curSet.includes(i)),
...curSet.filter(i => !oldSet.includes(i))
]
})
}
// Running> sym([1, 1, 2, 6], [2, 3, 5], [2, 3, 4])
console.log(sym([1, 1, 2, 6], [2, 3, 5], [2, 3, 4]))
// Return> [1, 6, 5, 2, 4]
This works for me:
function sym() {
var args = [].slice.call(arguments);
var getSym = function(arr1, arr2) {
return arr1.filter(function(each, idx) {
return arr2.indexOf(each) === -1 && arr1.indexOf(each, idx + 1) === -1;
}).concat(arr2.filter(function(each, idx) {
return arr1.indexOf(each) === -1 && arr2.indexOf(each, idx + 1) === -1;
}));
};
var result = getSym(args[0], args[1]);
var len = args.length - 1, i = 2;
while (--len) {
result = [].concat(getSym(result, args[i]));
i++;
}
return result;
}
console.info(sym([1, 1, 2, 5], [2, 2, 3, 5], [6, 8], [7, 8], [9]));
Alternative: Use the lookup inside a map instead of an array
function sym(...vs){
var has = {};
//flatten values
vs.reduce((a,b)=>a.concat(b)).
//if element does not exist add it (value==1)
//or mark it as multiply found value > 1
forEach(value=>{has[value] = (has[value]||0)+1});
return Object.keys(has).filter(x=>has[x]==1).map(x=>parseInt(x,10));
}
console.log(sym([1, 2, 3], [5, 2, 1, 4],[5,7], [5]));//[3,4,7])
Hey if anyone is interested this is my solution:
function sym (...args) {
let fileteredArgs = [];
let symDiff = [];
args.map(arrayEl =>
fileteredArgs.push(arrayEl.filter((el, key) =>
arrayEl.indexOf(el) === key
)
)
);
fileteredArgs.map(elArr => {
elArr.map(el => {
let index = symDiff.indexOf(el);
if (index === -1) {
symDiff.push(el);
} else {
symDiff.splice(index, 1);
}
});
});
return (symDiff);
}
console.log(sym([1, 2, 3, 3], [5, 2, 1, 4]));
Here is the solution
let a=[1, 1, 2, 6]
let b=[2, 3, 5];
let c= [2, 3, 4]
let result=[...a,...b].filter(item=>!(a.includes(item) && b.includes(item) ))
result=[...result,...c].filter(item=>!(b.includes(item) && c.includes(item) ))
console.log(result) //[1, 1, 6, 5, 4]
Concise solution using
Arrow functions
Array spread syntax
Array filter
Array reduce
Set
Rest parameters
Implicit return
const symPair = (a, b) =>
[...a.filter(item => !b.includes(item)),
...b.filter(item => !a.includes(item))]
const sym = (...args) => [...new Set(args.reduce(symPair))]
The function symPair works for two input arrays, and the function sym works for two or more arrays, using symPair as a reducer.
const symPair = (a, b) =>
[...a.filter(item => !b.includes(item)),
...b.filter(item => !a.includes(item))]
const sym = (...args) => [...new Set(args.reduce(symPair))]
console.log(sym([1, 2, 3], [2, 3, 4], [6]))
const removeDuplicates = (data) => Array.from(new Set(data));
const getSymmetric = (data) => (val) => data.indexOf(val) === data.lastIndexOf(val)
function sym(...args) {
let joined = [];
args.forEach((arr) => {
joined = joined.concat(removeDuplicates(arr));
joined = joined.filter(getSymmetric(joined))
});
return joined;
}
console.log(sym([1, 2, 3], [5, 2, 1, 4]));
Below code worked fine all the scenarios. Try the below code
function sym() {
var result = [];
for (var i = 0; i < arguments.length; i++) {
if (i == 0) {
var setA = arguments[i].filter((val) => !arguments[i + 1].includes(val));
var setB = arguments[i + 1].filter((val) => !arguments[i].includes(val));
result = [...setA, ...setB];
i = i + 1;
} else {
var setA = arguments[i].filter((val) => !result.includes(val));
var setB = result.filter((val) => !arguments[i].includes(val));
result = [...setA, ...setB];
}
}
return result.filter((c, index) => {
return result.indexOf(c) === index;
}).sort();
}
My code passed all test cases for the similar question on freecodecamp. Below is code for the same.
function sym(...args) {
const result = args.reduce((acc, curr, i) => {
if (curr.length > acc.length) {
const arr = curr.reduce((a, c, i) => {
if(a.includes(c)){
}
if (!acc.includes(c) && !a.includes(c)) {
a.push(c);
}
if (!curr.includes(acc[i]) && i < acc.length) {
a.push(acc[i])
}
return a;
}, []);
return [...arr];
} else {
const arr = acc.reduce((a, c, i) => {
if(a.includes(c)){
}
if (!curr.includes(c) && !a.includes(c)) {
a.push(c);
}
if (!acc.includes(curr[i]) && i < curr.length) {
a.push(curr[i])
}
return a;
}, []);
return [...arr]
}
});
let ans = new Set([...result])
return [...ans]
}
sym([1,2,3,3],[5, 2, 1, 4,5]);

Categories