Development Tip

배열 배열 병합 / 편 평화

yourdevel 2020. 9. 27. 14:11
반응형

배열 배열 병합 / 편 평화


다음과 같은 JavaScript 배열이 있습니다.

[["$6"], ["$12"], ["$25"], ["$25"], ["$18"], ["$22"], ["$10"]]

별도의 내부 배열을 다음과 같이 병합하는 방법은 무엇입니까?

["$6", "$12", "$25", ...]

concat배열을 병합 하는 사용할 수 있습니다 .

var arrays = [
  ["$6"],
  ["$12"],
  ["$25"],
  ["$25"],
  ["$18"],
  ["$22"],
  ["$10"]
];
var merged = [].concat.apply([], arrays);

console.log(merged);

apply메서드를 사용하면 concat두 번째 매개 변수를 배열로 사용하므로 마지막 줄은 다음과 같습니다.

var merged2 = [].concat(["$6"], ["$12"], ["$25"], ["$25"], ["$18"], ["$22"], ["$10"]);

또한 Array.prototype.flat()배열을 평탄화하는 데 사용할 수 있는 실험적 방법 (아직 ECMAScript 표준의 일부가 아님)도 있습니다.하지만 Edge에서는 전혀 사용할 수없는 Node.js 버전 11부터 만 사용할 수 있습니다 .

const arrays = [
      ["$6"],
      ["$12"],
      ["$25"],
      ["$25"],
      ["$18"],
      ["$22"],
      ["$10"]
    ];
const merge3 = arrays.flat(1); //The depth level specifying how deep a nested array structure should be flattened. Defaults to 1.
console.log(merge3);
    


다음은 새로운 JavaScript 배열 메서드를 사용하여 n 차원 배열을 평면화하는 짧은 함수입니다.

function flatten(arr) {
  return arr.reduce(function (flat, toFlatten) {
    return flat.concat(Array.isArray(toFlatten) ? flatten(toFlatten) : toFlatten);
  }, []);
}

용법:

flatten([[1, 2, 3], [4, 5]]); // [1, 2, 3, 4, 5]
flatten([[[1, [1.1]], 2, 3], [4, 5]]); // [1, 1.1, 2, 3, 4, 5]

혼란스럽게 숨겨진 메서드가 있는데, 원래 배열을 변경하지 않고 새 배열을 생성합니다.

var oldArray = [[1],[2,3],[4]];
var newArray = Array.prototype.concat.apply([], oldArray);
console.log(newArray); // [ 1, 2, 3, 4 ]


자바 스크립트 감소 기능으로 가장 잘 수행 할 수 있습니다.

var arrays = [["$6"], ["$12"], ["$25"], ["$25"], ["$18"], ["$22"], ["$10"], ["$0"], ["$15"],["$3"], ["$75"], ["$5"], ["$100"], ["$7"], ["$3"], ["$75"], ["$5"]];

arrays = arrays.reduce(function(a, b){
     return a.concat(b);
}, []);

또는 ES2015를 사용하는 경우 :

arrays = arrays.reduce((a, b) => a.concat(b), []);

js-fiddle

Mozilla 문서


여기에있는 대부분의 답변은 거대한 (예 : 200,000 개의 요소) 배열에서 작동하지 않으며 작동하더라도 느립니다. polkovnikov.ph의 대답 은 최고의 성능을 가지고 있지만 깊은 평탄화에는 작동하지 않습니다.

다음은 여러 수준의 중첩이있는 배열에서도 작동하는 가장 빠른 솔루션입니다 .

const flatten = function(arr, result = []) {
  for (let i = 0, length = arr.length; i < length; i++) {
    const value = arr[i];
    if (Array.isArray(value)) {
      flatten(value, result);
    } else {
      result.push(value);
    }
  }
  return result;
};

거대한 배열

flatten(Array(200000).fill([1]));

거대한 배열을 잘 처리합니다. 내 컴퓨터에서이 코드를 실행하는 데 약 14ms가 걸립니다.

중첩 배열

flatten(Array(2).fill(Array(2).fill(Array(2).fill([1]))));

중첩 배열에서 작동합니다. 이 코드는 [1, 1, 1, 1, 1, 1, 1, 1].

중첩 수준이 다른 배열

flatten([1, [1], [[1]]]);

