Finding unique values in multiple arrays - javascript

I'm trying to solve a freeCodeCamp exercise with this goal:
Write a function that takes two or more arrays and returns a new array
of unique values in the order of the original provided arrays.
In other words, all values present from all arrays should be included
in their original order, but with no duplicates in the final array.
The unique numbers should be sorted by their original order, but the
final array should not be sorted in numerical order.
So what I do is concatenate all the arguments into a single array called everything. I then search the array for duplicates, then search the arguments for these duplicates and .splice() them out.
So far everything works as expected, but the last number of the last argument does not get removed and I can't really figure out why.
Can anybody please point out what I'm doing wrong? Please keep in mind that I'm trying to learn, so obvious things probably won't be obvious to me and need to be pointed out. Thanks in advance.
function unite(arr1, arr2, arr3) {
var everything = [];
//concat all arrays except the first one
for(var x = 0; x < arguments.length; x++) {
for(var y = 0; y < arguments[x].length; y++) {
everything.push(arguments[x][y]);
}
}
//function that returns duplicates
function returnUnique(arr) {
return arr.reduce(function(dupes, val, i) {
if (arr.indexOf(val) !== i && dupes.indexOf(val) === -1) {
dupes.push(val);
}
return dupes;
}, []);
}
//return duplicates
var dupes = returnUnique(everything);
//remove duplicates from all arguments except the first one
for(var n = 1; n < arguments.length; n++) {
for(var m = 0; m < dupes.length; m++) {
if(arguments[n].hasOwnProperty(dupes[m])) {
arguments[n].splice(arguments[n].indexOf(dupes[m]), 1);
}
}
}
//return concatenation of the reduced arguments
return arr1.concat(arr2).concat(arr3);
}
//this returns [1, 3, 2, 5, 4, 2]
unite([1, 3, 2], [5, 2, 1, 4], [2, 1]);

