JS array concatenation for results of recursive flattening - javascript

Good day!
Task would be to get a flat version of an array, that may include some amount of nested arrays as well as other elements. For input [1, [2], [3, [[4]]]] output [1, 2, 3, 4] expected.
FreeCodeCamp spoiler alert.
Naturally, recursive solution comes to mind, e.g.:
function steamrollArray(arr) {
var result = [];
for(var i = 0; i < arr.length; i++){
//part of interest
if (Array.isArray(arr[i])){
var nestedElements = steamrollArray(arr[i]);
for(var j = 0; j < nestedElements.length; j ++){
result.push(nestedElements[j]);
}
//</part of interest>.
} else {
console.log("pushing: " + arr[i]);
result.push(arr[i]);
}
}
return result;
}
And it does it's thing. Result of sample run would be:
pushing: 1
pushing: 2
pushing: 3
pushing: 4
[1, 2, 3, 4]
And the question is: what goes awry, when we use concat to add nestedElements (that supposedly store return result of recursive call). If we are to change first if{} block in for loop (marked as part of interest) with snippet below:
if (Array.isArray(arr[i])){
var nestedElements = steamrollArray(arr[i]);
result.concat(nestedElements);
} else {
we will observe the following result:
pushing: 1
pushing: 2
pushing: 3
pushing: 4
[1]
My understanding was to pass the result of each recursive call to the concat function, which will add the returned array to the result, but for some reason it's not the case.
Questions on this task was asked, like this one, but those concerned with the flattening algorithm part, which is not questioned here.
I still fail to see the answer what exactly causes the difference. It very well might be something I simply overlooked in a hassle or as a result of my limited experience. Sorry if that's the case.

Array#concat returns a new array with the result.
The concat() method returns a new array comprised of the array on which it is called joined with the array(s) and/or value(s) provided as arguments.
So you need to assign the result:
result = result.concat(nestedElements);
// ^^^^^^ assignment

I am puzzled by the accepted answer as it can only concat two arrays. What you want for a nested array is actually:
var flatArray = [].concat.apply([], yourNestedArray);

this is my function.
function _recursive_array_flat( ...args )
{
var _ret_array = [];
for( _arg of args )
{
if ( _arg instanceof Array )
{
if ( _arg.length > 0 ) // work with consistent elements only
_ret_array = _ret_array.concat( _recursive_array_flat( ..._arg ) );
}
else _ret_array.push( _arg );
}
return _ret_array;
}
var _ret = _recursive_array_flat( [0], 1,2,3, [ 4,5,6, [ 7,8,9 ] ] );
console.log( _ret );

Related

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);
}

Javascript find the highest value in 2d array based on array itself