이와 같은 배열을 평면화하는 데 아무런 문제가 없습니다.


업데이트 :이 솔루션은 대형 어레이에서 작동하지 않는 것으로 나타났습니다. 더 빠르고 더 나은 솔루션을 찾고 계시다면 이 답변을 확인하세요 .


function flatten(arr) {
  return [].concat(...arr)
}

Is는 단순히 확장 arr하여 concat()모든 배열을 하나로 병합하는 인수로 전달합니다 . 그것은 동등이다 [].concat.apply([], arr).

딥 플랫 화를 위해 이것을 시도 할 수도 있습니다.

function deepFlatten(arr) {
  return flatten(           // return shalowly flattened array
    arr.map(x=>             // with each x in array
      Array.isArray(x)      // is x an array?
        ? deepFlatten(x)    // if yes, return deeply flattened x
        : x                 // if no, return just x
    )
  )
}

JSBin 데모를 참조하십시오 .

이 답변에 사용 된 ECMAScript 6 요소에 대한 참조 :


참고 : find()및 화살표 기능 과 같은 메서드 는 모든 브라우저에서 지원되지는 않지만 현재 이러한 기능을 사용할 수 없다는 의미는 아닙니다. Babel을 사용하면됩니다 . ES6 코드를 ES5로 변환합니다.


이를 정확히 수행하기 위해 flat 이라는 새로운 네이티브 ECMA 2019 메서드가 있습니다.

const arr1 = [1, 2, [3, 4]];
arr1.flat(); 
// [1, 2, 3, 4]

const arr2 = [1, 2, [3, 4, [5, 6]]];
arr2.flat();
// [1, 2, 3, 4, [5, 6]]

// Flatten 2 levels deep
const arr3 = [2, 2, 5, [5, [5, [6]], 7]];
arr3.flat(2);
// [2, 2, 5, 5, 5, [6], 7];

// Flatten all levels
const arr4 = [2, 2, 5, [5, [5, [6]], 7]];
arr4.flat(Infinity);
// [2, 2, 5, 5, 5, 6, 7];

밑줄 을 사용할 수 있습니다 .

var x = [[1], [2], [3, 4]];

_.flatten(x); // => [1, 2, 3, 4]

일반 절차는 특정 동작을 활용해야 할 때마다 복잡성을 다시 작성할 필요가 없음을 의미합니다.

concatMap(또는 flatMap)은이 상황에서 정확히 필요한 것입니다.

// concat :: ([a],[a]) -> [a]
const concat = (xs,ys) =>
  xs.concat (ys)

// concatMap :: (a -> [b]) -> [a] -> [b]
const concatMap = f => xs =>
  xs.map(f).reduce(concat, [])

// id :: a -> a
const id = x =>
  x

// flatten :: [[a]] -> [a]
const flatten =
  concatMap (id)

// your sample data
const data =
  [["$6"], ["$12"], ["$25"], ["$25"], ["$18"], ["$22"], ["$10"]]

console.log (flatten (data))

선견

그리고 네, 당신은 단지 평평하게, 올바르게 추측 하나 개 가 얼마나 정확히 수준 해야 작동을

이와 같은 데이터 세트를 상상해보십시오.

// Player :: (String, Number) -> Player
const Player = (name,number) =>
  [ name, number ]

// team :: ( . Player) -> Team
const Team = (...players) =>
  players

// Game :: (Team, Team) -> Game
const Game = (teamA, teamB) =>
  [ teamA, teamB ]

// sample data
const teamA =
  Team (Player ('bob', 5), Player ('alice', 6))

const teamB =
  Team (Player ('ricky', 4), Player ('julian', 2))

const game =
  Game (teamA, teamB)

console.log (game)
// [ [ [ 'bob', 5 ], [ 'alice', 6 ] ],
//   [ [ 'ricky', 4 ], [ 'julian', 2 ] ] ]

이제 참가할 모든 선수를 표시하는 명단을 인쇄하고 싶다고 가정 해 보겠습니다 game.

const gamePlayers = game =>
  flatten (game)

gamePlayers (game)
// => [ [ 'bob', 5 ], [ 'alice', 6 ], [ 'ricky', 4 ], [ 'julian', 2 ] ]

우리의 flatten절차가 중첩 된 배열도 평면화 한다면 , 우리는이 가비지 결과로 끝날 것입니다 ...