Looks like you overcomplicated it a bit ;)
function unite() {
return [].concat.apply([], arguments).filter(function(elem, index, self) {
return self.indexOf(elem) === index;
});
}
res = unite([1, 2, 3], [5, 2, 1, 4], [2, 1], [6, 7, 8]);
document.write('<pre>'+JSON.stringify(res));
Explanations
We split the problem into two steps:
combine arguments into one big array
remove non-unique elements from this big array
This part handles the first step:
[].concat.apply([], arguments)
The built-in method someArray.concat(array1, array2 etc) appends given arrays to the target. For example,
[1,2,3].concat([4,5],[6],[7,8]) == [1,2,3,4,5,6,7,8]
If our function had fixed arguments, we could call concat directly:
function unite(array1, array2, array3) {
var combined = [].concat(array1, array2, array3);
// or
var combined = array1.concat(array2, array3);
but as we don't know how many args we're going to receive, we have to use apply.
someFunction.apply(thisObject, [arg1, arg2, etc])
is the same as
thisObject.someFunction(arg1, arg2, etc)
so the above line
var combined = [].concat(array1, array2, array3);
can be written as
var combined = concat.apply([], [array1, array2, array3]);
or simply
var combined = concat.apply([], arguments);
where arguments is a special array-like object that contains all function arguments (actual parameters).
Actually, last two lines are not going to work, because concat isn't a plain function, it's a method of Array objects and therefore a member of Array.prototype structure. We have to tell the JS engine where to find concat. We can use Array.prototype directly:
var combined = Array.prototype.concat.apply([], arguments);
or create a new, unrelated, array object and pull concat from there:
var combined = [].concat.apply([], arguments);
This prototype method is slightly more efficient (since we're not creating a dummy object), but also more verbose.
Anyways, the first step is now complete. To eliminate duplicates, we use the following method:
combined.filter(function(elem, index) {
return combined.indexOf(elem) === index;
})
For explanations and alternatives see this post.
Finally, we get rid of the temporary variable (combined) and chain "combine" and "dedupe" calls together:
return [].concat.apply([], arguments).filter(function(elem, index, self) {
return self.indexOf(elem) === index;
});
using the 3rd argument ("this array") of filter because we don't have a variable anymore.
Simple, isn't it? ;) Let us know if you have questions.
Finally, a small exercise if you're interested:
Write combine and dedupe as separate functions. Create a function compose that takes two functions a and b and returns a new function that runs these functions in reverse order, so that compose(a,b)(argument) will be the same as b(a(argument)). Replace the above definition of unite with unite = compose(combine, dedupe) and make sure it works exactly the same.

You can also try this :
var Data = [[1, 2, 3], [5, 2, 1, 4], [2, 1], [6, 7, 8]]
var UniqueValues = []
for (var i = 0; i < Data.length; i++) {
 UniqueValues = [...new Set(UniqueValues.concat(Data[i]))]
}
console.log(UniqueValues)

Related

Why is my recursion function not working properly?

my a and b variables are coming out to be same each time I reach the return statement in the end with concat. Why when im pushing into array after I pass it to the first recursive function. Why does first one have the updated array?
let subset = function(n, arr) {
if (n == array.length) {
return [arr]
}
// let a = [...arr] //working fine with this
// arr.push(arra[n])
let a = subset(n + 1, arr)
arr.push(array[n])
let b = subset(n + 1, arr)
return a.concat(b)
}
let array = [1, 2, 3]
console.log(subset(0, []))
By destructuring my array its working properly. But I dont want to use extra space.
When the recursion gets to its base case, the returned array has one element, which is arr. The caller assigns this wrapped array to a. This means that a[0] and arr are the same array. If then arr gets an extra element via the push call, then this will also be seen via a[0]. You expected that a[0] and arr would be separate arrays that could live their own lives, but that is not the case.
You do create a new outer array with concat, but that doesn't solve the problem that the inner arrays are mutated with push.
You write that you don't want to use extra space, but as the goal is to create an array of subarrays, it really is needed to allocate the space for each separate subarray.
There are several other ways to get this done. For instance, it would make sense to create the copy of arr at the moment you place it in the result. On the other hand you can avoid the creation of a new outer array, and keep extending the existing one.
I would avoid referencing the global array variable from within the function: just pass that array as argument too.
let subset = function(array, n=0, arr=[], result=[]) {
if (n == array.length) {
result.push([...arr]); // <--- here we clone arr
} else {
subset(array, n + 1, arr, result);
arr.push(array[n]);
subset(array, n + 1, arr, result);
arr.pop(); // Undo the push
}
return result;
}
let array = [1, 2, 3];
console.log(subset(array));
So in this code, there is only one arr array: it grows and shrinks during the whole process. Only when it needs to be put in the result, a copy is made of it ("snapshot"), so that any further manipulation of arr will not affect this subarray in the result. There is also only one result array. Both that arr and result array are created by the default of the function parameters ([]).
As an alternative you could use a generator function. Then the caller of that generator function will build the result array:
let subsetIterator = function* (array, n=0, arr=[]) {
if (n == array.length) return yield [...arr];
yield* subsetIterator(array, n + 1, arr);
arr.push(array[n]);
yield* subsetIterator(array, n + 1, arr);
arr.pop(); // Undo the push
}
let array = [1, 2, 3];
let result = [...subsetIterator(array)]; // build result array from subarrays
console.log(result);
If you don't really need the result array, but only want to have the subarrays, then the story changes: in that case you don't have to create separate array instances. It is then up to the caller to be aware that the iterator will yield the same (but mutated) array. This is "dangerous" if the caller is not aware of that and is surprised that a previously yielded array still changed after that. But if you know what you are doing it does work:
let subsetIterator = function* (array, n=0, arr=[]) {
if (n == array.length) return yield arr; // no copy is made!
yield* subsetIterator(array, n + 1, arr);
arr.push(array[n]);
yield* subsetIterator(array, n + 1, arr);
arr.pop(); // Undo the push
}
let array = [1, 2, 3];
for (let arr of subsetIterator(array)) console.log(arr);
All iterated arr are actually the same array now, but it gets printed at a different moment in its (mutating) lifetime.

array.splice() returns the item I want to eliminate rather than the array minus the item

I'm trying to remove an item from an array using the indexOf() with splice() technique suggested. This is what's happening
let someArray: string[] = [first, second, third, fourth, fifth, sixth];
let newArray: string[] = someArray.splice(3, 1);
console.log(newArray);
//deisred result = [first, second, third, fifth, sixth]
//result I'm getting = [fourth]
That's not what virtually every article I've come across says should happen. Can someone shed light on this?
UPDATE
I discovered this problem in my code when I was only ghetting 1 result where I was expecting more and tracked it back to this point.
Because when you splice an array you are mutating it, which means you are changing the original array. You're storing the result (the element you're splicing from the array) within the "newArray" variable that you have created here. So this:
var arr = [1, 2, 3, 4];
var mine = arr.splice(1, 1);
console.log(mine);
console.log(arr);
would return the original ray minus index one if we print arr to the console, and will return [2] if we print mine to the console. To get the output you're expecting, you would have to perform a different operation such as iterating through the array and utilizing splice differently. Here is an example:
var arr = [1, 2, 3, 4];
var mine = [];
for(var i = 0; i < arr.length; i++) {
if(i !== 3) {
mine.push(arr[i]);
}
}
Now I am not mutating the original array, and I am simply pushing the elements to a new array.
But if you want to simply mutate the original array and not store the new array in some sort of variable you can simply splice the original array:
var arr = [1, 2, 3, 4];
arr.splice(3, 1);
console.log(arr);
However, if you are passing it to a function, i'd probably not mutate an array outside of the function, and i'd simply return a value and store that value in a new variable:
var arr = [1, 2, 3, 4];
function deleteIndex(ar, i) {
var a = [];
ar.forEach(function(elt, index) {
if(index === i) {
}
else {
a.push(elt);
}
});
return a;
}
var newArr = deleteIndex(arr, 3);
console.log(newArr);
This way you can delete any index, or pass a function and criteria that you would want to use to determine if an index should be deleted, without changing to top-level structure of your original array by utilizing functional programming. There are also some function in the underscore module that can help you if that's the case.

Why if statements blocks in while loop?

I have an array of numbers with a bunch of duplicates. I need to get rid of them so I put the code:
let dup = arr.filter((elem, pos)=> arr.indexOf(elem) !== pos);
// dup Array contains the duplicate numbers
arr = arr.filter((elem, pos)=> arr.indexOf(elem) == pos);
//arr contains the whole array with duplicates
let i = 0;
let j = 0;
while(i<arr.length){
while(j<dup.length){
if(arr[i] == dup[j]){
arr.splice(i, 1);
//the splice method resets the decrease the index of the array so
i--;
};
j++;
};
i++
}
The problem is that the if doesn't run after the first match.So the array splice the first duplicate that it finds and stops. How can I fix that?
From Get all unique values in a JavaScript array (remove duplicates)
const myArray = ['a', 1, 'a', 2, '1'];
const unique = [...new Set(myArray)];
// output ["a", 1, 2, "1"]
or as a function
const unique = [...new Set(myArray)]
The problem is that you never reset j. You need to move that inside the while (i ...) loop.
let arr = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]
console.log('arr', arr)
let dup = arr.filter((elem, pos)=> arr.indexOf(elem) !== pos);
console.log('dup', dup)
arr = arr.filter((elem, pos)=> arr.indexOf(elem) == pos)
console.log('new arr', arr)
let i = 0;
while(i<arr.length){
let j = 0;
while(j<dup.length){
if(arr[i] == dup[j]){
arr.splice(i, 1);
i--;
};
j++;
};
i++
}
console.log('final arr', arr)
But there are easier ways to do this.
Update
I got pinged for mentioning easier ways without showing one. Here is an alternative way to get the same results:
const singletons = (
xs,
dups = xs .filter ((x, i) => arr .indexOf (x) !== i)
) => xs .filter (x => dups .indexOf (x) < 0)
let arr = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]
console .log (
singletons (arr)
)
This version does not modify your original array, just returning a new one containing only the singleton elements (those that appear just once in the original list.)
Explanation
singletons is a function taking an array of some type of element and returning another array of the same type. Because that type is not specific, I use a non-specific name; a fairly strong convention make this xs. with the s noting that it's plural (i.e. an array of them.)
dups is an array of elements that is duplicated in your original. Although I include it as a defaulted parameter, we could just as easily have created it in the body of the function like this:
const singletons = (xs) => {
const dups = xs .filter ((x, i) => arr .indexOf (x) !== i)
return xs .filter (x => dups .indexOf (x) < 0)
}
The only reason I didn't was that I am perhaps over-fond of single expression bodies, thus avoiding the {-} pair and the return statement. But there is no real difference between these two approaches, except that the one I presented happens to do some additional work that I would never count on: if you supply a second parameter, an array of values, then, rather than removing the duplicates, it removes all those element from your array that are also in the second one, vaguely reminiscent of a set difference function.
The main advantage of this over your approach is that it is non-destructive. It does not mutate your original data. It also has no assignments, except in the default parameter, so there is no confusion in state management. ("Where do I put let j == 0?" is not a meaningful question here.) This makes it feel more robust.
You need to set j=0 inside the first while loop, otherwise, it will only run through the second while loop once.
Also, if I were you, I would use a for-loop (array.forEach() specifically) instead of while, because they already count the number of elements anyway.
My solution for this is this:
arr.forEach((e, i) => {
dup.forEach((f, j) => {
if(e==f){
arr.splice(i, 1);
}
})
});
Hope it works for you.
EDIT:
Stolen from the comment from James to the original question.
Javascript already provides an easy method to do just that anyway:
arr.filter(x=> dup.indexOf(x) < 0)

