finding symmetric difference/unique elements in multiple arrays in javascript - javascript

Hi I'm struggling to solve this problem. How to create a javascript function that takes any number of arrays as arguments, then returns an array of elements that only appear in one of the arrays. All items that appear in multiple arrays are removed. Getting nowhere with a solution, suspect I'm not approaching it in the right way, stumped!
Edit: the other question addresses eliminating duplicate values in one array, I need to compare x number of separate arrays and return the values that aren't duplicated between arrays. So ([5,6,7],[5,8,9]) returns [6,7,8,9].
function sym(args) {
var ans = [];
for(var i =0;i<arguments.length;i++){
var tempArr = arguments[i].filter(function(el){
var filtTrue = false;
for(var j = 0;j<arguments.length;j++){
if(Array.isArray(arguments[j]) && arguments[j] !== arguments[i]){
if(arguments[j].indexOf(el) === -1){
filtTrue = true;
}}
}
return filtTrue;
});
ans = ans.concat(tempArr);
}
return ans;
}

Here's one way to do it. The idea here is that you create a map for keeping counts of all the items in the array. You then cycle through each array, look up each value in the map and, if found, you increment its count. If not found, you set the count to 1. Then, when done with all the arrays, you collect any items that have a count of 1.
You weren't specific about what to do if an item appears more than once in the same array, but not in any other array. This first solution will not include that item (since it detects duplicates). It could be adapted (with a little more complexity) to allow that item if that was an issue (see 2nd code block below for that implementation).
function sym(/* pass one or more arrays here */) {
var ans = [], cnts = {};
//count all items in the array
for (var i = 0; i < arguments.length; i++){
arguments[i].forEach(function(item) {
if (cnts.hasOwnProperty(item)) {
// increase cnt
++cnts[item].cnt;
} else {
// initalize cnt and value
cnts[item] = {cnt: 1, val: item};
}
});
}
for (var item in cnts) {
if (cnts.hasOwnProperty(item) && cnts[item].cnt === 1) {
ans.push(cnts[item].val);
}
}
return ans;
}
If you want to include items that are present more than once in a single array, but not present in any other array, then you can use this slightly more complicated adaptation:
function sym(/* pass one or more arrays here */) {
var ans = [], cnts = {}, currentMap;
//count all items in the array
for (var i = 0; i < arguments.length; i++){
currentMap = {};
arguments[i].forEach(function(item) {
// if we haven't already counted this item in this array
if (!currentMap.hasOwnProperty(item)) {
if (cnts.hasOwnProperty(item)) {
// increase cnt
++cnts[item].cnt;
} else {
// initalize cnt and value
cnts[item] = {cnt: 1, val: item};
}
}
// keep track of whethere we've already counted this item in this array
currentMap[item] = true;
});
}
// output all items that have a cnt of 1
for (var item in cnts) {
if (cnts.hasOwnProperty(item) && cnts[item].cnt === 1) {
ans.push(cnts[item].val);
}
}
return ans;
}
Working demo: http://jsfiddle.net/jfriend00/bete5k3n/

I know this is increadibly late but this is another way to do it. Maybe not the most rigorous one but certainly creative. The method Array.symmetricDifference() expects any number of arguments and returns the symmetric difference of those arguments.
Array.prototype.symmetricDifference = function() {
var args = [];
// copy arguments into a real array and get rid of duplicates with filter
for(var i = 0; i < arguments.length; i++) {
args[i] = arguments[i];
args[i] = args[i].filter(function(item, pos, self) {
return self.indexOf(item) == pos;
});
}
var diff = args[0];
// iterate through every arguments in args.
// concatenate the first two arguments to form a new set.
// now every number in this new set that was contained in both arguments
// from before will be contained at least twice in this new set.
for(var j = 1; j < args.length; j++) {
//sort the new set so that duplicates are next to each other.
diff = diff.concat(args[j]).sort();
var index = 0;
// now iterate through the new set and delete both duplicates if you
// find any. Otherwise proceed to the next index.
while(index < diff.length) {
// if duplicate is found delete both, otherwise look at next index.
diff[index] === diff[index + 1] ? diff.splice(index, 2) : index++;
}
}
return diff;
};
You can invoke that method on any array or create a new one and invoke it on that one like this for example:
// take any number of arrays
var a = [3, 3, 3, 2, 5];
var b = [2, 1, 5, 7];
var c = [3, 4, 6, 6];
var d = [1, 2, 3];
var e = [5, 3, 9, 8];
var f = [1];
// invoke the method on "solution" with any number of arguments
// and store it in solution.
var solution = solution.symmetricDifference(a,b,c,d,e,f);
console.log(solution); // [1, 2, 4, 5, 6, 7, 8, 9]
I hope this helps!

