This question already has answers here:
From array, generate all distinct, non-empty subarrays, with preserved order
(3 answers)
Closed 1 year ago.
Assume I have this array:
const input = [“a”, “b”, “c”, “d”];
I want to create this output:
[
[“a”],
[“a”, “b”],
[“a”, “c”],
[“a”, “d”],
[“a”, “b”, “c”],
[“a”, “b”, “d”],
[“a”, “c”, “d”],
[“a”, “b”, “c”, “d”],
[“b”],
[“b”, “c”],
[“b”, “d”],
[“b”, “c”, “d”],
[“c”],
[“c”, “d”],
[“d”]
]
I don’t care about the order, or the length of the combos, just need all the various ways to uniquely combine the items in the array.
What is the best way to do this in JavaScript? I suspect there’s a beautiful recursive solution, but iterative would work too.
Also, what is the correct technical term for this?
The correct technical term is power set. Here is the canonical recursive form:
const f = (A, i=0) => i == A.length ? [[]] : f(A, i+1).flatMap(x => [x, [A[i]].concat(x)]);
console.log(JSON.stringify(f(['a', 'b', 'c', 'd'])));
Recursive solution probably is the easiest to understand, although I guess there are other more efficient (in terms of computation complexity) solutions too.
First of all, the actual elements in the array do not matter, so that we can just use their indexes (0...n-1) to do the computation, later we convert those indexes into actual elements by actualArray = indexArray.map(i => inputArray[i]). In the discussion below, we assume indexes are stored in the output/result.
Then, since the order in the combination does not matter, and since everything (the index) within the combination must be unique, we can just make sure the indexes in the combinations are always in increasing order.
So, we can start with the combinations (array of arrays) that contain only 1 elements. Without any computation we all know those are:
[[0], [1], [2], [3], ... [n-1]].
We can write code to generate them and use them as the seeds.
Then, for finding out all combinations (array of arrays) containing m+1 elements based on already known combinations (array of arrays) containing m elements, we can do this:
Iterate through all combinations having m elements (array of arrays)
For each combination (array of length m),
Iterate through the range between (inclusive at both ends) combination[combination.length -1] and n - 1 if the range is valid for finding the next index
Make a copy of the original array and append the new index to it. Such as newCombination = [...combination, newIndex]. This is a new combination containing m+1 elements.
Add the newly found combination to the result.
Now we have found all combinations having m+1 elements. They can be used to find all combinations having m+2 elements.
We can do this until we find the last combination, which contains n elements.
To facilitate the algorithm described above, the internal data structure could be:
[
[[0], [1], [2], ..., [n-1]], // indexes of combinations with 1 elements
[[0, 1], [0, 2], ..., [n-2, n-1]], // indexes of combinations with 2 elements
...
[[0, 2, ... n-1]], // indexes of combinations with n elements
]
To verify the correctness, we just need to check these:
Total number of combinations is the same as what we can calculate by math
Within each of the combinations, elements are always in increasing order
There's no duplicated combinations (adding combination.join(',') to a Set could be handy)
BTW, this is just my thoughts, I have not verified it with any real code or data :-)
Related
I want to get a range from my sheet. As recommended in Best practices, I am trying to get a array and manipulate it, but I'm confused:
const ss = Spreadsheet.getActive(),
sh = ss.getSheetByName("Sheet1"),
rg = sh.getRange("A1:C1"),//has 1,2,3
values = rg.getValues();
console.log(values);
The logs show
[[1,2,3]]
As you can see I got all three elements. But, when I log the length of the array(array.length), it is just 1(instead of 3). When I test existence of a element using .indexOf or .includes, It says -1 or false.
const values = /*same as logged above*/[[1,2,3]];
console.log(values.indexOf(2));//got -1 expected 1
console.log(values.includes(1));//got false expected true
Why?
I have the same issue with setValues().
rg.setValues([1,2,3]);//throws error
The error is
"The parameters (number[]) don't match the method signature for SpreadsheetApp.Range.setValues."
My specific Question is: What exactly does getValues() return? Is it a special kind of array?
Documentation excerpts:
From The official documentation, getValues() returns
a two-dimensional array of values,
It ALWAYS returns a two dimensional array of values.
One dimensional array is
[1,2,3]
Two dimensional array is
[[1,2,3]]
//or
[[1], [2], [3]]
There is/are array(s) inside a array.
indexed by row, then by column.
It is indexed by row first: i.e., The outer array has rows as inner array. Then each inner array has column elements. Consider the following simple spreadsheet:
A
B
C
1>
1
2
3
2>
2
3
4
3>
3
4
5
A1:A3 contains 3 rows and each row contains 1 column element. This is represented as [[1],[2],[3]]. Similarly, The following ranges represent the following arrays. Try to guess the array structure based on the A1 notation:
A1Notation
Number of Rows
Number of columns
Array Structure
array.length
array[0].length
A1:A3
3
1
[[1],[2],[3]]
3
1
A1:C1
1
3
[[1,2,3]]
1
3
A1:B2
2
2
[[1,2],[2,3]]
2
2
B1:C3
3
2
[[2,3],[3,4],[4,5]]
3
2
A2:C3
2
3
[[2,3,4],[3,4,5]]
2
3
Note how the two dimension provides direction.
See live visualization below:
/*<ignore>*/console.config({maximize:true,timeStamps:false,autoScroll:false});/*</ignore>*/
const test = {
'A1:A3': [[1], [2], [3]],
'A1:C1': [[1, 2, 3]],
'A1:B2': [
[1, 2],
[2, 3],
],
'B1:C3': [
[2, 3],
[3, 4],
[4, 5],
],
'A2:C3': [
[2, 3, 4],
[3, 4, 5],
],
};
Object.entries(test).forEach(([key, value]) => {
console.log(`The range is ${key}`);
console.table(value);
console.info(`The above table's JavaScript array notation is ${JSON.stringify(value)}`)
console.log(`=================================`);
});
<!-- https://meta.stackoverflow.com/a/375985/ --> <script src="https://gh-canon.github.io/stack-snippet-console/console.min.js"></script>
The values may be of type Number, Boolean, Date, or String, depending on the value of the cell.
In the above example, We have Spreadsheet Number type elements converted to JavaScript number type. You can check spreadsheet type using =TYPE(). Corresponding JavaScript type reference is here
Empty cells are represented by an empty string in the array.
Check using
console.log(values[0][0]==="")//logs true if A1 is empty
Remember that while a range index starts at 1, 1, the JavaScript array is indexed from [0][0].
Given the two dimensional array structure, to access a value, two indexes of format array[row][column] is needed. In the above table, if A2:C3 is retrieved, To access C3, Use values[1][2]. [1] is second row in range A2:C3. Note that the range itself starts on second row. So, second row in the given range is row3 [2]is third column C.
Notes:
Warning:
Retrieved values from a range is always two dimensional regardless of the range height or width(even if it is just 1). getRange("A1").getValues() will represent [[1]]
setValues() will accept the same array structure corresponding to the range to set. If a 1D array is attempted, the error
The parameters (number[]/string[]) don't match the method signature for SpreadsheetApp.Range.setValues.
is thrown.
If the array does NOT exactly correspond to the range being set,i.e.,if each of the the inner array's length does not correspond to the number of columns in the range or the outer array's length does not correspond to the number of rows in the range being set, The error similar to the following is thrown:
The number of columns in the data does not match the number of columns in the range. The data has 5 but the range has 6.
Related answers to the above error:
https://stackoverflow.com/a/63770270
Related Search
indexOf/includes uses strict type checking. They won't work when you compare primitives against array objects. You can use Array.flat to flatten the 2D array to a 1D one. Alternatively, Use a plain old for-loop to check something.
const values = [[1,2,3]].flat();//flattened
console.log(values.indexOf(2));//expected 1
console.log(values.includes(1));//expected true
References:
Basic reading
MDN Arrays guide
I wanted to use a set in javascript and I found in docs we have Set in js.
But when I tried to use it, it doesn't returns the elements in sorted order.
let mySet1 = new Set()
mySet1.add(1) // Set [ 1 ]
mySet1.add(5) // Set [ 1, 5 ]
mySet1.add(5) // Set [ 1, 5 ]
mySet1.add(15)
mySet1.add(150)
mySet1.add(23)
mySet1.add(45)
console.log(mySet1)
Set(6) {1, 5, 15, 150, 23, …}
[[Entries]]
0: 1
1: 5
2: 15
3: 150
4: 23
5: 45
size: (...)
Isn't the set implemented using a Binary Search Tree like structure in javascript as in other languages like C++, Java
What will be the time complexity for insertion, deletion here.
Or are they HashSet??
Isn't the set implemented using a Binary Search Tree like structure in javascript as in other languages like C++, Java
No, because that wouldn't make any sense - sets very often have non-numeric values in them, and may even have non-serializable values like functions and class instances. Binary search only works for numbers (or for values that can be serialized and converted into numeric values one-to-one, like strings)
But when I tried to use it, it doesn't returns the elements in sorted order.
The order in which values are iterated over is their insertion order, not their sorted order.
What will be the time complexity for insertion, deletion here.
See here. It's required to be at least sublinear. Past that is up to each individual implementation.
I need some help finding symmetric difference of a multi dimensional array, and a simple array. The first value in each inner array of the multidimensional array cells is the index that compares to the simple array.
So
array1 = [1,4,6,7]
array2 = [[1,"more",12],[8,"some",12]]
the result should be something like:
compare(array1, array2) = //[4,6,7] // there are three differences when compared this way
compare(array2, array1) = //[8,"some",12] // there is only one difference when compared this way
I need to return an array that has both difference of array1 from array2 AND difference from array2 from array1 in the same format as the lead array.
Ideally these are not overwriting the existing arrays but creates a new with the output results. There won't be other array formats besides these two array formats.
Each compare can use a different function if it helps. You don't have to use the same function for both, but if you can, great.
I tried a few permutations of loop comparisons
Also solutions found here
How to get the difference between two arrays of objects in JavaScript
And of the simple array methods here
How to get the difference between two arrays in JavaScript?
But I just am not being successful. Can someone give me a hand, and also explain their solution? Any modern tools are fine as long as its broadly cross browser compatible. All my other code sticks to ES6, so that would be ideal. If whipping out a one liner solution please explain what is going on so I can learn.
Thanks!
Update # Dave, this made sense to me, but after it failed I started trying different filter methods and other techniques in the posts above, without much success.
let newNurkles = new Array();
for(var i = 0; i < nurkles.length; i++){
if(this.activeNurkles.includes(nurkles[i])){
} else {
newNurkles.push(nurkles[i]);// if not then push to array
}
}
console.warn("Nurkles to Add" + newNurkles);
This shows how to perform a disjunctive union on two arrays, one being single dimensional while the other is a multidimensional array.
The symmetry is determined by each element of the single with the first element of each sub-array in the multi. The multi will only be one level deep.
Uses: Array.prototype.map(), Array.prototype.filter()
Steps:
Map over the first input array
For each element, filter the second input to exclude those found in first input
Limit results to only the first array returned
Notes:
o is the iteration of array1
t is iteration of array2
t[0] represents the match key
t[idx] represents the current value of the sub-array being iterated
Results from array2 will produce a multidimensional array
const array1 = [1, 4, 6, 7];
const array2 = [[1, "more", 12],[8, "some", 12], [7, 3, 9], [2, 7, 5, 4], [4, 3]];
const oneToTwo = array2.map((t, idx) => array1.filter(o => t[idx] !== o))[0]
const twoToOne = array1.map(o => array2.filter(t => o !== t[0]))[0]
console.log(oneToTwo);
console.log(twoToOne)
I've read this [SO post][1], it has helped, but it looks like it has played around with the data...
I have read in two CSV files that look like this:
word, frequency
random, 462546
stupid, 34652
dumb, 4346
I've merged them, which works. I've sorted them, however, it half works. The sorting function sorts the two array of objects as if they were separate. What I mean by this, is that my two arrays of objects merge together, but it has merged them one after another. Then sorted one array of objects, then sorted the other, without sorting them as one whole array, it's sorting them as two arrays.
A link to my CSV files is here enter link description here
d3.csv("data/ArsenalDictionary.csv", function(error1, Arsenal) {
d3.csv("data/ChelseaDictionary.csv", function(error2, Chelsea) {
var selected = d3.merge([Arsenal, Chelsea]);
selected.sort(function(a, b){ return d3.descending(a[2], b[2]); })
console.log(selected);
});
});
Your array selected isn't getting sorted because you are attempting to sort the objects by a non-existent property.
The elements of your array are objects with two properties, "words" and " frequency" (note the leading space in the latter). You are attempting to sort them by a property named 2, which they don't have.
You would have better luck sorting them by the frequency property:
selected.sort(function(a, b){ return d3.descending(a[" frequency"], b[" frequency"]); });
Note however that this doesn't entirely do what you expect: the frequencies end up in the order 94, 9, 9, 9, ..., 8, 8, 8, ..., etc. This is because they have been sorted as strings, not as numbers.
To deal with this either convert the values to numbers while sorting (note the extra + signs):
selected.sort(function(a, b){ return d3.descending(+a[" frequency"], +b[" frequency"]); });
Alternatively, you can convert the frequencies to numbers as part of reading in the files:
function mapRow(row) {
return { "words": row["words"], " frequency": +row[" frequency"] };
}
d3.csv("ArsenalDictionary.csv", mapRow, function(error1, Arsenal) {
d3.csv("ChelseaDictionary.csv", mapRow, function(error2, Chelsea) {
// ...
The former is more convenient but the latter may come in more useful if you want to do other things with the numbers, such as add up two counts if both files use the same word. (world appears in both files).
I'm reading JavaScript:The Good Parts by Crockford and I'm having trouble understanding the reimplementation of the unshift method that he does in his book.Here is the the code:
Array.method('unshift', function ( ) {
this.splice.apply(this,
[0, 0].concat(Array.prototype.slice.apply(arguments)));
return this.length;
});
It would be useful if someone could go through what is happening step by step. One of the things that I don't understand is why he concatenates [0 , 0] to the result of the Array.prototype.slice.
why he concatenates [0 , 0] to the result of the Array.prototype.slice
The first two arguments of splice (which is where the resulting array is applied to) are:
index which is 0 because you are adding to the front of the array
howMany (to remove) which is 0 because you are just adding new elements
The remaining arguments are the values to be added to the front of the array, which are taken from Array.prototype.slice.apply(arguments) (to convert the data into the arguments object into data in an array).