48476


多重配列のフラット化（`[ 'a', [ 'b', [ 'c', 'd' ], 'e' ], 'f' ]` => `[ 'a', 'b', 'c',
'd', 'e', 'f' ]`）ではなく、  
多重配列を、指定したセルの箇所だけ展開させたいです。どうすればいいですか？  
具体的には、  
**Start**

[code]

    //元のテーブル
    [
      ['1','b','c',['d1','d2'],['e1','e2']],
      ['2','b',['c1','c2'],['d1','d2'],'e'],
      ['3',['b1','b2'],'c',['d1','d2'],'e'],
      ['4',['b1','b2','b3','b4'],'c','d','e']
    ]
    //セルの指定
    [ false, true, false, true ] //例えば2つ目と4つ目のセル（この例だとbのセルとdのセル）だけ展開する指定
    
[/code]

**Goal**

[code]

    //展開後のテーブル
    [
      ['1','b','c','d1',['e1','e2']],
      ['1','b','c','d2',['e1','e2']],
      ['2','b',['c1','c2'],'d1','e'],
      ['2','b',['c1','c2'],'d2','e'],
      ['3','b1','c','d1','e'],
      ['3','b1','c','d2','e'],
      ['3','b2','c','d1','e'],
      ['3','b2','c','d2','e'],
      ['4','b1','c','d','e'],
      ['4','b2','c','d','e'],
      ['4','b3','c','d','e'],
      ['4','b4','c','d','e']
    ]
    
[/code]

みたいな感じです。  
Arrayのメソッドチェーンの途中で使いたいのでreduce等のコールバック関数にしたいです。  
↓うまくいかなかったやつ（というか何をやっているのかわからなくなってきたやつ）の中で一番ゴールに近かったやつ

[code]

    var expand = function(targets) {
      return function(newTable, oldRow) {
        //
        return newTable.concat(targets.reduce(function(rows, target, i) {
          return target ? rows.concat(rows.reduce(function(newRows, row) {
            return [].concat(row[ i ]).map(function(cell) {
              var newRow = row.slice()
              newRow[ i ] = cell
              return newRow
            })
          }, [])) : rows
        }, [ oldRow.slice() ]))
        //
      }
    }
    var result = [
      ['1','b','c',['d1','d2'],['e1','e2']],
      ['2','b',['c1','c2'],['d1','d2'],'e'],
      ['3',['b1','b2'],'c',['d1','d2'],'e'],
      ['4',['b1','b2','b3','b4'],'c','d','e']
    ].reduce(expand([ false, true, false, true ]), [])
    console.log(result)
    
[/code]


CC BY-SA 4.0
2018-09-16T07:51:48.950
48450
2018-09-17T06:20:38.157
30127
question
javascript array
多重配列を、指定したセルの箇所だけ展開させたいです。


`for` を使って力技で書いてみました。

[code]

    var expand = function(targets) {
        return function(acc, row) {
            //
            var i, j, k, v;
            var res = [[]], newres, r;
            for (i = 0; i < row.length; i++) {      // 行の各要素について
                v = row[i];
                if (i < targets.length && targets[i] && Array.isArray(v)) {
                    // 展開して追加
                    newres = [];
                    for (j = 0; j < res.length; j++) {          // ここまでの res それぞれに
                        for (k = 0; k < v.length; k++) {        // v の各要素を追加
                            r = res[j].slice();
                            r.push(v[k]);
                            newres.push(r);
                        }
                    }
                    res = newres;
                } else {
                    // そのまま追加
                    for (j = 0; j < res.length; j++) {          // ここまでの res それぞれに v を追加
                        res[j].push(v);
                    }
                }
            }
    
            return acc.concat(res);
            //
        }
    };
    var result = [
      ['1','b','c',['d1','d2'],['e1','e2']],
      ['2','b',['c1','c2'],['d1','d2'],'e'],
      ['3',['b1','b2'],'c',['d1','d2'],'e'],
      ['4',['b1','b2','b3','b4'],'c','d','e']
    ].reduce(expand([ false, true, false, true ]), []);
    console.log(result);
    
[/code]


CC BY-SA 4.0
2018-09-16T12:41:49.177
48460
2018-09-16T12:41:49.177
5288
48450
answer


