Cartesian product without duplicates - javascript

I am using a cartesian product function that given [1], [1,2,3], [1,2,3] returns 9 combinations:
[ [ 1, 1, 1 ],
[ 1, 2, 1 ],
[ 1, 3, 1 ],
[ 1, 1, 2 ],
[ 1, 2, 2 ],
[ 1, 3, 2 ],
[ 1, 1, 3 ],
[ 1, 2, 3 ],
[ 1, 3, 3 ] ]
But I need to remove those with the same items regardless of the order, so [ 1, 3, 1 ] and [ 1, 1, 3 ] are the same to me. The result should contain 6 items:
[ [ 1, 1, 1 ],
[ 1, 2, 1 ],
[ 1, 3, 1 ],
[ 1, 2, 2 ],
[ 1, 3, 2 ],
[ 1, 3, 3 ] ]
I can write a function that compares all possible pairs with _.xor, but for larger numbers it will probably be very inefficient. Is there a good way in Javascript to do this? An efficient way to compare all possible pairs or an algorithm for cartesian product without duplicates?

sort each array of the cartesian product
[ 1, 2, 1 ] -> [1 , 1 , 2]
[ 1, 1, 2 ] -> [1 , 1 , 2]
then gather these sorted arrays into a set, that will remove the duplicates.
Of course, you can do that while constructing the cartesian product rather than afterward.

JavaScript has Set and Map, however they compare objects and arrays by reference rather than by value, so you cannot take advantage of it directly. The idea is to use a key function which sorts and json encodes the items before putting it in a set.
pure ES5:
function product(sets) {
if (sets.length > 0) {
var head = sets[0];
var tail = product(sets.slice(1));
var result = [];
head.forEach(function(x) {
tail.forEach(function(xs) {
var item = xs.slice(0);
item.unshift(x);
result.push(item);
});
});
return result;
} else {
return [[]];
}
}
function myKeyFn(item) {
return JSON.stringify(item.slice(0).sort());
}
function uniqBy(items, keyFn) {
var hasOwn = Object.prototype.hasOwnProperty, keyset = {};
return items.filter(function(item) {
var key = keyFn(item);
if (hasOwn.call(keyset, key)) {
return false;
} else {
keyset[key] = 1;
return true;
}
});
}
function uniqProduct(sets) {
return uniqBy(product(sets), myKeyFn);
}
function log(x) {
console.log(x);
var pre = document.createElement('pre');
pre.appendChild(document.createTextNode(x));
document.body.appendChild(pre);
}
log(uniqProduct([[1],[1,2,3],[1,2,3]]).map(JSON.stringify).join("\n"));
<pre></pre>
lodash + modern JavaScript:
// Note: This doesn't compile on current babel.io/repl due to a bug
function product(sets) {
if (sets.length > 0) {
const [x, ...xs] = sets;
const products = product(xs);
return _.flatMap(x, head => products.map(tail => [head, ...tail]));
} else {
return [[]];
}
}
function uniqProduct(sets) {
return _.uniqBy(product(sets), x => JSON.stringify(x.slice(0).sort()));
}
console.log(uniqProduct([[1],[1,2,3],[1,2,3]]).map(JSON.stringify).join("\n"));

JavaScript has set data structure.
So store your results in a set where each element of the set is a collection of pairs of numbers from the original sets along with the number of times that number occurs.
So your result would look something like this:
[
{1:3},
{1:2, 2: 1},
{ 1:2, 3:1},
{ 1:1, 2:2},
{ 1:1, 2:1, 3:1},
{ 1:1, 3:2 } ]
This way, you won't be able to add the object a second time to the set.

Related

What is the best way to check multidimensional array in typescript or javascript to detect duplicate values?

