array subset combinations in javascript for a card game - javascript

I'm having trouble finding a solution to a combinatronics problem while making a card game in javascript. I'd like to get all combinations of a set such that all elements are used.
given: [1, 2, 3]
returns: [
[[1], [2], [3]],
[[1, 2], 3],
[[1, 3], 2],
[[2, 3], 1],
[[1, 2, 3]]
]
the game is Cassino.

function permute(arr){
const res = [];
for(var map = 1; map < 2 ** arr.length; map++){
const rest = [],
subset = arr.filter((el, i) => (map & (1 << i)) || (rest.push(el), false));
//console.log(subset, rest);
res.push(...(rest.length ? permute(rest).map(arr => [subset,...arr]) : [[subset]]));
}
//console.log(res);
return res;
}
console.log(permute([1,2,3]))
Try it!

This problem can be solved in a recursive manner.
First we need to declare two arrays, one which will contains the input (the given array) and the other will contains the result (initially empty)
var givenArray = [1, 2, 3];
var resultArray = [];
Now let's create our recursive function that will push a subset array to our result array:
function getSubArray(array, position) {
if(position === givenArray.length) {
resultArray.push(array);
return;
}
getSubArray(array.concat(givenArray[position]), position+1);
getSubArray(array, position+1);
}
Now to start set all the subsets to the resultArray we just need to call our getSubArray function with an empty array and a start position which is 0 as arguments
getSubArray([], 0);
After that just remove the last element of the resultArray if you want to remove the empty set ([]) from the result
resultArray.splice(-1,1);
You can test the algorithm online via this link: https://jsbin.com/xigipihebe/edit?js,console

Related

Best way to loop through an array and return the sub arrays that match the first 2 values

Let's say I have 4 arrays:
var arrays = [
[1, 2, 1],
[1, 3, 4],
[1, 2, 3],
[0, 2, 2]
];
And I want to return the child/sub arrays that start with both 1 and 2, what type of loop would I need?
Currently, this is what I have:
var arrays = [
[1, 2, 1],
[1, 3, 4],
[1, 2, 3],
[0, 2, 2]
];
var selected = [1, 2]; // These are the values that need to match
var result = [];
for (var i = 0; i < selected.length; i++) {
for (var j = 0; j < arrays.length; j++) {
if (arrays[i][j] === selected[i]) {
result.push(arrays[i]);
}
}
}
When there's more than 1 value in the selected array, it seems to return all the ones that match 2 on the second index, so the result would be:
[
[1, 2, 1],
[1, 2, 3],
[0, 2, 2]
]
The loop needs to ensure that on the second iteration it's making sure the first value is still true, as my intended result would be:
[
[1, 2, 1],
[1, 2, 3]
]
Please someone help me, I've had my head trying hundreds of different loop and checks variations for 2-3 days.
Thanks so much!!
Jake
Your current code pushes to the result array whenever any given index matches between arrays and selected. Instead you will need to reverse your loops and iterate over selected for every sub array and check if every element matches, if not break the inner loop and don't push.
const arrays = [
[1, 2, 1],
[1, 3, 4],
[1, 2, 3],
[0, 2, 2],
];
const selected = [1, 2]; // These are the values that need to match
const result = [];
for (let i = 0; i < arrays.length; i++) {
let match = true;
for (let j = 0; j < selected.length; j++) {
if (arrays[i][j] !== selected[j]) {
match = false;
break;
}
}
if (match) {
result.push(arrays[i]);
}
}
console.log(result);
A more modern solution would be to use filter() with a nested every() call on selected.
const arrays = [
[1, 2, 1],
[1, 3, 4],
[1, 2, 3],
[0, 2, 2],
];
var selected = [1, 2];
const result = arrays.filter(arr => selected.every((n, i) => n === arr[i]));
console.log(result);
Here is another approach where you turn both arrays to string and check it those inner arrays start with selected array.
var arrays = [
[1, 2, 1],
[1, 3, 4],
[1, 2, 3],
[0, 2, 2]
];
var selected = [1, 2];
const result = arrays.filter(e => e.toString().startsWith(selected.toString()))
console.log(result)
Let's try to put your condition into words. That way, an implementation may come to mind more easily.
A short wording may be: "Take all arrays that match (rather: start with) a certain sub-array." In code, it may look like this:
const arrays = [
[1, 2, 1],
[1, 3, 4],
[1, 2, 3],
[0, 2, 2]
];
const selection = [1, 2];
const result = filterArrays(arrays, selection);
console.log(result);
function filterArrays(arrays, selection) {
const selectedArrays = [];
for (let i = 0; i < arrays.length; ++i) {
const array = arrays[i];
const subarray = array.slice(0, selection.length); // Get starting sub-array
if (compareArrays(subarray, selection)) {
selectedArrays.push(array);
}
}
return selectedArrays;
}
/*Ignore; helper function*/
function compareArrays(array1, array2) {
if (array1.length !== array2.length) return false;
const length = array1.length;
for (let i = 0; i < length; ++i) {
if (array1[i] !== array2[i]) return false;
}
return true;
}
.as-console-wrapper {max-height:100%!important}
Another, more specific wording may be: "Take all arrays that match a selection at an index." Note that we only reworded the "match a sub-array" part. I believe this is what you tried.
Refer to pilchard's answer for an implementation. Note that their implementation assumes the arrays in arrays to be at least the same length as selected.
I see you used var instead of the preferred modern let/const declarators. Here's a short outline of their differences:
let/const declarators:
Block-scoped.
Narrower scope means less name-space pollution.
More similar to declarators in other well-known languages:
Variables of these declarators cannot be used before their declaration (see TDZ).
var declarator:
Function-scoped.
Hoisted and with no TDZ, resulting in this (perhaps confusing) behaviour:
Variables declared with var can be used even before their declaration.
Duplicate declarations are allowed since they are effectively the same.
Also, JavaScript has different kinds of for-loops:
for-loop: The for-loops you used are this kind. It is the most versatile kind.
for...of-loop: A loop to iterate over an iterable object (see iterators). For example, arrays are iterable, so you can get its values with a for...of-loop:
const values = [1, 2, 3];
let sum = 0;
for (const value of array) {
sum += value;
}
console.log(sum); // -> 6
for...in-loop: A loop to iterate over enumerable properties of an object. It is easily confused with a for...of-loop, but MDN's example demonstrates the differences understandably.
In my code example above, the for-loop in filterArrays() can be replaced with a for...of-loop to better convey my intention: To iterate over all arrays in arrays, disregarding their index:
for (let i = 0; i < arrays.length; ++i) {
const array = arrays[i];
// ...
}
// Same as
for (const array of arrays) {
// ...
}