Finding unique items in multiple arrays
function uniqueItemsInArrays(...args){
let allItems = [];
allItems = allItems.concat(...args)
return allItems.filter(function(item, pos, self) {
return self.indexOf(item) === pos && self.indexOf(item,pos+1) === -1;
});
}
uniqueItemsInArrays( [1, 5, 1, 8, 1, 2],
[2, 2, 9, 3, 5],
[1, 4, 7, 6] );
The above code uses ES6 rest parameters to access all the arrays passed as arguments. Then using the concat() function, I am joining all the individual arrays to a single array. Finally, filter function has been used to identify and return the unique items from this array. The logic here is to find out the first index of the current item and if there are no more occurrence from the first index then we return that item.

Related

Make formation of array into multiple arrays with having minimum items - JavaScript

I have an array of teams, I am trying to split that array into multiple arrays with having minimum players in each array. I mean, form the array into multiple teams from array. I forgot what this feature is called, but let me tell you with an explanation:
minimum players: 3
team array: [4, 6, 5, 8]
This array will form like this:
[
[4, 6, 5],
[4, 6, 8],
[4, 5, 8],
[6, 5, 8],
[4, 6, 5, 8]
]
So, I have tried doing this problem, but I got stuck that what to do and how to do.
function teams(minPlayers, arr) {
for (let i = 0; i < arr.length; i++) {
const subArray = []
let startMax = minPlayers - 2
let end = minPlayers
for (let j = 0; j < arr.length; j++) {
if (j < startMax) {
subArray[i] = arr[i]
}
}
}
}
console.log(teams(3, [4, 6, 5, 10]))
It will be a pleasure if you help me.
Here is a generator that generates the combinations through recursion. Array.from will consume the iterator:
function teams(minPlayers, arr) {
// Execute a generator, and pass it to Array.from to turn its
// yielded values into an array and return it
return Array.from(function* iter(minPlayers, start) {
// When there are as many remaining elements (from start)
// as we need players, then there are no other options
// than selecting all those values, so yield that slice of
// the array and return (base case)
if (arr.length - minPlayers == start) return yield arr.slice(start);
// Produce the cases where the element at start is not selected.
// Use recursion for this, by reducing the slice that is still available
yield* iter(minPlayers, start + 1);
// Produce the cases where the element at start is selected.
// Use recursion to get the combinations after that, and
// prefix the start element to each of those
for (let combi of iter((minPlayers || 1) - 1, start + 1)) {
yield [arr[start], ...combi];
}
}(minPlayers, 0)); // Immediately execute the generator function
}
console.log(teams(3, [4, 6, 5, 10]));
So the idea of the generator is to either take the current value or not (current is arr[start]). In either case, defer the other selections to recursion. The recursive call will get an increased start value so there are fewer elements to choose from.
This is not exactly permutation quite the opposite since we don't care for order, but we do care for picking n different items.
The recursive function picks works by picking an item (length times) then running picks on the rest - concatenating the results. A care is given not to pick numbers that are "left" to the position of our current item position since we already counted them.
// this loops over minPlayers to maxPlayers (length)
function teams(minPlayers, arr) {
var result = [];
for (var i = minPlayers; i <= arr.length; i++) {
result = result.concat(picks(i, arr))
}
return result;
}
// picks all possible n items from arr (hopefully)
function picks(n, arr) {
// will hold all possible series (pick of n items from array)
var result = [];
if (n == 1) {
// base case return n arrays of 1 item
for (var i = 0; i < arr.length; i++) {
result.push([arr[i]])
}
return result;
}
// else we will loop over each of items
for (var i = 0; i < arr.length; i++) {
// make a copy since we are going to remove items from it
var copy = [...arr];
// pick an item, the first one of our series
var item = copy.splice(i, 1);
// ignore those before it
for (j = 0; j < i; j++) {
// remove an item from beginning of array
copy.shift();
}
// recursion! get picks of the rest n-1 items
var others = picks(n - 1, copy);
// combine each pf those picks with our item
others.forEach(function(other) {
// push to result the series which is [item, ...rest]
result.push(item.concat(other))
})
}
// return array of arrays
return result;
}
console.log(teams(3, [4, 6, 5, 10]))
.as-console-wrapper {
max-height: 100% !important;
}
Last function can be refactored a little since base case looks similar to the other steps and no need to mutate the array. Here's the shorter version:
// loop for each possible minPlayers and above
function teams(minPlayers, arr) {
var result = [];
for (var i = minPlayers; i <= arr.length; i++) {
result = result.concat(picks(i, arr))
}
return result;
}
// recursive function to pick all possible n items from arr
function picks(n, arr) {
var result = [];
for (var i = 0; i < arr.length; i++) {
// foreach item
var item = arr[i];
if (n == 1) {
// if we need pick 1, then pick item
result.push([item])
} else {
// pick n-1 possible except our item (and those before it)
var others = picks(n - 1, arr.slice(i + 1));
// combine with our item n times
others.forEach(function(other) {
result.push([item, ...other])
})
}
}
return result;
}
console.log(teams(3, [4, 6, 5, 10]))
.as-console-wrapper {
max-height: 100% !important;
}