I have two dimensional array like below:
array = [ [ 1, 1 ], [ 1, 2 ], [ 1, 1 ], [ 2, 3 ] ]
I want to compare value in array index to see if they have duplicate values. For example
array[0] = [1,1];
array[1] = [1,2];
array[2] = [1,1];
We can see that value at index 0 and 2 are same that is [1,1]. So, in that case I want to have true flag. What is the most efficient way to do it? or What are different ways to do it? Any kind of suggestion or help would be great with bit of explanation. Thanks in advance.
You can achieve it by convert the inner array elements into a string just for the comparison purpose.
Demo :
const arr = [[ 1, 1 ], [ 1, 2 ], [ 1, 1 ], [ 2, 3 ]];
const stringConversion = arr.map((item) => JSON.stringify(item))
const duplicateElements = stringConversion.filter((item, index) => stringConversion.indexOf(item) !== index)
console.log(duplicateElements.length ? true : false);
So, what I think you can do is:
Cycle every element of the multidimensional array.
Then check if the two element are the same by accessing the "row" with the index 0 and index 1
I think you can use different way to loop the array, this is what I tried:
// DECLARATIONS
array = [[ 1, 1 ], [ 1, 2 ], [ 1, 1 ], [ 2, 3 ]];
// LOOPING THE ARRAY
for (row of array)
{
// RETURN TO CONSOLE OR WHATEVER THE BOOLEAN VALUE
console.log(row[0] == row[1]);
}
String to convert array to a one-dimensional array of strings and some to check if any element is duplicated:
const arr = [[1, 1], [1, 2], [1, 1], [2, 3]];
const result = arr.map(String).some((v, i, a) => a.indexOf(v) !== i);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

How to loop/iterate over a 2D array using every()?

I cannot find a solution that uses method every(). I want to see if each coordinate (both x and y) is <= 10. So, the following example should return true.
Here is my code:
const shipLocation = [ [ 2, 3 ], [ 3, 3 ], [ 4, 3 ], [ 5, 3 ], [ 6, 3 ] ]
const outOfBounds = function (shipLocation) {
Array.every(locationPoint =>
// code here!
locationPoint <= 10;
);
};
Thank you.
const shipLocation = [ [ 2, 3 ], [ 3, 3 ], [ 4, 3 ], [ 5, 3 ], [ 6, 3 ] ]
const outOfBounds = shipLocation.every(cords=> (cords[0]<=10) && (cords[1]<=10))
You need to return a value (boolean: true or false) from your function.
You have nested arrays so you need to use every on each of them and check that values inside those arrays are less or equal to 10 also by using every again, making sure you return true or false from that callback too.
const shipLocation=[[2,1],[3,3],[4,3],[5,3],[6,3]]
const shipLocation2=[[2,41],[3,3],[4,3],[5,3],[6,3]];
function outOfBounds(shipLocation) {
// For every ship location array
return shipLocation.every(arr => {
// Return whether every value is <= 10
return arr.every(el => el <= 10);
});
};
console.log(outOfBounds(shipLocation));
console.log(outOfBounds(shipLocation2));
You can use the flat() function to make the 2d array into a 1d array:
const shipLocation = [ [ 2, 3 ], [ 3, 3 ], [ 4, 3 ], [ 5, 3 ], [ 6, 3 ] ];
const outOfBounds =
shipLocation.flat().every(locationPoint =>
locationPoint <= 10 // do not put ";"
);
console.log(outOfBounds);

Javascript - Is there a more efficient way to create an array of arrays? - Examples provided