Push array if array doesn't exist in array of arrays

I am currently working on a small home project, and I am trying to push an array into an array of arrays, if said array does not already exist in the array of arrays.
var arrayArr = [[1,4]];
function pushArr() {
var tempArr = [1, 3];
var tempArr2 = [1, 4];
for(i = 0; i < arrayArr.length, i++)
if(!arrayArr.indexOf(tempArr[i])) {
arrayArr.push(tempArr[i]);
} else {
//other logic
}
}
Now, I know this example does not really make sense in the real world, it's just to illustrate my concern. How do I search through an array of arrays to make sure, that I don't create duplicates.
If you have any questions, please ask.
Thanks !
In my solution, in isArrayInArray(), I'm looping through each element in the main array arrayArr. I'm then comparing if the first and second element of each given array match. If so, the array has been added already so it'll return true.
var arrayArr = [[1, 4]];
pushArray([1, 4]); // does not get added
pushArray([1, 3]); // gets added
console.log(arrayArr);
function isArrayInArray(arrayToSearch, arrayToFind) {
for (let i = 0; i < arrayToSearch.length; i++) {
if (arrayToSearch[i][0] === arrayToFind[0] && arrayToSearch[i][1] === arrayToFind[1]) {
return true;
}
}
return false;
}
function pushArray(array) {
if (!isArrayInArray(arrayArr, array)) {
arrayArr.push(array);
}
}
You know [1,2] === [1,2] is false.
Please refer to How to Compare two Arrays are Equal using Javascript?
Even if you have the following:
let a = [1,2];
let b = [1,2];
Both a and b hold two different references to to arrays that have the same numbers. They are not equal !
let a = [1,2];
let b = [1,2];
console.log(a === b);
If we by pass this and we assume that for our problem that two arrays with the same length and the same data are the same, we can try to call the function arrayCanBePushed, before we want to append an array to our array of arrays and if it returns true, then we can proceed with the push.
var arrayOfArrays = [[1,2],[2,3],[3,4,5],[7]];
function arrayCanBePushed(arr){
return !arrayOfArrays.filter(a => a.length === arr.length)
.some(a => a.every(ele=>arr.includes(ele)));
}
console.log(arrayCanBePushed([1,2]));
console.log(arrayCanBePushed([1,2,3]));
Tried to keep the answer as simple as possible. Basically we are iterating over arrayArr and checking if all of the elements of the input array match all of the elements of any of the element arrays. So it should be simply an every nested in a some. However if we are comparing just two-element arrays (array to string conversion should be pretty fast) as you have now clarified in your answer then I would use string comparison as illustrated in pushArr1. This is the general direction you were heading in your first attempt so I added it to my answer.
var arrayArr = [[1, 3, 5]];
var tempArr = [1, 3, 5];
var tempArr2 = [1, 4];
pushArr1(tempArr);
function pushArr(temp)
{
if(!arrayArr.some(function(e){
return e.every(function(e1, i)
{
return e1 === temp[i];
})
})) arrayArr.push(temp);
console.log(arrayArr);
}
function pushArr1(temp)
{
if(!arrayArr.some(function(e){return e.join(",") === temp.join(",")})) arrayArr.push(temp);
console.log(arrayArr);
}

