Flatten array to 2D - javascript

I know there are a couple of answers on the matter, but as I was trying to make my own, I got very lost on how recursive functions work, here's what I tried :
function flatcoords(arr) {
for (i = 0; i < arr.length; i++) {
const value = arr[i];
if (value.isArray) {
return flatcoords(value);
} else {
return arr;
}
}
}
const arr_test = [
[
[1, 2],
[1, 2]
],
[1, 2],
[1, 2],
[
[
[1, 2],
[1, 2]
]
]
];
//wanted_result = [ [1,2],[1,2],[1,2],[1,2],[1,2],[1,2] ]
console.log(flatcoords(arr_test));
I want the result to be a 2D array, what am I missing in my logic?

First of all, you need to declare all variables before use. Then you could use Array.isArray for a check if an item is an array.
In this case, I suggest to check the first item of the checked element if it is an array as well, in this case flat the array.
As result, an array comes in handy, because not only one element should be returned, but all. And while arrays have more item, you need to append all items to the existing result array.
function flatcoords(array) {
var result = [],
value,
i;
for (i = 0; i < array.length; i++) {
value = array[i];
if (Array.isArray(value) && Array.isArray(value[0])) {
result = result.concat(flatcoords(value));
} else {
result.push(value);
}
}
return result;
}
const arr_test = [[[1, 2], [1, 2]], [1, 2], [1, 2], [[[1, 2], [1, 2]]]];
console.log(flatcoords(arr_test));
.as-console-wrapper { max-height: 100% !important; top: 0; }

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) {
// ...
}

When flattening an Array, when is arr[ i ] being added to the new array (flat)? Is it by flat.push() or by flat.concat()?

I understand the concept, but I don't understand how it is happening.
Once I get to this line:
flat = flat.concat(flatten(arr[i]))
I do not understand how that specific sub array that is about to be looped through makes it to flat = [].
I understand once the function is called again, that the sub array is basically being worked on as if it were not a sub array, and starts going though the loop.
The arr[i] makes it down to flat.push(arr[i]);
Is it at that point that the arr[i] items are being pushed into flat = [], or are they being pushed/directed here flat.concat(flatten(arr[i])) to be resolved and concat'ed when the sub array has finished going through the loop?
What confuses me is that if they are just pushed straight into flat = [] using flat.push(arr[i]), what is there to .concat?
There would be no info/data there to be able to concat on this line flat.concat(flatten(arr[i]))
What am I missing?
const arr = [[1, 2, 3], [4, [5, 6]], [[7], [8, [9]]], 10]
function flatten(arr) {
let flat = [];
for (let i = 0; i < arr.length; i++) {
if (Array.isArray(arr[i])) {
flat = flat.concat(flatten(arr[i]));
} else {
flat.push(arr[i]);
}
}
return flat;
}
It's a pretty straightforward recursive function
it takes an array or an element and return a new array with the concat of the flatten array or the element
it does the same of Arr.flat(Infinity)
you can check with the code above
const arr = [[1, 2, 3], [4, [5, 6]], [[7], [8, [9]]], 10]
function flatten(arr) {
let flat = [] ;
for (let i = 0; i < arr.length; i++) {
if (Array.isArray(arr[i])) {
flat = flat.concat(flatten(arr[i]));
} else {
flat.push(arr[i]);
}
}
return flat ;
}
console.log(flatten(arr))
console.log(arr.flat(Infinity))

How to only allow an array to be pushed into an array once?