Question: Is there a more efficient way of creating an array of arrays of incrementing numbers?
I've created a function to produce an array of arrays of incrementing numbers, which took far longer than expected, and I'm sure there is a more efficient way to achieve this (I'm new to JS).
Note for the genArray function of both example 1 and 2:
argu1 declares the start of the number range (e.g. 0 = start from 0),
argu2 declares the end of the number range (e.g. 9 = end at 9),
argu3 declares how many numbers are needed in each individual array (e.g. 3 = generate 3 numbers in the array),
argu4 carries the temp array to generate a single array of numbers,
argu5 carries the array of arrays through the function and nested functions.
Example 1: Below is the code purely for creating an array of arrays of incrementing numbers. My question refers to making a more efficient version of this function.
function genArray(start, finish, quantity, array, allArray = []) {
var collectArray = allArray;
//Cycle through digits from start to finish, e.g. 0-9
for (var i = start; i <= finish; i++) {
var tempArray = [];
//Collect digits for a single array if not first iteration
if (array !== undefined) {
tempArray = tempArray.concat(array);
};
//Add digit to single level array
tempArray.push(i);
//If not highest level, go higher
if (quantity > 1) {
var genArray2 = genArray(start, finish, quantity-1, tempArray, collectArray);
}
//If highest level collect a single array
else if (quantity == 1) {
collectArray.push(tempArray);
}
}
return collectArray;
}
//Call function with arguments
//argu1 declares the start of the number range, argu2 declares the end of the number range, argu3 declares how many numbers are needed in each individual array, argu4 carrays the temp array to generate a single array of numbers, argu4 carrys the array of arrays throught the function and nested functions.
var genArray2 = genArray(0, 9, 3);
console.log(genArray2);
This produces a log like so:
[ [ 0, 0, 0 ],
[ 0, 0, 1 ],
[ 0, 0, 2 ],
[ 0, 0, 3 ],
[ 0, 0, 4 ],
[ 0, 0, 5 ],
[ 0, 0, 6 ],
[ 0, 0, 7 ],
[ 0, 0, 8 ],
[ 0, 0, 9 ],
[ 0, 1, 0 ],
[ 0, 1, 1 ],
[ 0, 1, 2 ],
[ 0, 1, 3 ],
[ 0, 1, 4 ],
[ 0, 1, 5 ],
[ 0, 1, 6 ],
[ 0, 1, 7 ],
[ 0, 1, 8 ],
[ 0, 1, 9 ],
[ 0, 2, 0 ],
[ 0, 2, 1 ],
[ 0, 2, 2 ],
[ 0, 2, 3 ],
[ 0, 2, 4 ],
[ 0, 2, 5 ],
[ 0, 2, 6 ],
[ 0, 2, 7 ],
[ 0, 2, 8 ],
[ 0, 2, 9 ],
[ 0, 3, 0 ],
[ 0, 3, 1 ],
[ 0, 3, 2 ],
[ 0, 3, 3 ],
[ 0, 3, 4 ],
.... up to [ 9, 9, 9 ]
Example 2: Below is the code I'm actually using, with the only change being the addition of a check to see if an array produced is ascending and each number is unique, and storing only those that are true in both cases. Providing this for context and in case it's useful to someone:
//Check if ascending
function ascending(x) {
return x == parseInt(x.toString().split('').sort().join(''));
}
//Check if unique
function unique(x) {
return x.toString().split('').length == [...new Set(x)].length
}
//Create an array of arrays of ascending and unique numbers
function genArray(start, finish, quantity, array, allArray = []) {
var collectArray = allArray;
//Cycle through digits from start to finish, e.g. 0-9
for (var i = start; i <= finish; i++) {
var tempArray = [];
//Collect digits for a single array if not first iteration
if (array !== undefined) {
tempArray = tempArray.concat(array);
};
//Add digit to single level array
tempArray.push(i);
//If not highest level, go higher
if (quantity > 1) {
var genArray2 = genArray(start, finish, quantity-1, tempArray, collectArray);
}
//If highest level collect a single array
else if (quantity == 1 && ascending(tempArray.join('')) && unique(tempArray.join(''))) {
collectArray.push(tempArray);
}
}
return collectArray;
}
//Call function with arguments
var genArray2 = genArray(0, 9, 3);
console.log(genArray2);
This produces a log like so:
[ [ 0, 1, 2 ],
[ 0, 1, 3 ],
[ 0, 1, 4 ],
[ 0, 1, 5 ],
[ 0, 1, 6 ],
[ 0, 1, 7 ],
[ 0, 1, 8 ],
[ 0, 1, 9 ],
[ 0, 2, 3 ],
[ 0, 2, 4 ],
[ 0, 2, 5 ],
[ 0, 2, 6 ],
[ 0, 2, 7 ],
[ 0, 2, 8 ],
[ 0, 2, 9 ],
[ 0, 3, 4 ],
[ 0, 3, 5 ],
[ 0, 3, 6 ],
[ 0, 3, 7 ],
[ 0, 3, 8 ],
[ 0, 3, 9 ],
[ 0, 4, 5 ],
[ 0, 4, 6 ],
[ 0, 4, 7 ],
[ 0, 4, 8 ],
[ 0, 4, 9 ],
[ 0, 5, 6 ],
[ 0, 5, 7 ],
[ 0, 5, 8 ],
[ 0, 5, 9 ],
[ 0, 6, 7 ],
[ 0, 6, 8 ],
[ 0, 6, 9 ],
[ 0, 7, 8 ],
[ 0, 7, 9 ],
[ 0, 8, 9 ],
[ 1, 2, 3 ],
[ 1, 2, 4 ],
[ 1, 2, 5 ],
[ 1, 2, 6 ],
.... up to [ 7, 8, 9 ]
Without recursion you will be able to speed it up. Here is a loop that just uses the previously added subarray to calculate the next. It uses the mechanism one has when adding a 1 to a decimal number: first increment the right most digit. If it goes out of range (in decimal: it becomes 10), then set it back to the lowest digit and increment the digit on its left, ...etc, until the last changed digit remains within range:
function genArray(start, finish, quantity) {
const current = Array(quantity).fill(start);
const result = [];
for (let i = quantity; i >= 0; null) {
result.push(current.slice());
for (i = quantity; i--; null) {
current[i]++;
if (current[i] <= finish) break;
current[i] = start;
}
}
return result;
}
console.log(genArray(0, 2, 3));
If you are willing to do a little math, there is a pretty quick easy way to do this in general terms. The basic insight is that a number like 768 can be broken down to various log10 components taken modulo 10. For example Math.floor(768/100) % 10 gets you the third digit. Math.floor(768/10) % 10 get you the second. To get the length of the inner arrays you need you can take Math.floor(Math.log10(largestNumber + 1)). So for 1000 this will be 4, for 999 it will be 3, etc. The only annoying part of this arrangement it that arrays are build left to right but numbers are build right to left. Thats why we are working with length - index in the inner arrays.
You can put this together with Array.from to make a succinct function that avoids a lot of string parsing and if/else clauses:
function genArray(start, finish) {
return Array.from({length: finish - start + 1}, (_, i) => {
let ind = i + start
let innerLength = Math.floor(Math.log10(finish + 1))
return Array.from({length: innerLength + 1}, (_, i) => Math.floor(ind / (10 ** (innerLength - i))) % 10)
})
}
let a = genArray(0, 20)
console.log(a.join(' · '))
a = genArray(1020, 1040)
console.log(a.join(' · '))
Also, it's not clear how large your arrays will be, but if you are working with large sets of numbers, it can be a little more memory efficient to make a generator so you only produce the inner arrays as needed. It's not right solution for everything, but since it's almost the same code, I thought I'd mention it:
function* genArray(start, finish) {
let innerLength = Math.floor(Math.log10(finish + 1))
while (start <= finish) {
yield Array.from({length: innerLength + 1}, (_, i) => Math.floor(start / (10 ** (innerLength - i))) % 10)
start++
}
}
let a = genArray(101, 105)
console.log(a.next().value)
let b = genArray(20, 30)
console.log([...b].join(' · '))
This is a possible solution:
function genArrays(start, end){
let max_len = String(end).length;
let arr = [];
let cur_num = start;
let arr_num;
for (let i = start; i <= end; i++){
str_num = String(cur_num).padStart(max_len, '0').split('').map(x => parseInt(x));
arr.push(str_num);
cur_num++;
}
return arr;
}
console.log(genArrays(0, 1000));
console.log(genArrays(102, 1043));
The core is here: str_num = String(cur_num).padStart(max_len, '0');. A counter is firstly stringed and then it is applied a padding on the left in order to reach the length of the stringed end.
I don't speak a perfect english, and I hope to understand your question.
From the example you provided, it seems you just need a 2 level array, the first one containing n arrays of incremental numbers.
Instead of using a recursive function, that use lot of memory, you can try to use a normal for cycle, and a split on every number, padded with 0s to get the n length.. Don't know if it could work for you.
0002.split create an array [0,0,0,2]..
and then push it on the main array
Should be the fastest way
//s start, e end, n array size
f=new Array();
a=new Array();
for (i=s; i<e; i++) {
t=pad(i,n);
a=i.toString().split('');
f[f.length]=a;
}
function pad(s,n) {
while (s.length < n)
s = '0' + s;
return s;
};
Cheers
Daniele

find unique entries inside a multidimensional javascript array, order important dependent on level

What would be the most elegant solution to find all unique first level entries inside a multidimensional javascript array? There is only one important rule: the order of the entries is important only on the first level, but not important on the second level
For example, for the following array the script should return 4 unique entries (the first, the third, the fourth and the fifth):
[
[ [],[22],[1,13,17],[12],[] ],
[ [],[22],[17,13,1],[12],[] ],
[ [],[12],[1,13,17],[22],[] ],
[ [11],[12],[13],[14],[15] ],
[ [15],[14],[13],[12],[11] ]
]
PS. jQuery can be used as well.
First of all, here is a working JSFiddle for you to play around with: http://jsfiddle.net/missyalyssi/ro8o94nk/
Given an input array the function findUnique will return an array containing items that are unique according to your definition. So, for example:
[[8],[1,2,3],[9]] is a duplicate of [[8], [3,1,2], [9]] but it is not a duplicate of [[9], [3,1,2], [8]]
My main focus when writing this was to make it easy to read and understand.
function findUnique(input) {
let found = [];
let uniqueEls = new Set();
let hasDup = true;
for (let element of input) {
hasDup = found.length &&
found.every((el) => {return deepEqualsNaive(el, element)});
if (hasDup) {
uniqueEls.delete(element);
continue;
}
found.push(element);
uniqueEls.add(element);
}
return [...uniqueEls];
}
This function uses deepEqualsNaive to determine if two arrays are equal. Since object equality in javascript means that the arrays would point to the same memory location we need to build our own function to return true for what we are calling equal. Here, by equal we mean that they have the same elements even though they are not pointing to the same memory location, or appearing in the same order.
I have written this function recursively for readability I do not know the context that you are using this in. If you could overflow the stack then use an iterative version.
Here are some example inputs and what we would expect:
deepEqualsNaive([ [],[22],[1,13,17],[12],[] ], [ [],[22],[17,13,1],[12],[] ]) => true
deepEqualsNaive([ [],[22],[17,13,1],[12],[] ], [ [],[12],[1,13,17],[22],[] ]) => false
deepEqualsNaive([ [],[22],[1,13,17],[12],[] ], [ [],22,[17,13,1],[12],[] ]) => false
The function:
function deepEqualsNaive (input, clone) {
if (!Array.isArray(input) || !Array.isArray(clone)) return false;
if (input.length !== clone.length) return false;
var result = 0;
for (let elIdx = 0; elIdx < input.length; elIdx++) {
var tryDeep = true;
if (Array.isArray(input[elIdx])) tryDeep = deepEqualsNaive(input[elIdx], clone[elIdx]);
if (!tryDeep) return false;
result ^= input[elIdx];
result ^= clone[elIdx];
}
return result === 0;
}
If you're not all that worried about performance and just need something that works, you could use the constant depth you mentioned along with the string representation as a "fingerprint" of sorts (akin to Java's hashcode).
Then you use a Set to keep track of items you've not seen before, and add only those that are new.
function getUnique(rows) {
let unique = new Set();
let results = [];
for (let row of rows) {
// Fingerprint is the string representation of the row,
// with the inner-level sorted (as order doesn't matter).
// E.g., fingerprint of [ [8], [3, 2, 1], [9] ] is '[[8],[1,2,3],[9]]'
let fingerprint = JSON.stringify(row.map((cells) => {
return cells.concat().sort(); // Use concat to avoid sorting in place.
}));
// If we haven't seen this fingerprint before,
// add to the filter and the results list.
if (!unique.has(fingerprint)) {
unique.add(fingerprint);
results.push(row);
}
}
return results;
}
This, for example, will come up with...
> x = [
... [ [8], [3, 2, 1], [9] ],
... [ [7], [8, 3, 9], [1, 2] ],
... [ [8], [1, 2, 3], [9] ],
... ];
> getUnique(x);
[ [ [ 8 ], [ 3, 2, 1 ], [ 9 ] ],
[ [ 7 ], [ 8, 3, 9 ], [ 1, 2 ] ] ]
Obviously if your inner values are non-primitives (objects, arrays, etc) then this will fall over, but if you're dealing with numbers like your example, it should be fine.
If it's ok to have reference to the original array 'records' and inner arrays (that is, no deep copy), you can use something like:
function distinct(arr){
const res =[], //array with results
cmpArr = (a1,a2) => a1.length===a2.length && a1.every((i,ind) => a2[ind] === i),
cmpRec = (a1,a2) => [1,2,3].every(i=> cmpArr(a1[i],a2[i])); //compare 'records' for indices 1,2 and 3
for(let subarr of arr){
subarr[2].sort(); //NB, this alters the source array. If this is not allowed, a work around can be created
if(!res.some(r => cmpRec(r,subarr))) //check if 'res' doesn't have an entry , based on the cmpRec function
res.push(subarr);
}
return res;
}
//test:
let input = [
[ [],[22],[1,13,17],[12],[] ],
[ [],[22],[17,13,1],[12],[] ],
[ [],[12],[1,13,17],[22],[] ],
[ [11],[12],[13],[14],[15] ],
[ [15],[14],[13],[12],[11] ]
];
console.log(distinct(input).map(JSON.stringify));

What is wrong with this use of "forEach"?

I expect the errorLogArrayList.length to be 3, but the result is 2.
Could anyone please tell me why this code doesn't work as I expect and how to fix it?
CODE:
var logArrayList = [
[1,2,3],
[1,2,"error"],
[1,2,"error"],
[1,2,"error"]
]
var errorLogArrayList = [];
console.log(logArrayList);
console.log("======================");
logArrayList.forEach(function(logArray, index, self) {
if(logArray[2] === "error") {
var errorArray = self.splice(index, 1)[0];
errorLogArrayList.push(errorArray);
}
});
// I expect this value to be 3, but the result is 2.
console.log("errorLogArrayList.length is " + errorLogArrayList.length)
console.log(errorLogArrayList);
console.log("======================");
// I expect this value to be 1, but the result is 2.
console.log("logArrayList.length is " + logArrayList.length);
console.log(logArrayList);
LOG:
[ [ 1, 2, 3 ],
[ 1, 2, 'error' ],
[ 1, 2, 'error' ],
[ 1, 2, 'error' ] ]
======================
// I expect this value to be 3, but the result is 2.
errorLogArrayList.length is 2
[ [ 1, 2, 'error' ], [ 1, 2, 'error' ] ]
======================
//I expect this value to be 1, but the result is 2.
logArrayList.length is 2
[ [ 1, 2, 3 ], [ 1, 2, 'error' ] ]
You can just do a filter operation:
var errorLogArrayList = logArrayList.filter(function(array) {
return array[2] === 'error';
});
logArrayList = logArrayList.filter(function(array) {
return array[2] !== 'error';
});
Unfortunately this requires some duplicate iterations. You can use _.partition from lodash if you want (looks a little cleaner):
var lists = _.partition(logArrayList, function(array) {
return array[2] === 'error';
});
var errorLogArrayList = lists[0];
logArrayList = lists[1];
The problem with your current approach is that you are trying to keep track of multiple states and modify two arrays at the same time, and this is leading to some data conflicts. filter is usually a little more declarative, and it doesn't mutate the original array, but instead returns a new one.
Edit
Wanted to add a method with reduce as suggested by #zerkms in the comments:
var lists = logArrayList.reduce(function(agg, curr) {
if(curr[2] === 'error') {
agg[0] = (agg[0] || []).concat([curr]);
} else {
agg[1] = (agg[1] || []).concat([curr]);
}
return agg;
}, []);
That does the same as the partition example.

Categories

Resources