How do I filter all items that occur once into one list and all items that occur multiple times into a different one?

I'm currently working on a project but I'm stuck with removing all the duplicates.
I need to remove all the duplicate names and put into a separate file
This is an example of what Im trying to achieve:
So I have an array of numbers (1,2,2,3,3,4,5) and I would like to remove all of the duplicates from the array to result in (1,4,5).
For loop the array and place each value into a hash map that tracks the number of times that number is recorded. Then loop through your hash map and create a new array with only the values that have a record of 1.
const arr = [1, 2, 2, 3, 3, 4, 5];
function removeDuplicates(arr) {
var hashMap = {};
for(let i of arr) {
if(hashMap[i]){
hashMap[i] += 1
} else {
hashMap[i] = 1
}
}
var newArray = [];
for(let [key, value] of Object.entries(hashMap)){
if(value === 1) {
newArray.push(parseInt(key));
} else {
// If you want to do something with values recorded more
// than once, you can do that here.
}
}
return newArray;
}
Without using any external libraries - I am sure there are more concise ways to do it, but this should work:
var numbers = [1, 2, 2, 3, 3, 4, 5];
function removeDuplicates(array) {
var existingValues = []; // Holds all values that exist at least once
var duplicates = []; // Holds all values that are duplicates
array.forEach(function(num) {
if (existingValues.indexOf(num) === -1) {
existingValues.push(num);
} else {
duplicates.push(num);
}
});
// Filter out the values from existingValues that are in the duplicates array
return existingValues.filter(function(i) {
return duplicates.indexOf(i) === -1;
});
}
console.log(removeDuplicates(numbers)); // [1,4,5]
Will the array always be sorted?
no , but that might be something to consider #Thomas
OK, this would have allowed for something like this:
Just looking at the neighbors to determine wether a value is single or has multiple occurances.
const array = [1,2,2,3,3,4,5];
const single = [];
const multiple = [];
for (let i = 0, length = array.length; i < length; ++i) {
let value = array[i];
const isDupe = i > 0 && value === array[i - 1]
|| i + 1 < length && value === array[i + 1];
if (isDupe) {
multiple.push(value);
} else {
single.push(value);
}
}
console.log("singles", single);
console.log("multiple", multiple);
If the data ain't guaranteed to be sorted we need to do a count pass first the check which items are unique in that array and which ones are not. And in a secnd pass we can add them to the result arrays.
const array = [3, 2, 4, 2, 5, 1, 3];
const single = [];
const multiple = [];
const count = {};
for (let i = 0; i<array.length; ++i) {
let value = array[i];
count[value] = (count[value] || 0) + 1;
}
for (let i = 0; i<array.length; ++i) {
let value = array[i];
if (count[value] > 1) {
multiple.push(value);
} else {
single.push(value);
}
}
console.log("singles", single);
console.log("multiple", multiple);
Based on the input you gave: [1, 2, 2, 3, 3, 4, 5] and the fact you said you wanted two outputs: one with the unique values, [1,4,5], and one with duplicates [2,2,3,3].
The below function will give you two arrays as outputs, one with the unique values, and one with the duplicates.
const getUniqueAndDuplicates = (arr) =>{
//use a JavaScript object as a map to count frequency
const map={};
for(let i=0;i<arr.length;i++){
if(map[arr[i]]){map[arr[i]]++;}
else{map[arr[i]]=1;}
}
const uniqueArray=[];
const duplicateArray=[];
for(let key in map){
//get the frequency count
let freq=map[key];
if(freq===1){uniqueArray.push(key);}
else{
for(let i=0;i<freq;i++){
duplicateArray.push(key);
}
}
}
return [uniqueArray,duplicateArray];
}
There's many ways to remove duplication in array. Here's some samples.
Using Set()
Set objects are collections of values. You can iterate through the
elements of a set in insertion order. A value in the Set may only
occur once
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set
const duplicated = [1,2,3,2,3,4,3,5];
const uniqSet = new Set(duplicated);
console.log([...uniqSet]) // Should be [1, 2, 3, 4, 5]
Using lodash uniq() method
Document: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set
const _ = require('lodash');
const duplicated = [1,2,3,2,3,4,3,5];
const uniq = _.uniq(duplicated);
console.log(uniq) // Should be [1, 2, 3, 4, 5]
Scratch
const duplicated = [1,2,3,2,3,4,3,5];
const uniq = [];
for (const e of duplicated) {
if (!uniq.includes(e)) {
uniq.push(e)
}
}
console.log(uniq) // Should be [1,2,3,4,5]