I have an array that has other arrays in it which have been pushed in. For an example:
const Arrays = [ [1,2,3], [4,5,6], [7,8,9], [2,1,3] ];
let myArr = [];
Arrays.map(arr => {
if(myArr.indexOf(arr)){
return
}
myArr.push(arr)
})
const myArr = [ [1,2,3], [4,5,6], [7,8,9], [2,1,3] ];
In this array you can see that there are two arrays with the same set of numbers 1, 2 and 3. I want to somehow set a condition saying:
If this array already exist then do not add this array in any order again to prevent this from happening. So that when it comes in the loop that this set of numbers comes up again it will just skip over it.
You can use some() and every() methods to check if same array already exists before push().
const myArr = [ [1,2,3], [4,5,6], [7,8,9] ];
let input = [2,1,3]
function check(oldArr, newArr) {
return oldArr.some(a => {
return a.length == newArr.length &&
a.every(e => newArr.includes(e))
})
}
if(!check(myArr, input)) myArr.push(input)
console.log(myArr)
You can make temp array with sorted element with joined and check by indexOf
const myArr = [ [1,2,3], [4,5,6], [7,8,9], [2,1,3],[6,5,4] ];
var newArr = [];
var temp = [];
for(let i in myArr){
let t = myArr[i].sort().join(",");
if(temp.indexOf(t) == -1){
temp.push(t);
newArr.push(myArr[i]);
}
}
console.log(newArr);
The accepted answer does not respect the special case where only two values are in the array and the array has to check against two values in a different count like
[1, 1, 2]
and
[1, 2, 2]
which are different arrays.
For a working solution, I suggest to use a Map and count the occurences of same values of the first array and subtract the count of the values for the other arrray.
As result return the check if all elements of the Map are zero.
function compare(a, b) {
var map = new Map;
a.forEach(v => map.set(v, (map.get(v) || 0) + 1));
b.forEach(v => map.set(v, (map.get(v) || 0) - 1));
return [...map.values()].every(v => !v);
}
var array = [[1, 2, 3], [4, 5, 6], [7, 8, 9], [2, 1, 3], [1, 1, 2], [1, 2, 2], [2, 1, 1], [2, 1, 2]],
unique = array.reduce((r, a) => (r.some(b => compare(a, b)) || r.push(a), r), []);
console.log(unique);
.as-console-wrapper { max-height: 100% !important; top: 0; }
One way is to sort them numerically with .sort() then compare them.

Sorting arrays (2D array) Need assistance

Basiclly what i am trying to do is for sort the values of a array input like uniteUnique([1, 3, 2], [5, 2, 1, 4], [2, 1]) into a sorted 2D array like [[1, 1, 1], [2, 2, 2], [4], [3], [5]]. The arrays don't have to be in number/ value order. The code below is what i tried:
function uniteUnique(arr) {
let times = 0;
var unique = [[]];
for (var i = 0; i < Array.prototype.slice.call(arguments).length; i++) {
times = 0;
for (var j = 0; j < arguments[i].length; j++) {
for (var h = 0; h < unique.length; h++) {
var pushArray = []
if(unique[h][0] === arguments[i][j]) {
unique[h].push(arguments[i][j])
arguments[i].splice(j)
}
else {
unique.push([arguments[i][j]])
}
}
}
}
return unique
}
uniteUnique([1, 3, 2], [5, 2, 1, 4], [2, 1]);
--> https://repl.it/#John_Nicole/unique
I only have one parameter for all of the input arrays
I have a uniquearray with 1 blank array. Its a 2D array.
Then i go through, for instance, the values of [1, 3, 2] and check if any arrays in uniquehave there first value as my number (arguments[i][j]).
If true, i push the number arguments[i][j], then remove the number in the original array with splice() .
If false, i push new array into unique with this unrecognized value.
Quick Overview of the variables
h: This is the array in unique that i am going to compare with. It could be [2, 2, 2].
i: This is the array from the input. For instance, [1, 3, 2].
j: This the number itself, its partner with i. For example, arguments[i][j] = [2, 1] --> 2
arguments grabs all, in this case, the 3 input arrays.
Times: This just means 0. No use what so ever.
The input could include 2D arrays in themselves like [1, 3, 2], [1, [5]], [2, [4]]
This is a part of a freeCodeCamp challenge --> https://www.freecodecamp.org/challenges/sorted-union
My question is that, why is my output:
[ [],
[ 1, 1 ],
[ 5, 5 ],
[ 5 ],
[ undefined, undefined ],
[ 2, 2 ],
[ 2 ],
[ 2 ],
[ 2 ],
[ 2 ],
[ undefined, undefined ],
[ undefined, undefined ],
[ undefined, undefined ],
[ undefined, undefined ] ]
?
when the wanted output is: Wanted output was [1, 1, 1], [2, 2, 2], [4], [3], [5]
Am i doing something wrong?
I'm getting multiple 2's arrays for example ([[ 2 ],[ 2 ],[ 2 ],[ 2 ]]) even though i'm suppose to be putting all 2 into one array?
You could use a hash table and check if the hash key exists and if not, then take an empty array as value for the hash and push it to the result set.
The hash table is an object (here without prototypes) which takes the values as keys and an array as value. A the the end the hash table keeps all values in arrays, which are inserted in the result set if a new value is found.
{
1: [1, 1, 1],
2: [2, 2, 2],
3: [3],
4: [4],
5: [5]
}
function uniteUnique() {
var result = [],
hash = Object.create(null);
Array.prototype.forEach.call(arguments, function (a) {
a.forEach(function (b) {
if (!(b in hash)) {
hash[b] = [];
result.push(hash[b]);
}
hash[b].push(b);
});
});
return result;
}
console.log(uniteUnique([1, 3, 2], [5, 2, 1, 4], [2, 1]));
.as-console-wrapper { max-height: 100% !important; top: 0; }
Some annotation to your code (not used variables are deleted):
function uniteUnique() {
var unique = [], // move all declarations to top
i, j, h,
pushed;
// array like objects have a length property
for (i = 0; i < arguments.length; i++) {
for (j = 0; j < arguments[i].length; j++) {
pushed = false;
for (h = 0; h < unique.length; h++) {
if (unique[h][0] === arguments[i][j]) {
// take the element, do not use splice, because with splicing
// the array becomes shorter and the index is updated in the
// next loop and is pointing to the element with the wrong index,
// because you get the element after next
// it is better not to mutate a variable, if it works without
// in this case, you iterate and visit each element only once
unique[h].push(arguments[i][j]);
pushed = true; // a value is found
break; // exit this loop
} // prevent more looping
}
if (!pushed) { // if not found
unique.push([arguments[i][j]]); // push the value
}
}
}
return unique;
}
console.log(uniteUnique([1, 3, 2], [5, 2, 1, 4], [2, 1]));
.as-console-wrapper { max-height: 100% !important; top: 0; }