Flatten array with stack

I'm trying to flatten an array iteratively with a stack ( or rather in this example splicing in the result array rather than adding / removing from a stack ) up to a specific depth ( Not for any practical reason other than to learn ).
So far I have
function flatten(arr, depth = 1) {
let flat = [];
let i = 0;
let d = depth;
for (let elem of arr) {
flat.push(elem);
d = depth;
while(i < flat.length) {
if (Array.isArray(flat[i]) && d !== 0) {
flat.splice(i, 1, ...flat[i]);
d--;
} else {
i++;
}
}
}
return flat;
}
but it falls over if I have a level such as [[3], 4, [5, [6]] as once it goes into the depth of '3' it doesn't back out.
Is there a simple modification I can make?
The original array I was using is flatten([[1],2,[[3], 'ddd', [4,[5]]]], 2) , where I would expect the result to be [1,2,3,'ddd',4, [5]]
I also have the same issue if I implement it normally using a stack.
I think with concat and reduce you can get it done in a quite clean way:
const flatten = (array, depth) => {
if(!depth) return array;
while(depth > 0) {
array = array.reduce((pre, curr) => pre.concat(curr), []);
depth--;
}
return array;
}
const flat = flatten([1, 2, ['a', 3], [4, [6, 4]], 3, [1, 3], [[['a', 2], 2, 'b'], 'c', 3], 4], 2);
console.log(flat)
Basically what it does is iterate over the array depth times.
Each pass it will reduce the array to another array which is the concat of each element in the previous array. The gist is that concat has two different possible invocations:
when invoked with a non-array parameter it simply adds the element to the original array
when invoked with an array parameter it adds each element of the given array to the original one
So basically the core of "flattening" is already done by concat itself, you just need to keep doing that depth times.

Copy all elements of 1 array into a second array at a specific index in order

Im trying to copy elements of arr1 into arr2 at index n. The elements must be copied in the exact order they're in. I can get the code to work when I loop through the arrow backwards but I cant pass the tests because its not in order.
function frankenSplice(arr1, arr2, n) {
let newArr = arr2.splice(" ");
for(let i = 0; i < arr1.length;i++) {
newArr.splice(n,0,arr1[i]);
}
return console.log(newArr);
}
An example of how this should be called is frankenSplice([1, 2, 3], [4, 5, 6], 1);
Expected output is [4, 1, 2, 3, 5]
I keep getting [ 4, 1, 2, 3, 5, 6 ]
The reason your output is coming backwards is because you keep inserting at the same position n. This pushes the previous element after it, so they end up in reverse order. If you increment n each time through the loop, you'll insert them in order.
But there's no need for a loop, use spread syntax to use all of arr1 as the arguments in a single call to splice().
function frankenSplice(arr1, arr2, n) {
let newArr = [...arr2]; // make copy of arr2
newArr.splice(n, 0, ...arr1); // splice arr1 into the copy
return newArr;
}
console.log(frankenSplice([1, 2, 3], [4, 5, 6], 1));
I don't understand why you don't expect 6 in the output.
Not sure why the 6 is getting deleted but maybe something like this:
function frankenSplice(arr1, arr2, n) {
let newArr = arr2
newArr.splice(n+1)
for(let i = arr1.length - 1; i >= 0; i--) {
newArr.splice(n,0,arr1[i]);
}
return console.log(newArr);
}
frankenSplice([1, 2, 3], [4, 5, 6], 1);

How to get distinct values from an array of arrays in JavaScript using the filter() method? [duplicate]

This question already has answers here:
How to remove duplicates from a two-dimensional array? [closed]
(3 answers)
Closed 3 years ago.
I have an array like this:
let x = [[1, 2], [3, 4], [1, 2], [2, 1]];
What should I do to retrieve an array without the duplicates?
[[1, 2], [3, 4], [2, 1]];
I would like to use the filter method. I tried this but it doesn't work:
x.filter((value,index,self) => (self.indexOf(value) === index))
EDIT: as I specified to use the filter method, I don't think this question is a duplicate. Also, I got several interesting answers.
Try converting the inner arrays to a string, then filter the dupes and parse the string again.
let x = [[1, 2], [3, 4], [1, 2]];
var unique = x.map(ar=>JSON.stringify(ar))
.filter((itm, idx, arr) => arr.indexOf(itm) === idx)
.map(str=>JSON.parse(str));
console.log(unique);
Filter just causes things to get into O(n^2).
The currently accepted answer uses .filter((itm, idx, arr) => arr.indexOf(itm) === idx) which will cause the array to be iterated each time during each iteration... n^2.
Why even go there? Not only that, you need to parse in the end. It is a lot of excess.
There is no real good way to use filter without hitting O(n^2) here, so if performance is the goal is should probably be avoided.
Instead, just use reduce. It is very straightforward and fast easily accomplishing O(n).
"Bin reduce the set to unique values."
let x = [[1, 2], [3, 4], [1, 2], [2, 1]];
let y = Object.values(x.reduce((p,c) => (p[JSON.stringify(c)] = c,p),{}));
console.log(y);
In case it isn't as clear, here is a more readable version of the bin reduction.
// Sample Data
let dataset = [[1, 2], [3, 4], [1, 2], [2, 1]];
// Create a set of bins by iterating the dataset, which
// is an array of arrays, and structure the bins as
// key: stringified version of the array
// value: actual array
let bins = {};
// Iteration
for(let index = 0; index < dataset.length; index++){
// The current array, from the array of arrays
let currentArray = dataset[index];
// The JSON stringified version of the current array
let stringified = JSON.stringify(currentArray);
// Use the stringified version of the array as the key in the bin,
// and set that key's value as the current array
bins[stringified] = currentArray;
}
// Since the bin keys will be unique, so will their associated values.
// Discard the stringified keys, and only take the set of arrays to
// get the resulting unique set.
let results = Object.values(bins);
console.log(results);
If you were to have to go the route of filter, then n^2 must be used. You can iterate each item looking for existence using every.
"Keep every element which does not have a previous duplicate."
let x = [
[1, 2],
[3, 4],
[1, 2],
[2, 1]
];
let y = x.filter((lx, li) =>
x.every((rx, ri) =>
rx == lx ||
(JSON.stringify(lx) != JSON.stringify(rx) || li < ri))
);
console.log(y);
Okay, the string hash idea is brilliant. Props to I wrestled a bear once. I think the code itself could be a bit better though, so here's how I tend to do this type of thing:
let x = [[1, 2], [3, 4], [1, 2]];
const map = new Map();
x.forEach((item) => map.set(item.join(), item));
console.log(Array.from(map.values()));
And if you want an ugly one liner:
let x = [[1, 2], [3, 4], [1, 2]];
const noRepeats = Array.from((new Map(x.map((item) => [item.join(), item]))).values());
console.log(noRepeats);
This is a solution with time complexity of O(n) where n is the number of elements in your array.
Using the filter method as the OP wants it:
const x = [[1, 2], [3, 4], [1, 2], [2, 1]];
const s = new Set();
const res = x.filter(el => {
if(!s.has(el.join(""))) {
s.add(el.join(""));
return true;
}
return false
})
console.log(res)
My personal preference here is to use ForEach as it looks more readable.
const x = [[1, 2], [3, 4], [1, 2], [2, 1]];
const s = new Set();
const res = [];
x.forEach(el => {
if(!s.has(el.join(""))) {
s.add(el.join(""));
res.push(el)
}
})
console.log(res);
We are using a Set and a simple combination of the elements of the array to make sure they are unique. Otherwise this would become O(n^2).
The equivalent to
x.filter((value,index,self) => (self.indexOf(value) === index))
would be
x.filter((v,i,self) => {
for1:
for (let j = 0; j < self.length; j++) {
if (i == j) {
return true;
}
if (self[j].length != v.length) {
continue;
}
for (let k = 0; k < v.length; k++) {
if (self[j][k] != v[k]) {
continue for1;
}
}
return false;
}
return true;
})
Unlike some of the other answers, this does not require a conversion to string and can thus work with more complex values.
Use === instead of == if you want.
The time complexity is not great, of course.
indexOf does not work on identical instances of arrays/objects type elements within an array, as such arrays just hold references.
In filter function instance you get via parameter v (in below code) is not the same instance as stored in array, making indexOf unable to return the index of it.
In below code, by converting objects to strings we can use indexOf to find duplicates.
let x = [[1, 2], [3, 4], [1, 2], [2, 1]];
console.log(x.
map(function(v){
return JSON.stringify(v)
})
.filter(function(v, i, o) {
return o.length == i ? true : o.slice(i + 1).indexOf(v) == -1;
})
.map(function(v) {
return JSON.parse(v)
})
);

Suming a nested array's indices together with JavaScript map

Working to get a better grasp of nested arrays. I have an array with two arrays nested inside as the indices. I am trying to figure out how to add these. I understand how you would add them if they were separate arrays, but I am wondering how/if possible you can map through a nested array to add the indices.
The current array that I am looking at is
strArr= [[5, 2, 3], [2, 2, 3] ];
If this was two separate arrays I could simply run a map with index as a second parameter such as ...
var arr = [1,2,3,4];
var arr2 = [1,1,1,2,9];
arr.map((a, i) => a + arr2[i]);
However, I am not able to achieve this with the index
strArr.map((a,idx) => a[0][idx] + a[1][idx]) // [Nan, Nan]
The best I can do to get any addition is
return strArr.map(a => a[0] + a[1]) // [7,4]
However, I am not sure why I am only getting [7,4]
You iterate over the outer array, which has a length of 2. Therefore you get a result of two elements.
You could use Array#reduce for the outer array and Array#forEach for the inner arrays and sum the values at a given index.
var strArr= [[5, 2, 3], [2, 2, 3] ];
console.log(strArr.reduce((r, a) => (a.forEach((b, i) => r[i] = (r[i] || 0) + b), r), []));
You can use the .reduce function to sum your indices
var arr = [1, 2, 3, 4];
var arr2 = [1, 1, 1, 2];
var result = arr
.map((item, i) => i + arr2[i])
.reduce((memo, value) => memo + value);
console.debug("res: ", result);
// res: 11
However you will have to handle cases when the array lengths are not equal to eachover or not numerical values
Map each element in the parent array (sub-arrays)
For each sub-array reduce and sum
strArr= [[5, 2, 3], [2, 2, 3] ];
var result = strArr.map((arr)=>arr.reduce((a,b)=>a+b));
console.log(result);
Why not just target/split the original array?
"If this was two separate arrays I could simply run a map with index as a second parameter"
Then make it two separate arrays...
strArr= [[5, 2, 3], [2, 2, 3] ]; /* becomes strArr[0] & strArr[1] */
console.log(strArr[0].map((a, i) => a + strArr[1][i]));

Categories

Resources