Return sorted unique values from array, but if count is equal, return values in order

I am creating a function that takes in an array of unsorted integers and returns an array of the unique integers, sorted by frequency. However, if the integers have the same frequency, they will be returned in the original order of the input array. Here is my current function:
function uniqueUnionSorted(arr) {
counter = {};
for(var i=0; i<arr.length; i++) {
if (arr[i] in counter) {
counter[arr[i]] ++;
} else {
counter[arr[i]] = 1;
}
}
sortedStrings = Object.keys(counter).sort(function(a,b) {
return counter[b] - counter[a]
});
var sortedNumbers = sortedStrings.map(Number);
return sortedNumbers;
}
So for an array like this:
arr = [1, 3, 2, 1, 5, 2, 1, 4]
the function should return:
[1,2,3,5,4]
However, my function is sorting the 5 and 4 and is returning:
[1,2,3,4,5]
Please help!
The cause of this reordering is that object properties that are numerical will come out ordered when using Object.keys().
Instead of defining counter as an object, use a Map, which will retain the insertion order:
function uniqueUnionSorted(arr) {
var counter = new Map();
for(var i=0; i<arr.length; i++) {
counter.set(arr[i], (counter.get(arr[i]) || 0) + 1);
}
// Spreading the Map will produce an array of pairs
var sortedNumbers = [...counter].sort(function(a,b) {
return b[1] - a[1]; // sort by count
}).map(a => a[0]); // only keep the values, not the counts
return sortedNumbers; // Map keys retain original type, so they remain numeric
}
arr = [1, 3, 2, 1, 5, 2, 1, 4]
console.log(uniqueUnionSorted(arr));
In counter object we can also save lowest index of each element so we can keep elements with lower index earlier in sorted array.
function uniqueUnionSorted(arr) {
counter = {};
for(var i=0; i<arr.length; i++) {
if (arr[i] in counter) {
counter[arr[i]].count ++;
} else {
counter[arr[i]] = {'count' : 1, 'index' : i}; //save lowest index of element
}
}
sortedStrings = Object.keys(counter).sort(function(a,b) {
return counter[b].count - counter[a].count || counter[a].index - counter[b].index;
});
var sortedNumbers = sortedStrings.map(Number);
return sortedNumbers;
}
console.log(uniqueUnionSorted([1, 3, 2, 1, 5, 2, 1, 4]));
https://jsfiddle.net/anLrwwfa/4/
Here's another way you could do this using a Set object, an object to store frequency, and the original array to keep the origin intact. It's a bit longer than the current popular answer but I was in the midst of writing it, so I figured I would throw my hat in the ring.
function sortArr(arr) {
let uniqueValues = new Set();
let frequencies = new Object();
//add values to set object
arr.map((val) => uniqueValues.add(val));
//get frequencies of values
for (let val of uniqueValues) {
frequencies[val] = 0;
}
arr.map((val) => frequencies[val]++);
//sort by frequency, then sort by original placement in array
let sorted_arr = Array.from(uniqueValues).sort((a, b) => {
return frequencies[a] - frequencies[b];
}).sort((a, b) => {
return (frequencies[a] === frequencies[b]) ?
Array.from(uniqueValues).indexOf(a) - Array.from(uniqueValues).indexOf(b) :
b;
});
//return array
return sorted_arr;
};
sortArr([1, 3, 2, 1, 5, 2, 1, 4]); //1,2,3,5,4
EDIT
optimized code a bit
May add an additional condition to actually sort after the original array index if the frequency is equal:
sortedStrings = Object.keys(counter).sort(function(a,b) {
return counter[b] - counter[a] || arr.indexOf(+a)-arr.indexOf(+b)
});
http://jsbin.com/zewulanozo/edit?console