I've a multidimensional array
arr = [[[1,1],[2,1],[3,1],[4,2],[5,2],[6,2],[7,3],[8,4]],
[[1,1],[2,1],[3,1],[4,3],[5,3],[6,4],[7,4],[8,5],[9,5],[10,5]]
];
and so on ... but the dimension of the arr is not fixed, is variable.
I've a variable that tell me where to point my attention
var number = 2;
So my goal is the look in any arr[i] and find the max 1st argument based on the 2nd argument, I try to explain better my self, in this particular case if number is 2 my expectation is to have from arr:
for the 1st array in arr -> 6 (because the second argument is 1,1,1,2,2,2,3 so I've to point at the last 2 and return the 1st argument)
for the 2nd array in arr -> 3 (because 2 is missing and the 1 is the last second argument)
I know is a little tricky
My first idea was to make a for loops where I delete all value over my number, then I can take the very last one, but I think I'm over-complicating all.
There is a better and fast way to achieve the same result?
J
You present lists (arrays) of pairs of numbers, where the pairs are sorted in ascending order, first by the second number, then by the first.
What you seem to ask for is: Given a number to search for among the second numbers, e.g. number = 2, find the last pair where the second number is less than or equal to this number, and return the corresponding first number in this pair.
You state that you could use for loops to solve the problem. A straightforward approach could be like the following snippet:
var arr = [[[1,1],[2,1],[3,1],[4,2],[5,2],[6,2],[7,3],[8,4]],
[[1,1],[2,1],[3,1],[4,3],[5,3],[6,4],[7,4],[8,5],[9,5],[10,5]]
];
var findNumber = 2;
var result = [];
for(var i = 0; i < arr.length; i++){
var maxIndex = -1;
for(var j = 0;
j < arr[i].length && arr[i][j][1] <= findNumber;
j++){
maxIndex = j;
}
result.push(arr[i][maxIndex][0]);
}
//gives the expected answers 6 and 3
console.log(result);
Then you ask:
There is a better and fast way to achieve the same result?
A solution involving .map and .reduce could be considered more elegant, like the following:
var arr = [[[1,1],[2,1],[3,1],[4,2],[5,2],[6,2],[7,3],[8,4]],
[[1,1],[2,1],[3,1],[4,3],[5,3],[6,4],[7,4],[8,5],[9,5],[10,5]]
];
var findNumber = 2;
var result = arr.map(function(val){
return val[val.reduce(function(acc, curr, index){
return curr[1] <= findNumber? index : acc;
}, -1)][0];
});
//gives the expected answers 6 and 3
console.log(result);
However, in terms of performance, for loops are likely to perform better (run faster) and are easy to comprehend.
In addition, you mention that
the dimension of the arr is not fixed
You would need to post some code examples on how the dimensionality of your data may vary before it would be possible to provide any answer that handles this aspect.
Update
To handle a single array of pairs, you do not need the outer loop or .map(). Putting the solution above into a reusable function:
function lookupFirstNumberFromSecond(secondNumberToFind, arr){
var j = 0, maxIndex = -1;
while(j < arr.length && arr[j][1] <= secondNumberToFind){
maxIndex = j++;
}
return arr[maxIndex][0];
}
//gives the expected answer 6
console.log(lookupFirstNumberFromSecond(
2,
[[1,1],[2,1],[3,1],[4,2],[5,2],[6,2],[7,3],[8,4]]
));
//gives the expected answer 3
console.log(lookupFirstNumberFromSecond(
2,
[[1,1],[2,1],[3,1],[4,3],[5,3],[6,4],[7,4],[8,5],[9,5],[10,5]]
));
I'm not entirely sure about what you are trying to achieve but I guess Array.reduce is a pretty elegant solution to get a single value out of an array.
e.g.
var number = 2;
[[1,4],[2,1],[3,1],[4,2],[5,2],[6,2],[7,3],[8,4]]
.reduce(function (a, b) {
return ((!a || b[0] > a[0]) && b[1] === number) ? b : a;
});
Not entirely sure what you're trying to solve either, but if you're trying to get the max value in a n dimensional array, then the most straightforward method is to solve this standardly in a recursive manner
function recurseMax(arr) {
if (Number.isInteger(arr)) {
return arr;
}
else {
answer = 0;
for (let i = 0; i < arr.length; i++) {
answer = answer > recurseMax(arr[i]) ? answer : recurseMax(arr[i]);
}
return answer;
}
}
console.log(recurseMax([1,[3, 5], [5, 6, 7, 10], [2, [3, [500]]]])); //Outputs 500
For each element, either is a number or another possible multidimensional element, so we recursively find its max. This avoids potential overhead from a reduce operation (though I'm not experienced enough to speak with confidence whether or not it is completely faster, not really sure of the optimizations V8 can do on reduce or a plain old recursion loop). Either way, the solution is fairly straightforward.
I am answering the question based on the assumption that you mean that the array can have a max dimension of n.

Finding unique values in multiple arrays

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)

forEach on a 'new Array' isn't doing what I expect

I'm just learning how to use JS higher-order functions (map, forEach, reduce, etc), and have stumbled into confusion. I'm trying to write a simple 'range' function, but can't seem to populate my output array. This is the goal:
range(1, 4) // [1, 2, 3, 4]
I'm getting this:
[undefined × 4]
Here is my code:
function range(num1, num2) {
var rangeArr = new Array((num2 + 1) - num1);
return rangeArr.map(function(e, i, arr) {return arr[i] = num1 + i});
}
What am I missing here? As far as I can tell the problem appears to have something to do with the way I'm utilizing 'new Array', but beyond that I'm lost.
Oh, and here's the part that really confuses me. This works fine:
function bleck() {
var blah = [1, 2, 3, 4];
var x = 'wtf';
return blah.map(function(e, i, arr) {return arr[i] = x})
}
["wtf", "wtf", "wtf", "wtf"]
Thanks!!
The forEach method iterates over the indices of the array. Interestingly enough, when you create a new array via new Array(n), it contains no indices at all. Instead, it just sets its .length property.
> var a = new Array(3);
> console.info(a)
[]
> console.info([undefined, undefined, undefined])
[undefined, undefined, undefined]
MDN describes forEach, and specifically states:
forEach executes the provided callback once for each element of the
array with an assigned value. It is not invoked for indexes which have
been deleted or elided.
Here's a neat technique to get an array with empty, but existing, indices.
var a = Array.apply(null, Array(3));
This works because .apply "expands" the elided elements into proper arguments, and the results ends up being something like Array(undefined, undefined, undefined).
The array is defined with 4 entires each of which is undefined.
Map will not iterate over undefined entires, it skips them.
callback is invoked only for indexes of the array which have assigned
values; it is not invoked for indexes that are undefined, those which
have been deleted or which have never been assigned values.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map
When you create a new Array(x) it is creating what is called a sparse array, which might behave a bit differently, as you can see, some browsers will say [undefined x 20,"foo", undefined x 5] if you just set one value, and I believe it doesn't iterate over those values.
The problem is that map doesn't iterate undefined entries (*).
I suggest using a for loop instead:
var rangeArr = new Array((num2 + 1) - num1);
for(var i=0; i<=num2-num1; ++i)
rangeArr[i] = num1 + i;
return rangeArr;
(*) With undefined entries I mean rangeArr.hasOwnProperty(i) === false, not to be confused with rangeArr[i] === void 0.

Convert columns to rows in javascript

I have read this Swap rows with columns (transposition) of a matrix in javascript However, it did not work for me (because I still stupi).
There are numbers of arrays each as individual colum like that:
id [1, 2, 3]
caption [one, two, three]
title [One, Two, Three]
I want to convert columns to row:
arr= [1, one, One]
...
Some code
var res = [];
for(i in this.fields) {
for(j in this.fields[i].value) {
res[j][i] = this.fields[i].value[j];
}
}
it give me "TypeError: can't convert undefined to object "
In php this method works fine, but could somebody point me how to do it in js. Thanks in advance.
UPDATE for simplication
var arr = [];
arr[0] = [];
arr[6][0] = 5;
/*
Exception: can't convert undefined to object
#Scratchpad/1:4
*/
When we scan common string we iterate with indexes like 0-0, 0-1, 0-2 until end-of-row when starts again 1-0 and so on. But here I need 0-0, 1-0, 2-0 end of col and again 1-0, 1-1, 1-1 ...
UPDATE for "this". Just add a cople of lines:
console.log(this.fields[i].value[j]);
console.log('indexes: i='+i, 'j='+j);
and as could you see there are no undefined values
4
indexes: i=0 j=0
1
indexes: i=1 j=0
1
indexes: i=2 j=0
one
indexes: i=3 j=0
One
indexes: i=4 j=0
There are a few mistakes in your source code. We don´t know how your this.fields value looks like, but for the sake of your code snippet let it look like that:
this.fields = [
{ value: [1, 2, 3] },
{ value: [4, 5, 6] },
{ value: [7, 8, 9] }
]
If your this.fields variable looks like that, you are not so far away from the solution. Your error message says TypeError: can't convert undefined to object, so I am guessing your variable does not look like described.
When we transform your data in a form that looks like my example, your are not so far away from the solution in your code snippet. The problem is res does not know, that its second dimension is supposed to consist of arrays, because you never defined that. We can fix that by adding if(i === 0) res[j] = [];.
So with the described structure and our little fix, it should work:
var res = [];
for(i in this.fields) {
for(j in this.fields[i].value) {
if(i === 0) res[j] = [];
res[j][i] = this.fields[i].value[j];
}
}
For sure one error is within the loop itself. On the first iteration res[j] exists, but inside the inner loop res[j][i] is not defined and will throw an error. You could fix it by checking if the element already exists:
var res = [];
for(i in this.fields) {
for(j in this.fields[i].value) {
if( !res[j] ) { res[j] = []; }
res[j][i] = this.fields[i].value[j];
}
}

Categories