const gamePlayers = game =>
  badGenericFlatten(game)

gamePlayers (game)
// => [ 'bob', 5, 'alice', 6, 'ricky', 4, 'julian', 2 ]

롤링 딥, 베이비

그것은 때때로 당신이 중첩 된 배열을 평탄화하고 싶지 않다는 것을 말하는 것이 아닙니다. 단지 이것이 기본 동작이되어서는 안됩니다.

우리는 deepFlatten쉽게 절차를 만들 수 있습니다

// concat :: ([a],[a]) -> [a]
const concat = (xs,ys) =>
  xs.concat (ys)

// concatMap :: (a -> [b]) -> [a] -> [b]
const concatMap = f => xs =>
  xs.map(f).reduce(concat, [])

// id :: a -> a
const id = x =>
  x

// flatten :: [[a]] -> [a]
const flatten =
  concatMap (id)

// deepFlatten :: [[a]] -> [a]
const deepFlatten =
  concatMap (x =>
    Array.isArray (x) ? deepFlatten (x) : x)

// your sample data
const data =
  [0, [1, [2, [3, [4, 5], 6]]], [7, [8]], 9]

console.log (flatten (data))
// [ 0, 1, [ 2, [ 3, [ 4, 5 ], 6 ] ], 7, [ 8 ], 9 ]

console.log (deepFlatten (data))
// [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ]

그곳에. 이제 각 작업에 대한 도구가 있습니다. 하나는 한 수준의 중첩을 스쿼시 flatten하고 다른 하나는 모든 중첩을 제거하기위한 것 deepFlatten입니다.

어쩌면 당신은 그것을 호출 할 수 있습니다 obliterate또는 nuke당신이 이름이 마음에 들지 않으면 deepFlatten.


두 번 반복하지 마십시오!

물론 위의 구현은 영리하고 간결하지만 .map뒤에 호출을 사용하면 .reduce실제로 필요한 것보다 더 많은 반복을 수행하고 있음 의미합니다.

내가 부르는 신뢰할 수있는 결합 mapReduce자를 사용하면 반복을 최소화 하는 데 도움이됩니다. 매핑 함수 m :: a -> b, 축소 함수를 취하고 r :: (b,a) ->b새로운 축소 함수를 반환합니다.이 결합기는 변환기 의 핵심입니다 . 관심이 있으시면 다른 답변을 썼습니다

// mapReduce = (a -> b, (b,a) -> b, (b,a) -> b)
const mapReduce = (m,r) =>
  (acc,x) => r (acc, m (x))

// concatMap :: (a -> [b]) -> [a] -> [b]
const concatMap = f => xs =>
  xs.reduce (mapReduce (f, concat), [])

// concat :: ([a],[a]) -> [a]
const concat = (xs,ys) =>
  xs.concat (ys)

// id :: a -> a
const id = x =>
  x

// flatten :: [[a]] -> [a]
const flatten =
  concatMap (id)
  
// deepFlatten :: [[a]] -> [a]
const deepFlatten =
  concatMap (x =>
    Array.isArray (x) ? deepFlatten (x) : x)

// your sample data
const data =
  [ [ [ 1, 2 ],
      [ 3, 4 ] ],
    [ [ 5, 6 ],
      [ 7, 8 ] ] ]

console.log (flatten (data))
// [ [ 1. 2 ], [ 3, 4 ], [ 5, 6 ], [ 7, 8 ] ]

console.log (deepFlatten (data))
// [ 1, 2, 3, 4, 5, 6, 7, 8 ]


배열에 배열이 아닌 요소가있을 수있는보다 일반적인 경우에 대한 솔루션입니다.

function flattenArrayOfArrays(a, r){
    if(!r){ r = []}
    for(var i=0; i<a.length; i++){
        if(a[i].constructor == Array){
            r.concat(flattenArrayOfArrays(a[i], r));
        }else{
            r.push(a[i]);
        }
    }
    return r;
}

기능적 스타일의 또 다른 ECMAScript 6 솔루션 :

함수 선언 :

const flatten = arr => arr.reduce(
  (a, b) => a.concat(Array.isArray(b) ? flatten(b) : b), []
);

그리고 그것을 사용하십시오 :