Emptying array when it matches another array

So, I'm trying to match 2 different arrays. If the same cells match, I want to remove that cell from one array using the .slice method.
Edit: What I'm trying to do is remove a number from array1 if array2 contains a matching number. The way the code works now is that it only deletes 1 entry. I want all the entries deleted from the first array.
array1 = [1, 2, 4, 5, 7, 10];
array2 = [1,2,4,5,6,7,8];
var misc = function deleteValues(array, arayy) {
for(var i = 0; i < array.length; i++) {
if ( arayy[i] == array[i]) {
array.splice(i, 1);
}
}
return array;
};
I try to run this and under console log, the array1 is unchanged. It seems like the splice method isn't removing any cells. I searched SE, but couldn't find anything that could help me.
jsFiddle Demo
The problem is that you are modifying one of the arrays as you iterate, but you are still using the same index. The end result is that you end up comparing the wrong indexes to each other after the first removal. Use two indexes, have one offset back down when it removes an item, and have the other simply iterate.
var misc = function deleteValues(array, arayy) {
for(var i = 0, j = 0; i < array.length; i++, j++) {
if ( arayy[j] == array[i]) {
array.splice(i--, 1);
}
}
return array;
};
It seems you want to remove items from the first array if the values are also in the second. The reduceRight method seems suitable as it iterates from right to left over the array, hence removing items doesn't affect the index of subsequent elements in the array. The same result can be achieved with a decrementing loop.
Also, I think function declarations are better than assignment of expressions, but each to their own.
var array1 = [1, 2, 4, 5, 7, 10];
var array2 = [1,2,4,5,6,7,8];
function deleteValues(arr0, arr1) {
arr0.reduceRight(function(x, value, index) {
if (arr1.indexOf(value) != -1) {
arr0.splice(index, 1);
}
},'');
// Not necessary but handy for chaining
return arr0;
}
document.write(deleteValues(array1, array2));
Using an arrow function, the above can be reduced to:
function deleteValues(arr0, arr1) {
arr0.reduceRight((x, v, i) => arr1.indexOf(v) != -1? arr0.splice(i, 1):'');
return arr0;
}

Categories