populating array of arrays with distinct values

I have an array of array with elements for example:
var one = [1span,2span,3span,4span,5span,6span,7span];
var two = [1span,2span,3span,4span,5span,6span,7span];
var three = [1span,2span,3span,4span,5span,6span,7span];
var ...till seven.
var total = [one,two,three ..till 7]
so basically we have 7 arrays and total variable will display 7 elements for each one of all 7 arrays.
now I have a function that should populate my variables with distinct numbers from 1 to 7 on each section.
var bar = [1, 2, 3, 4, 5, 6, 7];
for(var j=0; j<total.length; j++) {
Array.from(total[j]).forEach(function(e, i, a) {
e.textContent = bar[Math.round(Math.random()*(bar.length-1))];
console.log(e,i,k);
});
}
all good my function does that but unfortunately is populating span elements with values from bar variable for each variable from total var 7 times for each and should populate just once for each variable.
So my problem is:
I want to populate each variable from total var with values from bar array just once.
The values should be randomly and unique for each variable.
You could use an copy of the given array and generate random items without repeat.
function generate(count, values) {
return Array.apply(null, { length: count }).map(function () {
var r = [],
array = values.slice();
while (array.length) {
r.push(array.splice(Math.floor(Math.random() * array.length), 1)[0]);
}
return r;
});
}
console.log(generate(7, [1, 2, 3, 4, 5, 6, 7]));
Randomize a single array
function generate(values) {
var r = [],
array = values.slice();
while (array.length) {
r.push(array.splice(Math.floor(Math.random() * array.length), 1)[0]);
}
return r;
}
console.log(generate([1, 2, 3, 4, 5, 6, 7]));
If we would like to extend the question for a general case of an N dimensional array filled with random integers then a reusable approach could be as follows;
We will use two generic functions that i like to use very much; Array.prototype.clone() and Array.prototype.shuffle(). Our arrayND function takes indefinite number of arguments. The last argument will designate the minimum (base) of the random integer to be filled. Previous arguments will give the length of each dimension. So in the particular case as we will need a 2D 7x7 matrice to be filled with random but unique numbers in each starting from 1, we shall invoke our function as arrayND(7,7,1)
Array.prototype.shuffle = function(){
var i = this.length,
j;
while (i > 1) {
j = ~~(Math.random()*i--);
[this[i],this[j]] = [this[j],this[i]];
}
return this;
};
Array.prototype.clone = function(){
return this.map(e => Array.isArray(e) ? e.clone() : e);
};
function arrayND(...n){
return n.reduceRight((p,c) => c = Array(...Array(c)).map((e,i) => Array.isArray(p) ? p.clone().shuffle() : i+p));
}
var arr = arrayND(7,7,1);
console.log(arr);
since you want these Arrays to be processed together you should put them together into a data-structure:
var spans = [ one, two, tree, ..., seven ]
now you want a random order without repetition, put the possible indices into an Array and shuffle that:
var indices = [0,1,2,3,4,5,6];
and some shuffler:
function shuffle(arr){
for(var i=arr.length, j, tmp; i-- > 1; ){
tmp = arr[j = 0|(Math.random()*i)];
arr[j] = arr[i];
arr[i] = tmp;
}
return arr;
}
now if you want the Nodes for total:
var total = shuffle(indices).map((index, n) => spans[n][index]);
at least as far as I understand your question/code/intentions.