flatten( [1, [2,3], [4,[5,[6]]]] ) // -> [1,2,3,4,5,6]

 const flatten = arr => arr.reduce(
         (a, b) => a.concat(Array.isArray(b) ? flatten(b) : b), []
       );


console.log( flatten([1, [2,3], [4,[5],[6,[7,8,9],10],11],[12],13]) )

최신 브라우저의 최신 릴리스에서 사용 가능한 기본 함수 Array.prototype.flat () (ES6에 대한 제안 ) 도 고려하십시오 . @ (Константин Ван) 및 @ (Mark Amery) 덕분에 댓글에서 언급했습니다.

flat함수에는 1기본적으로 동일한 배열 중첩의 예상 깊이를 지정하는 하나의 매개 변수가 있습니다.

[1, 2, [3, 4]].flat();                  // -> [1, 2, 3, 4]

[1, 2, [3, 4, [5, 6]]].flat();          // -> [1, 2, 3, 4, [5, 6]]

[1, 2, [3, 4, [5, 6]]].flat(2);         // -> [1, 2, 3, 4, 5, 6]

[1, 2, [3, 4, [5, 6]]].flat(Infinity);  // -> [1, 2, 3, 4, 5, 6]

let arr = [1, 2, [3, 4]];

console.log( arr.flat() );

arr =  [1, 2, [3, 4, [5, 6]]];

console.log( arr.flat() );
console.log( arr.flat(1) );
console.log( arr.flat(2) );
console.log( arr.flat(Infinity) );


사용 reduce(callback[, initialValue])방법은 어떻습니까?JavaScript 1.8

list.reduce((p,n) => p.concat(n),[]);

일을 할 것입니다.


단일 요소 배열의 배열을 평면화하려면 라이브러리를 가져올 필요가 없습니다. 간단한 루프가 가장 간단하고 효율적인 솔루션입니다.

for (var i = 0; i < a.length; i++) {
  a[i] = a[i][0];
}

반대 투표자에게 : 질문을 읽고 매우 다른 문제에 적합하지 않으므로 반대 투표하지 마십시오. 이 솔루션은 질문에 대해 가장 빠르고 간단합니다.


const common = arr.reduce((a, b) => [...a, ...b], [])

제발 참고 하면 Function.prototype.apply( [].concat.apply([], arrays)) 또는 확산 연산자 ( [].concat(...arrays)) 두 함수의 모든 인수를 스택에 저장되기 때문에 스택 큰 어레이를위한 오버 플로우가 발생할 수있는 배열을 평평하게하기 위해 사용된다.

다음은 가장 중요한 요구 사항을 서로 비교하는 기능적 스타일의 스택 안전 구현입니다.

  • 재사용 성
  • 가독성
  • 간결
  • 공연

// small, reusable auxiliary functions:

const foldl = f => acc => xs => xs.reduce(uncurry(f), acc); // aka reduce

const uncurry = f => (a, b) => f(a) (b);

const concat = xs => y => xs.concat(y);


// the actual function to flatten an array - a self-explanatory one-line:

const flatten = xs => foldl(concat) ([]) (xs);

// arbitrary array sizes (until the heap blows up :D)

const xs = [[1,2,3],[4,5,6],[7,8,9]];

console.log(flatten(xs));


// Deriving a recursive solution for deeply nested arrays is trivially now


// yet more small, reusable auxiliary functions:

const map = f => xs => xs.map(apply(f));

const apply = f => a => f(a);

const isArray = Array.isArray;


// the derived recursive function:

const flattenr = xs => flatten(map(x => isArray(x) ? flattenr(x) : x) (xs));

const ys = [1,[2,[3,[4,[5],6,],7],8],9];

console.log(flattenr(ys));

카레 형태, 함수 구성 및 고차 함수의 작은 화살표 함수에 익숙해지면이 코드는 산문처럼 읽습니다. 프로그래밍은 부작용이 없기 때문에 항상 예상대로 작동하는 작은 빌딩 블록을 모으는 것으로 구성됩니다.


ES6 편 평화

참조 lodash 펼치기 , 밑줄 펼치기 (얕은를 true)

function flatten(arr) {
  return arr.reduce((acc, e) => acc.concat(e), []);
}

또는

function flatten(arr) {
  return [].concat.apply([], arr);
}

테스트