masmさんのコードを参考に、自分でも作ってみました。たぶんうまく動いています。

[code]

    var expand = function(targets) {
      return function(newTable, oldRow) {
        var expand = function(table, cellIndex) {
          return cellIndex < table[ 0 ].length ? expand(targets[ cellIndex ] ? table.reduce(function(rows, row) {
            return rows.concat([].concat(row[ cellIndex ]).map(function(cellItem) {
              var newRow = row.slice()
              newRow[ cellIndex ] = cellItem
              return newRow
            }))
          }, []) : table, ++cellIndex) : table
        }
        return newTable.concat(expand([ oldRow.slice() ], 0))
      }
    }
    var result = [
      ['1','b','c',['d1','d2'],['e1','e2']],
      ['2','b',['c1','c2'],['d1','d2'],'e'],
      ['3',['b1','b2'],'c',['d1','d2'],'e'],
      ['4',['b1','b2','b3','b4'],'c','d','e'],
      ['5','b','c','d','e']
    ].reduce(expand([ false, true, false, true ]), [])
    console.log(result)
    
[/code]


CC BY-SA 4.0
2018-09-17T03:08:28.707
48474
2018-09-17T03:15:36.320
2018-09-17T03:15:36.320
30127
30127
48450
answer


### 直積

この質問において「展開」と説明されている部分は、出力例を見ると[直積（デカルト積）](https://ja.wikipedia.org/wiki/%E7%9B%B4%E7%A9%8D%E9%9B%86%E5%90%88)を求める処理に近そうです。直積を簡単に言うと「全ての組み合わせ」です。

[code]

    // product が直積を求める関数だとすると
    product([ [1, 2], ["A", "b"], ['X'] ])
    // このような出力になります
    [ [ 1, 'A', 'X' ],
      [ 1, 'b', 'X' ],
      [ 2, 'A', 'X' ],
      [ 2, 'b', 'X' ] ]
    
[/code]

### 例

質問の一部を「直積」を使って言い換えると、

  * `reduce` で渡される配列の各要素をそれぞれ集合と見なして直積を求めたい
  * 配列でない要素は、1要素の集合と見なす
  * 引数(`targets`)により `true` と指定されていない要素は配列であっても、1要素の集合と見なす

といった感じかと思います。この考え方をコードにすると、例えば以下のようになります。

[code]

    const product = require('cartesian-product')
    
    function expand(targets) {
        return function expander(acmArray, currentArray) {
            const wrapped = currentArray.map((v, i) => {
                if (targets[i] && Array.isArray(v)) {
                    return v
                }
                return [v]
            })
            return acmArray.concat(product(wrapped))
        }
    }
    
    // 使用例:
    const arr = [
        [ [1, 2], ["A", "b"], ['X'] ],
        [ [10, 20], ["C", "d"], ['Y'] ]
    ].reduce(expand([false, true, true]), [])
    console.log( arr )
    // 出力:
    // [ [ [ 1, 2 ], 'A', 'X' ],
    //   [ [ 1, 2 ], 'b', 'X' ],
    //   [ [ 10, 20 ], 'C', 'Y' ],
    //   [ [ 10, 20 ], 'd', 'Y' ] ]
    
[/code]

上では直積を求める関数に、[cartesian-product](https://www.npmjs.com/package/cartesian-
product) を使用しています[（ソースコード）](https://github.com/izaakschroeder/cartesian-
product/blob/master/lib/product.js)。このモジュールは特に吟味してはいないです。汎用的な処理なので、よりテストされた高速なものに入れ替えることも可能かと思います。

### 空の配列

空集合との直積は空集合になります。

[code]

    // 3番目の要素が空の配列:
    const arr = [
        [ [1, 2], ["A", "b"], [] ],
    ].reduce(expand([false, true, true]), [])
    console.log( arr )
    // 出力は空の配列に:
    // []
    
[/code]

`targets` で `true` と指定されている（「展開」の対象となる）位置の要素に空の配列が来るケースについて考慮していない場合は注意して下さい。


CC BY-SA 4.0
2018-09-17T06:20:38.157
48476
2018-09-17T06:20:38.157
3054
48450
answer