I want to flatten string[][]
into string[]
.
The advice given in dozens of SO answers is: [].concat(...arrays)
.
But that gives me this error:
Argument of type 'string[]' is not assignable to parameter of type 'ConcatArray'.
Types of property 'slice' are incompatible.
Type '(start?: number | undefined, end?: number | undefined) => string[]' is not assignable to type '(start?: number | undefined, end?: number | undefined) => never[]'.
Type 'string[]' is not assignable to type 'never[]'.
Type 'string' is not assignable to type 'never'.
Another way I tried is this:
let foo: string[][] = [["a", "b", "c"], ["a", "b", "c"], ["a", "b", "c"]];let bar = [].concat(...foo);
Which gives a similar error:
Argument of type 'string[]' is not assignable to parameter of type 'ConcatArray'.
Why does it work for everyone but me?
Best Answer
Try this:
const a = [["a", "b", "c"], ["a", "b", "c"], ["a", "b", "c"]]const result = a.reduce((accumulator, value) => accumulator.concat(value), []);console.log(result)
You can flatten the array with flat()
let foo: string[][] = [["a", "b", "c"], ["a", "b", "c"], ["a", "b", "c"]];let bar = foo.flat()
log
console.log(bar) // a,b,c,a,b,c,a,b,c
UPDATE
By correcting the type to string[] you can also use concat
let foo: string[][] = [["a", "b", "c"], ["a", "b", "c"], ["a", "b", "c"]];let bar : string[] = []bar = bar.concat(foo[0], foo[1], foo[2])
Here's the simplest option:
let bar = ([] as string[]).concat(...foo);
Like @Kokodoko's approach but with the typings inline.
Here's a generic solution:
function flatten<T>(arr: T[][]): T[] {return ([] as T[]).concat(...arr);}
And a deep/recursive extension:
type NestedArray<T> = Array<NestedArray<T> | T>;function flattenDeep<T>(input: NestedArray<T>): T[] {return flatten(input.map(x => Array.isArray(x) ? flattenDeep(x) : [x]));};
This answer may be more efficient for a deep flatten, I just had fun trying to write something that felt more elegant to me.
.flat() will also give the type error. You can use Generics to solve this
let st : string[][] | Array<string> = [['a'] , ['b']]let bar = [].concat(...st);console.log(bar)
Either way, your call. Just know that your type declaration is not right.
The code
const res = [].concat(...foo);
should work. I guess it's a misconfiguration in tsconfig that causes that error for you. Make sure that there is at least es2015
(better es2018
) in your tsconfig's lib
array. To make the new flat
work as shown by kokodoko, make sure to also add esnext
"lib": ["es2018","dom","esnext"]
I believe you have strictNullCheck: true
An empty array with no contextual type ([] in [].concat(arg)) is inferred as never[] under strictNullChecks. and never is not assignable from any other type.
([] as any[]).concat(foo); should do the trick
For much more deeply nested array of arrays such as:[1, 2, 3, [4, [5, [6, [7]]]]]
type NestedArray<T> = Array<NestedArray<T> | T>;const flatten = <T>(input: NestedArray<T>, acc: T[] = []): T[] => {return input.reduce((_: T[], current) => {if (Array.isArray(current)) return flatten(current, acc); acc.push(current);return acc;}, []);};
Usage:
console.log(flatten([1, 2, 3, [4, [5, [6, [7]]]]]));
The answer from Steven worked for my jest tests but not in my actual angular 15 app due to a constraint error I can't figure out. Slightly modified as a regular recursive function to keep Typescript quiet :
import { Injectable} from '@angular/core';export type NestedArray<T> = Array<NestedArray<T> | T>;export class ArrayHelper {public static flatten = <T>(input: NestedArray<T>, res: T[] = []): T[] => {input.forEach((el: T | NestedArray<T>) => {if (Array.isArray(el)) {ArrayHelper.flatten(el, res);} else {res.push(el);}});return res;};}
Example tests:
describe('flattening', () => {it('pass through', () => {expect(ArrayHelper.flatten(['hi', 'there'])).toHaveLength(2);});it('2 level', () => {expect(ArrayHelper.flatten(['hi', 'there', ['that', 'is', 'all', 'folks']])).toHaveLength(6);});it('as per SO', () => {expect(ArrayHelper.flatten([1, 2, 3, [4, [5, [6, [7]]]]])).toHaveLength(7);});});