test('already flatted', () => {
  expect(flatten([1, 2, 3, 4, 5])).toEqual([1, 2, 3, 4, 5]);
});

test('flats first level', () => {
  expect(flatten([1, [2, [3, [4]], 5]])).toEqual([1, 2, [3, [4]], 5]);
});

ES6 한 줄 딥 플랫

참조 lodash flattenDeep , 밑줄 펼치기를

function flattenDeep(arr) {
  return arr.reduce((acc, e) => Array.isArray(e) ? acc.concat(flattenDeep(e)) : acc.concat(e), []);
}

테스트

test('already flatted', () => {
  expect(flattenDeep([1, 2, 3, 4, 5])).toEqual([1, 2, 3, 4, 5]);
});

test('flats', () => {
  expect(flattenDeep([1, [2, [3, [4]], 5]])).toEqual([1, 2, 3, 4, 5]);
});

Haskellesque 접근 방식

function flatArray([x,...xs]){
  return x ? [...Array.isArray(x) ? flatArray(x) : [x], ...flatArray(xs)] : [];
}

var na = [[1,2],[3,[4,5]],[6,7,[[[8],9]]],10];
    fa = flatArray(na);
console.log(fa);


ES6 방법 :

const flatten = arr => arr.reduce((acc, next) => acc.concat(Array.isArray(next) ? flatten(next) : next), [])

const a = [1, [2, [3, [4, [5]]]]]
console.log(flatten(a))

flattenN 배 중첩 배열에 대한 ES3 대체 기능을 사용하는 ES5 방식 :

var flatten = (function() {
  if (!!Array.prototype.reduce && !!Array.isArray) {
    return function(array) {
      return array.reduce(function(prev, next) {
        return prev.concat(Array.isArray(next) ? flatten(next) : next);
      }, []);
    };
  } else {
    return function(array) {
      var arr = [];
      var i = 0;
      var len = array.length;
      var target;

      for (; i < len; i++) {
        target = array[i];
        arr = arr.concat(
          (Object.prototype.toString.call(target) === '[object Array]') ? flatten(target) : target
        );
      }

      return arr;
    };
  }
}());

var a = [1, [2, [3, [4, [5]]]]];
console.log(flatten(a));


문자열 요소가 1 개인 배열 만있는 경우 :

[["$6"], ["$12"], ["$25"], ["$25"]].join(',').split(',');

일을 할 것입니다. 코드 예제와 구체적으로 일치하는 Bt.


var arrays = [["a"], ["b", "c"]];
Array.prototype.concat.apply([], arrays);

// gives ["a", "b", "c"]

(@danhbear의 의견에 따라 별도의 답변으로 작성했습니다.)


공간 효율적인 생성기 기능을 권장 합니다 .

function* flatten(arr) {
  if (!Array.isArray(arr)) yield arr;
  else for (let el of arr) yield* flatten(el);
}

// Example:
console.log(...flatten([1,[2,[3,[4]]]])); // 1 2 3 4

원하는 경우 다음과 같이 평면화 된 값의 배열을 만듭니다.

let flattened = [...flatten([1,[2,[3,[4]]]])]; // [1, 2, 3, 4]

중첩 배열의 깊이에 대해 Array.flat()with Infinity사용할 수 있습니다 .

var arr = [ [1,2,3,4], [1,2,[1,2,3]], [1,2,3,4,5,[1,2,3,4,[1,2,3,4]]], [[1,2,3,4], [1,2,[1,2,3]], [1,2,3,4,5,[1,2,3,4,[1,2,3,4]]]] ];

let flatten = arr.flat(Infinity)

console.log(flatten)

여기에서 브라우저 호환성을 확인하세요.


나는 전체 배열을 그대로 문자열로 변환하고 싶지만 다른 답변과 달리 원치 않는 결과를 생성하는 방법 을 사용 JSON.stringify하고 사용하지 않을 것 toString()입니다.

JSON.stringify출력으로 남은 것은 모든 대괄호를 제거하고 결과를 시작 및 끝 대괄호로 다시 감싸고 JSON.parse문자열을 "life"로 되 돌리는 결과를 제공하는 것입니다 .

  • 속도 비용없이 무한 중첩 배열을 처리 할 수 ​​있습니다.
  • 쉼표를 포함하는 문자열 인 배열 항목을 올바르게 처리 할 수 ​​있습니다.