Remove duplicates from a multidimensional array

I have found questions that kind of touch on the issue I'm having, but I haven't found a solution that works for me yet. I have this array: [[1, red], [2, green], [3, red], [3, blue], [5, green]] and I need it to return [[1, red], [2, green], [3, blue]. What I need the code to do is go through the array and find ONLY colors that match, not numbers, and get rid of that entire index.
I have tried something like this
var uniqueArray = colors.filter(function(item, pos) {
return colors.indexOf(item) == pos;
});
I'm thinking that this code is searching for a complete match, and I only require a partial match. So basically, how would I modify .filter() to get rid of partial duplicates (only matching the colors)?
Please let me know if I need to provide any more information.
// Parameter marr: multidimensional array
function removeSameColors(marr){
var carr = [];
var rarr = [];
var j = -1;
for(var i = 0, l = marr.length; i < l; i++){
if(carr[marr[i][1]] !== true){
carr[marr[i][1]] = true;
rarr[++j] = marr[i];
}
}
return rarr;
}
That should solve your problem with very low execution time.
You could use a hash table with the color and use Array#filter for the wanted items.
var data = [[1, 'red'], [2, 'green'], [3, 'red'], [3, 'blue'], [5, 'green']],
result = data.filter(function (a) {
if (!this[a[1]]) {
return this[a[1]] = true;
}
}, Object.create(null));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
I would use a for loop to populate a new, unique array:
var old_array = [[1, red], [2, green], [3, red], [3, blue], [5, green]],
old_count = old_array.length,
unique_array = [], check_array = [], i = 0;
for(; i < old_count; i++) {
if (check_array.indexOf(old_array[i][1]) === -1) {
check_array.push(old_array[i][1]);// this array is filled with new colors, and used for checking
unique_array.push(old_array[i]);
}
}
I would just keep track of the unique colors in an object passed into the filter, because as a hash it's guaranteed to be unique. If the object doesn't have a property by that color name, it returns it from the filter. Otherwise if it does it ignores the item.
var colors = [[1, "red"], [2, "green"], [3, "red"], [3, "blue"], [5, "green"]];
var uniqueArray = colors.filter(function(item, pos) {
if (!this.hasOwnProperty(item[1])) {
return this[item[1]] = true;
}
return false;
}, {});
This gives you uniqueArray = [[1,"red"],[2,"green"],[3,"blue"]], as expected.

Categories

Resources