flatten an array without using .flat(); - javascript
how can i flatten an array without using flat(). by 1 level?
so far i have this
function flatten(array) {
let flattened = [];
for (let i = 0; i < array.length; i++) {
const current = array[i];
for (let j = 0; i < current.length; j++) {
flattened.push(current[j])
}
}
return flattened
}
console.log(flatten([['foo', 'bar'], ['baz', 'qux']]));
// -> ["foo", "bar", "baz", "qux"]
flatten([[1], [2], 3, 4, [5]]);
// -> [1, 2, 3, 4, 5]
flatten([false, [true, [false]], [true]]);
// -> [false, true, [false], true]
flatten([]);
// -> []
and its crashing my memory
I hope this helps
var twoDimension = [[1], [2], 3, 4, [5]];
var plano = twoDimension.reduce((acc, el) => acc.concat(el), []);
console.log(plano);
You could use Array.reduce and the spread syntax:
function flatten(array) {
return array.reduce(
(accumulator, item) => {
// if `item` is an `array`,
// use the `spread syntax` to
// append items of the array into
// the `accumulator` array
if (Array.isArray(item)) {
return [...accumulator, ...item];
}
// otherwise, return items in the
// accumulator plus the new item
return [...accumulator, item];
}
, []); // initial value of `accumulator`
}
console.log(flatten([['foo', 'bar'], ['baz', 'qux']]));
// -> ["foo", "bar", "baz", "qux"]
console.log(flatten([[1], [2], 3, 4, [5]]));
// -> [1, 2, 3, 4, 5]
console.log(flatten([false, [true, [false]], [true]]));
// -> [false, true, [false], true]
console.log(flatten([]));
// -> []
References:
Array.reduce - MDN
Spread syntax - MDN
To flatten by a single level only, Array#concat() can be leveraged. It accepts any amount of arguments, so an array can be spread into the function call:
[].concat(...arr)
This avoids any explicit loops. JavaScript handles everything:
function flatten(arr) {
return [].concat(...arr);
}
console.log(flatten([['foo', 'bar'], ['baz', 'qux']]));
// -> ["foo", "bar", "baz", "qux"]
console.log(flatten([[1], [2], 3, 4, [5]]));
// -> [1, 2, 3, 4, 5]
console.log(flatten([false, [true, [false]], [true]]));
// -> [false, true, [false], true]
console.log(flatten([]));
// -> []
You can use the following method if your array have primitive data type and want to flat it completely:
arr.toString().split(',');
you can use the reducer of javascript as an alternative to flat().
const arr = [1, 2, [3, 4]];
arr.reduce((acc, val) => acc.concat(val), []);
// [1, 2, 3, 4]
or you can use decomposition syntax
const flattened = arr => [].concat(...arr);
For more details, go to Mozilla MDN
You have an error here:
for (let j = 0; i < current.length; j++) {
// ^ wrong variable, should be j
And you need to check if the value is not an array, then just push the current value and continue the loop.
function flatten(array) {
let flattened = [];
for (let i = 0; i < array.length; i++) {
const current = array[i];
if (!Array.isArray(current)) {
flattened.push(current);
continue;
}
for (let j = 0; j < current.length; j++) {
flattened.push(current[j])
}
}
return flattened
}
console.log(flatten([['foo', 'bar'], ['baz', 'qux']]));
// -> ["foo", "bar", "baz", "qux"]
console.log(flatten([[1], [2], 3, 4, [5]]));
// -> [1, 2, 3, 4, 5]
console.log(flatten([false, [true, [false]], [true]]));
// -> [false, true, [false], true]
console.log(flatten([]));
// -> []
You had a typo where in your innermost loop you set i to 0 instead of j. The only other thing you needed to do was check to see if each element in the outer array was scalar (not an array) and push it to the returned array if so.
function flatten(arr) {
let flat = []
for (let i=0; i < arr.length; i++) {
const cur = arr[i]
if(!Array.isArray(cur)){
flat.push(cur)
}else{
for (let j=0; j < cur.length; j++) {
flat.push(cur[j])
}
}
}
return flat
}
console.log(flatten([['foo','bar'],['baz','qux']]))
console.log(flatten([[1],[2],3,4,[5]]))
console.log(flatten([false,[true,[false]],[true]]))
console.log(flatten([]))
Well you can use spread operator with reduce.
function flatten(array) {
return array.reduce((a,v) => [...a, ...(Array.isArray(v) ? v : [v])], []);
}
console.log(flatten([['foo', 'bar'], 'baz', 'qux']))
Following could be used as a general implementation of Array.prototype.flat()
function flattenArray(arr, depth = 1) {
if (!Array.isArray(arr)) {
return [arr];
}
return depth > 0
? arr.reduce(
(acc, curr) =>
acc.concat(
Array.isArray(curr) ? flattenArray(curr, depth - 1) : curr
),
[]
)
: arr.slice();
}
const a = [1, 2, 3, 4];
const b = "a";
const c = [1, [2, 3], 4];
const d = [1, [2, [3, 4], 5], 6];
const e = [1, [2, [3, [4, [5], [6]], 7], 8], 9];
console.log(flattenArray(a, Infinity));
console.log(flattenArray(b, Infinity));
console.log(flattenArray(c, Infinity));
console.log(flattenArray(d, Infinity));
console.log(flattenArray(e, Infinity));
There is another interesting way to do it.
Stringify the array
remove all array start symbol ([) and array end symbol(])
Add array start symbol at the beginning and array end symbol at the end.
Now parse the resulting string
const arr2 = [0, 1, 2, [5, [10, [3, 4]]]]
const arr2 = [0, 1, 2, [5, [10, [3, 4]]]]
console.log( JSON.parse('['+ JSON.stringify(arr2).replace(/\[/g, ' ').replace(/\]/g, ' ') + ']'))
Suppose given flatten number list without using the flat function is:
let array = [2,3,[5,2,[6,[3, [4, 5, [5, 1, 3]]]],1,1],9];
//let array= [2,3,[5,2,[6,[3, [4, 5, [5, {"key":"value"}, 3]]]],1,1],9];
//achieve above commented nested array condition using second approach.
The best answer already given by #Mahipal that would be first approach i.e.
array.toString().split(',')
with number array conversion
array.toString().split(',').map(n => +n)
another approach would be using function recursion without toString()
function flatter(arr) {
if (!Array.isArray(arr) && (!isNaN(arr) || typeof arr === "object")) {
return arr;
}
return arr.reduce((a, b) => {
a.push(...[].concat(flatter(b)));
return a;
}, [])
}
flatter(array);
and output is:
[ 2, 3, 5, 2, 6, 3, 4, 5, 5, 1, 3, 1, 1, 9 ]
Hope this would help many ones.
//Using Recursion
const arr = [1, 2, 3, 4, [5, 6, [6, 7], 7, 8]]
let arr2 = [];
function flat(arr) {
arr.forEach(element => {
if (typeof (element) == 'object') {
flat(element);
} else {
arr2.push(element);
}
});
}
flat(arr);
console.log(arr2);
var multiDimensionArray = [["a"],["b","c"],["d"]]; //array of arrays
var flatArray = Array.prototype.concat.apply([], multiDimensionArray); //flatten array of arrays
console.log(flatArray);
This worked for me:
function myFlattern(arr) {
let check;
do{
check=false;
for(let i=0;i<arr.length;i++)
{
if(Array.isArray(arr[i]))
{
check=true;
let x=arr[i];
arr.splice(i,1,...x);
}
}
}while(check)
return arr;
}
A possible alternative would be without using flat():
var arr = [['object1', 'object2'],['object1'],['object1','object2','object3']];
var flattened = [].concat.apply([],arr);
You can use this to forget about the depth of nesting:
let multiArr = [1, [1, 2, [3, 4]], [2, 4, [45, 98]]];
while (multiArr.find((elem) => Array.isArray(elem))) {
multiArr = [].concat.apply([], multiArr);
}
console.log(multiArr);
Related
How to filter an array and return new array of objects with indexed values?
Given the array const vals = [1, 2, 3, 4, 5, 6, 7, 8, 9]; How can I filter and return a new array of indexed key/value pair objects for example: const vals = [1, 2, 3, 4, 5, 6, 7, 8, 9]; // My fail attempt using filter() let obj = vals.filter((n, i) => { return new Object({ i: n % 2 }); }); return obj; // expected result [{1:2}, {3:4}, {5:6}, {7:8}] I need to keep the index values as I will filter 2 different arrays with different criteria and associated them later. Update Second attempt using map() as suggested in the comments let obj = vals.map((n, i) => { if (n % 2) { return { [i]: n }; } }); Gives me the following: [{0:1}, undefined, {2:3}, undefined, {4:5}, undefined, {6:7}, undefined, {8:9}]
To get a list of { key: value } objects where key is the index, and the values are only even without the odd values, you can do this: const vals = [1, 2, 3, 4, 5, 6, 7, 8, 9]; const result = vals.map((v, i) => [i, v]) .filter(([_, v]) => v % 2 == 0) .map(([i, v]) => ({ [i]: v })); console.log(result); With the first map, you make a list of [[0, 1], ...] pairs to save the index for later. Then you filter your index-value pairs so only even values remain. Then you pack those pairs into an object in another map. This can be done more efficiently with a single iteration using reduce: const vals = [1, 2, 3, 4, 5, 6, 7, 8, 9]; const result = vals.reduce((a, v, i) => { if (v % 2 == 0) { a.push({ [i]: v }); } return a; }, []); console.log(result);
Youn can try simple for loop or the reduce function let arr = []; for(let i = 0; i<vals.length-1;i += 2) { let obj={}; obj[vals[i]]=vals[i+1]; arr.push(obj); };
Convert an object to a query string
i have the following object let filters = { first_ids: [1,2,3], second_ids: [2,4,9] } Now, i want to loop over them and then return something like the following. filters[first_ids][]=1&filters[first_ids][]=2&filters[first_ids][]=3&filters[second_ids][]=2&filters[second_ids][]=4&filters[second_ids][]=9& What i tried is following, but i know this is not a elegant way. let filters = { first_ids: [1,2,3], second_ids: [2,4,9] } let finalFilters = [] let firstFilters = filters.first_ids.map((e) => `filters[first_ids][]=${e}&`) let secondFilters = filters.second_ids.map((e) => `filters[second_ids][]=${e}&`) finalFilters.push(firstFilters) finalFilters.push(secondFilters) finalFilters.toString().replace(/,/g, "") Also the filters object can have more keys.
Something like this might work: let filters = { first_ids: [1, 2, 3], second_ids: [2, 4, 9], }; const output = Object.entries(filters).flatMap(([name, ids]) => { return ids.map(id => { return `filters[${encodeURIComponent(name)}]=${encodeURIComponent(id)}`; }); }).join('&'); console.log(output) The main idea is to look though each key, value pair in the object fitlers, then loop though all the ids, and construct the query string fragment, and finally join it on &. Take a look at Object.entries, Array.prototype.flatMap and encodeURIComponent
You could take a nested approach for generating the string. const filters = { first_ids: [1, 2, 3], second_ids: [2, 4, 9] }, result = Object .entries(filters) .flatMap(([k, a]) => a.map(v => `filters[${k}][]=${v}`)) .join('&'); console.log(result);
reduce into an array first and then use join. let filters = { first_ids: [1, 2, 3], second_ids: [2, 4, 9], }; const res = Object.entries(filters) .reduce((r, [k, v]) => (v.forEach((n) => r.push(`filters[${k}][]=${n}`)), r), []) .join("&"); console.log(res)
Here's some generic solution: function toPhpQueryString(o, key) { if (o === null || o === undefined) return ''; if (typeof o !== 'object') return key + '=' + encodeURIComponent(o); if (Array.isArray(o)) return o.map(v => toPhpQueryString(v, key + '[]')).join('&'); return Object.entries(o).map(p => toPhpQueryString(p[1], key + '[' + p[0] + ']')).join('&'); } // let filters = { first_ids: [1, 2, 3], second_ids: [2, 4, 9], deep: { object: { abc: [33, 44], xyz: 'hey', } } } qs = toPhpQueryString(filters, 'filters'); console.log(qs)
Get item that is present in all arrays (Javascript)
I have an object containing a bunch of arrays like this: { names: [0, 1, 2], gender: [2, 5, 1], boolean: [7, 2, 1, 6] } How can I get the value which is present in all arrays, in this case, 1?
A way of achieving this is by getting the distinct values for all the key values and then looping through the values, checking the value exists for each key's array. const obj = { names: [0, 1, 2], gender: [2, 5, 1], boolean: [7, 2, 1, 6] } // [1] Get the object's keys const keys = Object.keys(obj); // [2] Get distinct values by combining all the key arrays. const distinctValues = [...new Set(keys.reduce((all, curr) => { return all.concat(obj[curr]); }, []))]; // [3] Filter the distinct values by checking each value against // the key's array const presentInAll = distinctValues.filter((value) => { return keys.every((key) => obj[key].includes(value)); }); console.log(presentInAll);
I thought I'd add another solution that does not make any temporary copies of the original data which can be useful if the data is larger or memory management is important. const data = { names: [0, 1, 2], gender: [2, 5, 1], boolean: [7, 2, 1, 6] }; function findCommon(...arrays) { // sort arrays by length so we optimize and iterate first by the shortest array arrays.sort((a, b) => { return a.length - b.length; }); let results = new Set(); // for each item in the first array for (let item of arrays[0]) { // look in other arrays for this value let found = true; for (let i = 1; i < arrays.length; i++) { if (!arrays[i].includes(item)) { found = false; break; } } if (found) { results.add(item); } } return results; } let results = findCommon(data.names, data.gender, data.boolean); console.log(Array.from(results));
how to sort an array of numbers in javascript making sure that the first count finished before adding duplicates?
I have this array of numbers that i am working with that currently look this; count = [1,4,3,1,2,3,4,5,6,2,3,5,7]; How can i transform and sort it to make it look like this; count = [1,2,3,4,5,6,7,1,2,3,3,4,5]; Please help, any idea out there on how to approach this?
1) Get unique elements and sort 2) Get remaining elements and sort 3) combine (1) and (2) arrays. count = [1, 4, 3, 1, 2, 3, 4, 5, 6, 2, 3, 5, 7]; const spSort = arr => { const uniq = [...new Set([...arr])]; const rem = []; const temp_set = new Set([...arr]); arr.forEach(x => { if (temp_set.has(x)) { temp_set.delete(x); } else { rem.push(x); } }); return [...uniq.sort(), ...rem.sort()]; }; console.log(spSort(count));
Use a Set to create unique numbers and a hash object to keep count of duplicates: const count = [1, 4, 3, 1, 2, 3, 4, 5, 6, 2, 3, 5, 7]; const hash = count.reduce((obj, num) => { obj[num] = obj[num] ? ++obj[num] : 1; return obj; }, {}); const uniq = [...new Set(count)].sort(); uniq.forEach((num, _, arr) => { while (--hash[num]) arr.push(num); }); console.info(uniq);
Expect Arrays to be equal ignoring order
With Jasmine is there a way to test if 2 arrays contain the same elements, but are not necessarily in the same order? ie array1 = [1,2,3]; array2 = [3,2,1]; expect(array1).toEqualIgnoreOrder(array2);//should be true
Edit Jasmine 2.8 adds arrayWithExactContents that will succeed if the actual value is an Array that contains all of the elements in the sample in any order. See keksmasta's answer Original (outdated) answer If it's just integers or other primitive values, you can sort() them before comparing. expect(array1.sort()).toEqual(array2.sort()); If its objects, combine it with the map() function to extract an identifier that will be compared array1 = [{id:1}, {id:2}, {id:3}]; array2 = [{id:3}, {id:2}, {id:1}]; expect(array1.map(a => a.id).sort()).toEqual(array2.map(a => a.id).sort());
You could use expect.arrayContaining(array) from standard jest: const expected = ['Alice', 'Bob']; it('matches even if received contains additional elements', () => { expect(['Alice', 'Bob', 'Eve']).toEqual(expect.arrayContaining(expected)); });
jasmine version 2.8 and later has jasmine.arrayWithExactContents() Which expects that an array contains exactly the elements listed, in any order. array1 = [1,2,3]; array2 = [3,2,1]; expect(array1).toEqual(jasmine.arrayWithExactContents(array2)) See https://jasmine.github.io/api/3.4/jasmine.html
The jest-extended package provides us few assertions to simplify our tests, it's less verbose and for failing tests the error is more explicit. For this case we could use toIncludeSameMembers expect([{foo: "bar"}, {baz: "qux"}]).toIncludeSameMembers([{baz: "qux"}, {foo: "bar"}]);
simple... array1 = [1,2,3]; array2 = [3,2,1]; expect(array1).toEqual(jasmine.arrayContaining(array2));
// check if every element of array2 is element of array1 // to ensure [1, 1] !== [1, 2] array2.forEach(x => expect(array1).toContain(x)) // check if every element of array1 is element of array2 // to ensure [1, 2] !== [1, 1] array1.forEach(x => expect(array2).toContain(x)) // check if they have equal length to ensure [1] !== [1, 1] expect(array1.length).toBe(array2.length)
//Compare arrays without order //Example //a1 = [1, 2, 3, 4, 5] //a2 = [3, 2, 1, 5, 4] //isEqual(a1, a2) -> true //a1 = [1, 2, 3, 4, 5]; //a2 = [3, 2, 1, 5, 4, 6]; //isEqual(a1, a2) -> false function isInArray(a, e) { for ( var i = a.length; i--; ) { if ( a[i] === e ) return true; } return false; } function isEqArrays(a1, a2) { if ( a1.length !== a2.length ) { return false; } for ( var i = a1.length; i--; ) { if ( !isInArray( a2, a1[i] ) ) { return false; } } return true; }
There is currenly a matcher for this USE CASE: https://github.com/jest-community/jest-extended/pull/122/files test('passes when arrays match in a different order', () => { expect([1, 2, 3]).toMatchArray([3, 1, 2]); expect([{ foo: 'bar' }, { baz: 'qux' }]).toMatchArray([{ baz: 'qux' }, { foo: 'bar' }]); });
function equal(arr1, arr2){ return arr1.length === arr2.length && arr1.every((item)=>{ return arr2.indexOf(item) >-1 }) && arr2.every((item)=>{ return arr1.indexOf(item) >-1 }) } The idea here is to first determine if the length of the two arrays are same, then check if all elements are in the other's array.
Here's a solution that will work for any number or arrays https://gist.github.com/tvler/cc5b2a3f01543e1658b25ca567c078e4 const areUnsortedArraysEqual = (...arrs) => arrs.every((arr, i, [first]) => !i || arr.length === first.length) && arrs .map(arr => arr.reduce( (map, item) => map.set(item, (map.get(item) || 0) + 1), new Map(), ), ) .every( (map, i, [first]) => !i || [...first, ...map].every(([item]) => first.get(item) === map.get(item)), ); Some tests (a few answers to this question don't account for arrays with multiple items of the same value, so [1, 2, 2] and [1, 2] would incorrectly return true) [1, 2] true [1, 2], [1, 2] true [1, 2], [1, 2], [1, 2] true [1, 2], [2, 1] true [1, 1, 2], [1, 2, 1] true [1, 2], [1, 2, 3] false [1, 2, 3, 4], [1, 2, 3], [1, 2] false [1, 2, 2], [1, 2] false [1, 1, 2], [1, 2, 2] false [1, 2, 3], [1, 2], [1, 2, 3] false
This algorithm is great for arrays where each item is unique. If not, you can add in something to check for duplicates... tests = [ [ [1,0,1] , [0,1,1] ], [ [1,0,1] , [0,0,1] ], //breaks on this one... [ [2,3,3] , [2,2,3] ], //breaks on this one also... [ [1,2,3] , [2,1,3] ], [ [2,3,1] , [1,2,2] ], [ [2,2,1] , [1,3,2] ] ] tests.forEach(function(test) { console.log('eqArraySets( '+test[0]+' , '+test[1]+' ) = '+eqArraySets( test[0] , test[1] )); }); function eqArraySets(a, b) { if ( a.length !== b.length ) { return false; } for ( var i = a.length; i--; ) { if ( !(b.indexOf(a[i])>-1) ) { return false; } if ( !(a.indexOf(b[i])>-1) ) { return false; } } return true; }
This approach has worse theoretical worst-case run-time performance, but, because it does not perform any writes on the array, it might be faster in many circumstances (haven't tested performance yet): WARNING: As Torben pointed out in the comments, this approach only works if both arrays have unique (non-repeating) elements (just like several of the other answers here). /** * Determine whether two arrays contain exactly the same elements, independent of order. * #see https://stackoverflow.com/questions/32103252/expect-arrays-to-be-equal-ignoring-order/48973444#48973444 */ function cmpIgnoreOrder(a, b) { const { every, includes } = _; return a.length === b.length && every(a, v => includes(b, v)); } // the following should be all true! const results = [ !!cmpIgnoreOrder([1,2,3], [3,1,2]), !!cmpIgnoreOrder([4,1,2,3], [3,4,1,2]), !!cmpIgnoreOrder([], []), !cmpIgnoreOrder([1,2,3], [3,4,1,2]), !cmpIgnoreOrder([1], []), !cmpIgnoreOrder([1, 3, 4], [3,4,5]) ]; console.log('Results: ', results) console.assert(_.reduce(results, (a, b) => a && b, true), 'Test did not pass!'); <script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.5/lodash.js"></script>
I am currently using this helper function (for TypeScript). It makes sure that arrays that have non unique elements are supported as well. function expectArraysToBeEqualIgnoringOrder<T>(arr1: T[], arr2: T[]) { while(arr1.length > 0) { expect(arr1.length).toEqual(arr2.length) const elementToDelete = arr1[0] arr1 = arr1.filter(element => element !== elementToDelete) arr2 = arr2.filter(element => element !== elementToDelete) } expect(arr2.length).toEqual(0) } Many of the other asnwers do not correctly handle cases like this: array1: [a, b, b, c] array2: [a, b, c, c] Here the number of elements in both arrays is the same and both arrays contain all elements from the other array, yet they are different arrays and the test should fail. It runs in O(n^2) (precisely (n^2 + n) / 2), so it's not suitable for very large arrays, but it's suitable for arrays that are not easilly sorted and therefore can not be compared in O(n * log(n))