var arr = ["abc",[[[6]]],["3,4"],"2"];

var s = "[" + JSON.stringify(arr).replace(/\[|]/g,'') +"]";
var flattened = JSON.parse(s);

console.log(flattened)

  • 문자열 / 숫자의 다차원 배열에만 해당 (객체 아님)

RECURSION의 직업인 것 같습니다!

  • 여러 수준의 중첩 처리
  • 빈 배열 및 배열이 아닌 매개 변수 처리
  • 돌연변이 없음
  • 최신 브라우저 기능에 의존하지 않음

암호:

var flatten = function(toFlatten) {
  var isArray = Object.prototype.toString.call(toFlatten) === '[object Array]';

  if (isArray && toFlatten.length > 0) {
    var head = toFlatten[0];
    var tail = toFlatten.slice(1);

    return flatten(head).concat(flatten(tail));
  } else {
    return [].concat(toFlatten);
  }
};

용법:

flatten([1,[2,3],4,[[5,6],7]]);
// Result: [1, 2, 3, 4, 5, 6, 7] 

재귀 및 폐쇄를 사용하여 수행했습니다.

function flatten(arr) {

  var temp = [];

  function recursiveFlatten(arr) { 
    for(var i = 0; i < arr.length; i++) {
      if(Array.isArray(arr[i])) {
        recursiveFlatten(arr[i]);
      } else {
        temp.push(arr[i]);
      }
    }
  }
  recursiveFlatten(arr);
  return temp;
}

나는 요 전에 ES6 Generators를 가지고 놀면서이 요점을 썼다 . 포함하는...

function flatten(arrayOfArrays=[]){
  function* flatgen() {
    for( let item of arrayOfArrays ) {
      if ( Array.isArray( item )) {
        yield* flatten(item)
      } else {
        yield item
      }
    }
  }

  return [...flatgen()];
}

var flatArray = flatten([[1, [4]],[2],[3]]);
console.log(flatArray);

기본적으로 원래 입력 배열을 반복하는 생성기를 만들고 있습니다. 배열을 찾으면 yield * 연산자를 재귀와 함께 사용하여 내부 배열을 지속적으로 평면화합니다. 항목이 배열이 아니면 단일 항목 생성 합니다. 그런 다음 ES6 Spread 연산자 (일명 splat 연산자)를 사용하여 생성기를 새 배열 인스턴스로 평면화합니다.

나는 이것의 성능을 테스트하지 않았지만 생성기와 yield * 연산자를 사용하는 좋은 간단한 예라고 생각합니다.

그러나 다시 말하지만, 나는 단지 어리석은 일 이었으므로 이것을 수행하는 더 많은 방법이 있다고 확신합니다.


lodash없는 최고의 솔루션

let flatten = arr => [].concat.apply([], arr.map(item => Array.isArray(item) ? flatten(item) : item))

어렵지 않습니다. 배열을 반복하고 병합하면됩니다.

var result = [], input = [["$6"], ["$12"], ["$25"], ["$25"], ["$18"]];

for (var i = 0; i < input.length; ++i) {
    result = result.concat(input[i]);
}

재귀없는 두 가지 짧은 솔루션을 제안합니다. 계산 복잡도 관점에서는 최적이 아니지만 평균적인 경우에는 잘 작동합니다.

let a = [1, [2, 3], [[4], 5, 6], 7, 8, [9, [[10]]]];

// Solution #1
while (a.find(x => Array.isArray(x)))
    a = a.reduce((x, y) => x.concat(y), []);

// Solution #2
let i = a.findIndex(x => Array.isArray(x));
while (i > -1)
{
    a.splice(i, 1, ...a[i]);
    i = a.findIndex(x => Array.isArray(x));
}

여기서 논리는 입력 배열을 문자열로 변환하고 모든 대괄호 ([])를 제거하고 출력을 배열로 구문 분석하는 것입니다. 이를 위해 ES6 템플릿 기능을 사용하고 있습니다.

var x=[1, 2, [3, 4, [5, 6,[7], 9],12, [12, 14]]];

var y=JSON.parse(`[${JSON.stringify(x).replace(/\[|]/g,'')}]`);

console.log(y)

참고 URL : https://stackoverflow.com/questions/10865025/merge-flatten-an-array-of-arrays

반응형