_.difference but allow duplicates - javascript

I'm trying to find the difference of two arrays but duplicates are allowed in the array and so I want to only remove them one at a time.
This is probably easier to show with an example. The _.difference function works like this:
_.difference([1, 1, 2], [1]); // returns [2]
But I would like to know if there is a function (preferably in underscore) that would instead do this:
_.difference2([1, 1, 2], [1]); // returns [1, 2]
_.difference2([1, 1, 2], [1, 1]); // returns [2]
And if there is not already a way to do this what would be an efficient way to make a mixin that does?

This mixin I came up with works.. but open to suggestions on how to improve it:
_.mixin({
remove: function(base, toRemove) {
var ret = [];
toRemove = _.clone(toRemove);
_.each(base, function(elem) {
var i = _.indexOf(toRemove, elem);
if(i < 0) {
ret.push(elem);
} else {
toRemove[i] = undefined;
}
});
return ret;
}
});
_.remove([1, 1, 2], [1]); // returns [1, 2]
_.remove([1, 1, 2], [1, 1]); // returns [2]

Related

Cumulatively merge arrays based on intersection without duplicates in Javascript [duplicate]

This question already has answers here:
JS - Merge arrays that share at least one common value
(3 answers)
Closed 11 months ago.
I have data that clusters like so
[[1, 2, 3], [0, 2], [2, 4], [6, 7]].
I'd like to merge arrays that share items without duplicates.
For example, the result I'm looking for from the above data would be
[[1, 2, 3, 0, 4], [6, 7]].
My approach is to loop through the arrays, and when an intersection is found, form the union of the two arrays. Here's what I've tried:
let clusters = [[1, 2, 3], [0, 2], [2, 4], [6, 7]];
let len = clusters.length;
let i, j;
for (i = 0; i < len; i++) {
for (j = i+1; j < len; j++) {
if (clusters[i].filter(x => clusters[j].includes(x))) {
clusters[i] = [...new Set([...this.clusters[i], ...this.clusters[j]])]; // this won't work
}
}
}
The result is that the clusters array is unchanged. I think this could work if I could find a way to run the union operation recursively (when the intersection condition is met).
Or maybe there's a better approach?
You can do this with Array.reduce, pushing arrays from clusters into sets in the output array or merging if a value already exists in one of the sets in the output, and then converting back from sets to arrays once complete:
let clusters = [
[1, 2, 3],
[0, 2],
[2, 4],
[6, 7]
];
let result = clusters.reduce((c, a) => {
for (i = 0; i < c.length; i++) {
if (a.some(v => c[i].has(v))) {
a.forEach(v => c[i].add(v));
return c;
}
}
c.push(new Set(a));
return c;
}, [])
.map(s => Array.from(s));
console.log(result);
This may (I didn't really tested it, take this as an idea to begin your research) work.
You don't really need recursion since you are dealing with just one nesting level. Beware of the bad performance of this if you have a large data set.
var a=[[1, 2, 3], [0, 2], [2, 4], [6, 7]];
function match(a,b) {
//if you meant _at least_ one match between two elements
for(var i=0;i<a.length;i++) if(b.indexOf(a[i])>0) return true;
return false;
}
function combine(a,b) {
var c=[];
for(var i=0;i<a.length;i++) if(c.indexOf(a[i])<0) c.push(a[i]);
for(var i=0;i<b.length;i++) if(c.indexOf(b[i])<0) c.push(b[i]);
return c;
}
while(1) {
var found=false;
for(var i=0;i<a.length;i++) {
for(var j=0;j<a.length;j++) {
if(i==j) continue;
//if a match is found, merge the two elements and start over
if(match(a[i],a[j])) {
a[i]=combine(a[i],a[j]);
a.splice(j,1);
found=true;
break;
}
}
if(found) break;
}
//repeat until no matches were found
if(!found) break;
}
console.log(a);

Symmetric Difference of unknown number of arrays

Okay so I know there are multiple answers to this question but all of them use different approaches and I'm confused af rn.
The objective is to create a function that takes two or more arrays and returns an array of the symmetric difference of the provided arrays. The individual helper function is working fine but the code throws an error when I try to run it whole.
Here is my attempt:
function sym(args) {
let totalArguments = [...args];
var helper = function (arr1, arr2) {
return arr1.filter(item => arr2.indexOf(item) === -1).concat(arr2.filter(item => arr1.indexOf(item) === -1));
}
return totalArguments.reduce(helper);}
The input of sym([1, 2, 3],[2, 3, 4]) should be [1, 4]
Your code isn't working because you're only using the first argument:
function sym(args) {
let totalArguments = [...args];
This takes the first argument, args, and makes a shallow copy of the array - which doesn't accomplish anything because you aren't mutating anywhere anyway. If you wanted to accept a variable number of arguments, use argument rest syntax, to collect all arguments in an array:
function sym(...totalArguments) {
function sym(...totalArguments) {
var helper = function(arr1, arr2) {
return arr1.filter(item => arr2.indexOf(item) === -1).concat(arr2.filter(item => arr1.indexOf(item) === -1));
}
return totalArguments.reduce(helper);
}
console.log(sym([1, 2, 3], [2, 3, 4])) // [1, 4]
console.log(sym([1, 2, 3], [2, 3, 4], [0, 1])) // [0, 4], since 1 was duplicated
(There's no need to provide an initial value to the reducer)
Another option whose logic will probably be clearer to read and understand would be to iterate over all arrays and reduce into an object that keeps track of the number of times each number has occurred. Then, take the entries of the object, and return an array of the keys whose values are 1:
function sym(...args) {
const counts = args.reduce((a, arr) => {
arr.forEach((num) => {
a[num] = (a[num] || 0) + 1;
});
return a;
}, {});
return Object.entries(counts)
.filter(([, count]) => count === 1)
.map(([key]) => key);
}
console.log(sym([1, 2, 3], [2, 3, 4])) // [1, 4]
console.log(sym([1, 2, 3], [2, 3, 4], [0, 1])) // [0, 4], since 1 was duplicated

Flattening an array recursively (withoout looping) javascript

I'm practicing recursion and am trying to flatten an array without looping (recursion only). As a first step I did the iterative approach and it worked, but am stuck on the pure recursion version:
function flattenRecursive(arr) {
if (arr.length === 1) {
return arr;
}
return Array.isArray(arr) ? arr = arr.concat(flattenRecursive(arr)) : flattenRecursive(arr.slice(1))
}
console.log(flattenRecursive([
[2, 7],
[8, 3],
[1, 4], 7
])) //should return [2,7,8,3,1,4,7] but isn't - maximum call stack error
//working version (thanks #Dave!):
function flattenRecursive(arr) {
if (arr.length === 1) {
return arr;
}
return arr[0].concat(Array.isArray(arr) ? flattenRecursive(arr.slice(1)) : arr);
}
console.log(flattenRecursive([
[2, 7],
[8, 3],
[1, 4], 7
]))
//returns [ 2, 7, 8, 3, 1, 4, 7 ]
Here's a working version that's slightly less verbose.
//using reduce
function flattenRecursive(arr) {
return arr.reduce(function(result, a){
return result.concat(Array.isArray(a) ? flattenRecursive(a) : a);
}, []);
}
//without reduce
function flattenRecursive2(arr) {
if (arr.length === 0)
return arr;
var head = arr.shift();
if (Array.isArray(head))
return flattenRecursive2(head).concat(flattenRecursive2(arr));
else
return [head].concat(flattenRecursive2(arr));
}
var testArray = [1,[2, 3],[[4, 5, [6, 7]], [8, 9]], 10];
console.log(flattenRecursive(testArray));
console.log(flattenRecursive2(testArray));
<script src="http://gh-canon.github.io/stack-snippet-console/console.min.js"></script>
Sorry, I do not have the reputation to comment. Here are my 2 cents.
1) be careful about your initial condition. Here, it seems that if your input is ̀arr = [[1,2]], your function returns [[1,2]], while you would like it to return [1,2].
2) in the core of the recursion, you must be sure than you recursively call your function with a smaller argument. Here, you should concat the first element of your array with the flattened rest of the array. The functionslice` may be handy for that.
3) It would be also be possible to use a reduce-like function.

difference between two arrays with javascript filter

Problem:
Compare two arrays and return a new array with any items not found in both of the original arrays. Use Array.filter and Array.indexOf to solve this.
function diff(arr1, arr2) {
var newArr = [];
//code here
return newArr;
}
diff([1, 2, 3, 5], [1, 2, 3, 4, 5]);
I am not sure how to proceed. My solution is different from the above and uses a hard coded array. How do I make mine generic ?
function arrayNotContains(element){
var arr = [1, 2, 3, 5];
if(arr.indexOf(element) === -1){
return true;
}else{
return false;
}
}
var filtered = [1, 2, 3, 4, 5].filter(arrayNotContains);
console.log(filtered);
I got one more solution below. Is that ok ?
var arr1 = [1,2,3,5];
var arr2 = [1,2,3,4,5];
var filtered = arr2.filter(function(num) {
if (arr1.indexOf(num) === -1) return num;
});
You will want to use a closure:
function notContainedIn(arr) {
return function arrNotContains(element) {
return arr.indexOf(element) === -1;
};
}
var filtered = [1, 2, 3, 4, 5].filter(notContainedIn([1, 2, 3, 5]));
console.log(filtered); // [4]
Notice this is just a generalised version of your solution, I'm not saying that this is actually a valid solution for a symmetric diff function. For that, as it was stated in your problem, you'd need to do something like
function symmDiff(a, b) {
return a.filter(notContainedIn(b)).concat(b.filter(notContainedIn(a)));
}

Functional JavaScript: Produce array of unique values

What is the way of producing an array of unique values out of another array using functional programming in JavaScript?
This should do: toUnique([1,1,2,3,4,4]) => [1,2,3,4]
Take a look at the uniq function of the Ramda functional javascript libriary.
R.uniq([1, 1, 2, 1]); //=> [1, 2]
R.uniq([{}, {}]); //=> [{}, {}]
R.uniq([1, '1']); //=> [1, '1']
You can use the function from libriary or check the source code...
function uniq(list) {
var idx = -1, len = list.length;
var result = [], item;
while (++idx < len) {
item = list[idx];
if (!_contains(item, result)) {
result[result.length] = item;
}
}
return result;
};
This has been asked and answered 1000 times before, but since you're asking for a functional programming solution, here you go:
head = function(ls) { return ls[0] };
tail = function(ls) { return ls.slice(1) };
empty = function(ls) { return ls.length == 0 };
cons = function(a, b) { return [a].concat(b) };
has = function(x, ls) {
return empty(ls) ? false : head(ls) == x || has(x, tail(ls));
};
_uniq = function(ls, seen) {
return empty(ls) ? [] :
has(head(ls), seen) ?
_uniq(tail(ls), seen) :
cons(head(ls),
_uniq(tail(ls),
cons(head(ls), seen)));
};
uniq = function(ls) {
return _uniq(ls, []);
};
console.log(uniq([1,1,2,3,1,2,5])); // [1,2,3,5]
This is pure functional solution, as requested (in fact, a straight port of nub). For a practical one, consider one of the answers over here.
Well, if you are not worried about the performance, I would use Array.prototype.filter and Array.prototype.indexOf, like this
function toUnique(array) {
return array.filter(function(currentItem, index) {
return (index === array.indexOf(currentItem));
});
}
console.log(toUnique([1, 1, 2, 3, 4, 4]));
# [ 1, 2, 3, 4 ]
If you can use any other libraries, you can use lodash's uniq function, like this
_.uniq([1, 1, 2, 3, 4, 4]);
// → [1, 2, 3, 4]
It can also take advantage of the fact that the input array is already sorted. So, you might want to invoke it like this
_.uniq([1, 1, 2, 3, 4, 4], true);
// → [1, 2, 3, 4]

Categories