Javascript's "splice" method strange behavior

I was trying to solve some of the programming challenges at "free code camp" website, The problem was to find the symmetric difference between multiple arrays and returning an array of the symmetric difference of the provided arrays.
for example the following arrays:
[1, 2, 5], [2, 3, 5], [3, 4, 5]
should return [ 1, 4, 5 ]
so that's what I came up with:
function sym() {
var final = [];
var current_array = [];
for (var i = 0; i < arguments.length; i++) {
current_array = arguments[i];
//ensures duplicates inside each array are removed first
current_array = current_array.filter(function (element, index) {
return current_array.indexOf(element) == index;
});
for (var j = 0, end = current_array.length; j < end; j++) {
if(final.indexOf(current_array[j]) < 0)
final.push(current_array[j]);
else
// for some reason "splice" isn't working properly..
// final.splice(final.indexOf(current_array[j], 1));
delete final[final.indexOf(current_array[j])];
}
}
var final_2 = [];
// Removing the empty slots caused by the "delete" keyword usage
for (var m = 0; m < final.length; m++) {
if(typeof final[m] !== 'undefined')
final_2.push(final[m]);
}
return final_2;
}
in the previous logic I created an array called final that is supposed to hold all of the elements that only exist once in all of the passed arrays, firstly I loop over the arguments parameter which represents here the arrays and for each array I loop over its elements and check if that element exists in the final array or not. If it exists I remove it from the final array, else I push it to the array.
The problem here is if I use the splice method as given in the code above, it behaves very strangely, for example for the following arrays
[1, 2, 3], [5, 2, 1, 4], the result should be: [3, 5, 4]
when I use this line
final.splice(final.indexOf(current_array[j], 1));
instead of
delete final[final.indexOf(current_array[j])];
and return the final array it returns this [ 4 ]
here is the array values at each iteration
round (0, 0): 1
round (0, 1): 1,2
round (0, 2): 1,2,3
round (1, 0): 1,2,3,5
round (1, 1): 1
round (1, 2):
round (1, 3): 4
once it gets to an element that exists in the array it removes all of the elements starting from this element until the end of the array.
I don't know exactly if I'm missing something, I tried to search for any similar problems but most of what I came up with was a problem of removing elements from an array that the person was looping over and hence missing with its indices .. In my case the array I'm trying to modify got nothing to do with the arrays I'm iterating through.
Also I believe splice modifies the array in place and returns the removed elements.. please correct me if I'm not getting it well.
You've misplaced a ), here's the correction:
final.splice( final.indexOf(current_array[j]), 1 );
An additional note: the algorithm adds 5 for the first array, removes it for the second, and adds it again for the third (since it isn't present in final anymore), resulting in [1,4,5].
With an odd number of arguments, the value is preserved, with an even number, it is removed.
A simpler way to get all unique values from all arrays (if that is the intent), is to count the occurrences and filter on a single occurrence:
function sym2() {
var count = {};
for ( var i in arguments ) {
console.log("Processing ", i );
for ( var k = 0; k < arguments[i].length; k ++)
count[ arguments[i][k] ] = (count[ arguments[i][k] ]||0) + 1;
}
var final = [];
for ( var i in count )
if ( count[i] == 1 )
final.push( i );
return final;
}
sym2([1, 2, 5], [2, 3, 5], [3, 4, 5]);
Note that this will return [1,4] rather than [1,4,5].

Categories