уменьшить массив массивов в массив в упорядоченном порядке

Я пытаюсь использовать reduce() объединения набора массивов в "разборном" порядке, чтобы элементы с одинаковыми индексами были вместе. Например:

input = [["one","two","three"],["uno","dos"],["1","2","3","4"],["first","second","third"]]

output = [ 'first','1','uno','one','second','2','dos','two','third','3','three','4' ]

Неважно, в каком порядке идут элементы с похожим индексом, пока они вместе, поэтому результат 'one','uno','1'... будет хорошим, как и выше. Я хотел бы сделать это просто используя неизменяемые переменные, если это возможно.

У меня есть способ, который работает:

    const output = input.reduce((accumulator, currentArray, arrayIndex)=>{
        currentArray.forEach((item,itemIndex)=>{
            const newIndex = itemIndex*(arrayIndex+1);
            accumulator.splice(newIndex<accumulator.length?newIndex:accumulator.length,0,item);
        })
        return accumulator;
    })

Но это не очень красиво, и мне это не нравится, особенно из-за того, как он мутирует аккумулятор в методе forEach. Я чувствую, что должен быть более элегантный метод.

Я не могу поверить, что никто не спрашивал об этом раньше, но я пробовал кучу разных запросов и не могу найти его, поэтому, пожалуйста, скажите мне, если он есть, и я пропустил его. Есть ли способ лучше?

Чтобы пояснить каждый вопрос в комментариях, я хотел бы иметь возможность делать это без изменения каких-либо переменных или массивов, как я это делаю с accumulator.splice и использовать только функциональные методы, такие как .map или .reduce не мутирующий цикл. как .forEach.

Ответ 1

Я сделал это с рекурсивным подходом, чтобы избежать мутации.

let input = [["one","two","three"],["uno","dos"],["1","2","3","4"],["first","second","third"]]

function recursion(input, idx = 0) {
  let tmp = input.map(elm => elm[idx])
                 .filter(e => e !== undefined)
  return tmp[0] ? tmp.concat(recursion(input, idx + 1)) : []
}

console.log(recursion(input))

Ответ 2

Может быть, просто цикл for... i который проверяет каждый массив на предмет позиции в позиции i

var input = [["one","two","three"],["uno","dos"],["1","2","3","4"],["1st","2nd","3rd"]]

var output = []
var maxLen = Math.max(...input.map(arr => arr.length));

for (i=0; i < maxLen; i++) {
  input.forEach(arr => { if (arr[i] !== undefined) output.push(arr[i]) })
}

console.log(output)

Ответ 3

Используйте Array.from() для создания нового массива с длиной самого длинного подмассива. Чтобы получить длину самого длинного Array.map(), получите массив длин с помощью Array.map() и возьмите элемент max.

В Array.from() используйте Array.reduceRight() или Array.reduce() (в зависимости от желаемого порядка), чтобы собрать элементы из каждого подмассива. Возьмите предмет, если в подмассиве существует текущий индекс. Выровняйте вложенные массивы с помощью Array.flat().

const input = [["one","two","three"],["uno","dos"],["1","2","3","4"],["first","second","third"]]

const result = Array.from(
    { length: Math.max(...input.map(o => o.length)) },
    (_, i) => input.reduceRight((r, o) =>
      i < o.length ? [...r, o[i]] : r
    , [])
  )
  .flat();

console.log(result);

Ответ 4

Вот рекурсивное решение, которое соответствует указанным вами стандартам элегантности:

const head = xs => xs[0];

const tail = xs => xs.slice(1);

const notNull = xs => xs.length > 0;

console.log(collate([ ["one", "two", "three"]
                    , ["uno", "dos"]
                    , ["1", "2", "3", "4"]
                    , ["first", "second", "third"]
                    ]));

function collate(xss) {
    if (xss.length === 0) return [];

    const yss = xss.filter(notNull);

    return yss.map(head).concat(collate(yss.map(tail)));
}

Ответ 5

Смешное решение

  1. добавить индекс в качестве префикса на внутренний массив
  2. Свести массив
  3. сортировать массив
  4. Удалить префикс

let input = [["one","two","three"],["uno","dos"],["1","2","3","4"],["first","second","third"]]
let ranked=input.map(i=>i.map((j,k)=>k+'---'+j)).slice()
console.log(ranked.flat().sort().map(i=>i.split('---')[1]));

Ответ 6

Я видел проблему под названием round-robin, но, возможно, interleave - лучшее название. Конечно, map, reduce и filter являются функциональными процедурами, но не все функциональные программы должны полагаться на них. Когда это единственные функции, которые мы знаем, как использовать, результирующая программа иногда бывает неуклюжей, потому что зачастую она лучше подходит.

  • map дает один-к-одному результат. Если у нас есть 4 подмассива, наш результат будет иметь 4 элемента. interleave должен получиться результат, равный длине объединенных подмассивов, так что map могла бы получить нас только частично. Дополнительные шаги потребуются для получения окончательного результата.

  • reduce итерацию через входные элементы одной за-время для получения конечного результата. При первом сокращении нам дадут первый подмассив, но нет прямого способа обработать весь подмассив перед тем, как перейти к следующему. Мы можем заставить нашу программу, чтобы использовать reduce, но при этом, это заставляет нас думать о процедуре сортировки в качестве процедуры recuding вместо того, что он на самом деле.

Реальность такова, что вы не ограничены использованием этих примитивных функциональных процедур. Вы можете написать interleave способом, который непосредственно кодирует его намерение. Я думаю, что interleave имеет красивое рекурсивное определение. Я думаю, что использование глубокого назначения деструктуризации здесь хорошо, потому что сигнатура функции показывает форму данных, которые ожидает interleave; массив массивов. Математическая индукция позволяет нам естественно обращаться с ветвями нашей программы -

const None =
  Symbol ('None')

const interleave =
  ( [ [ v = None, ...vs ] = []  // first subarray
    , ...rest                   // rest of subarrays
    ]
  ) =>
    v === None
      ? rest.length === 0
        ? vs                                   // base: no 'v', no 'rest'
        : interleave (rest)                    // inductive: some 'rest'
      : [ v, ...interleave ([ ...rest, vs ]) ] // inductive: some 'v', some 'rest'

const input =
  [ [ "one", "two", "three" ]
  , [ "uno", "dos" ]
  , [ "1", "2", "3", "4" ]
  , [ "first", "second", "third" ]
  ]

console.log (interleave (input))
// [ "one", "uno", "1", "first", "two", "dos", "2", "second", "three", "3", "third", "4" ]

Ответ 7

Здесь я предоставил функцию генератора, которая выдаст значения в нужном порядке. Вы можете легко превратить это в обычную функцию, возвращающим массив, если заменить yield с push в массив возвращаемых результатов.

Алгоритм принимает в качестве аргументов все массивы, а затем получает итераторы для каждого из них. Затем он входит в основной цикл, где обрабатывает массив iters как очередь, берет перед собой итератор, возвращает следующее сгенерированное значение и помещает его обратно в конец очереди, если он не пуст. Эффективность повысилась бы, если бы вы преобразовали массив в связанный список, в котором добавление в начало и конец занимает постоянное время, тогда как shift в массиве - это линейное время, чтобы сдвинуть все вниз на одну точку.

function* collate(...arrays) {
  const iters = arrays.map(a => a.values());
  while(iters.length > 0) {
    const iter = iters.shift();
    const {done, value} = iter.next();
    if(done) continue;
    yield value;
    iters.push(iter);
  }
}

Ответ 8

Как насчет этого?

  • Сортируйте массивы, чтобы поместить самый длинный в первую очередь.
  • flatMap их, так что индекс каждого элемента в самом длинном массиве получает каждый индекс любого другого массива в xs.
  • Отфильтровать undefined элементы в плоском массиве (те, которые получены путем получения индексов вне диапазона каждого возможного массива)

const input = [
  ["one", "two", "three"],
  ["uno", "dos"],
  ["1", "2", "3", "4"],
  ["first", "second", "third"]
]

const arrayLengthComparer = (a, b) => b.length - a.length

const collate = xs => {
  const [xs_, ...ys] = xs.sort (arrayLengthComparer)
  
  return xs_.flatMap ((x, i) => [x, ...ys.map (y => y[i])])
            .filter (x => x)
}

const output = collate (input)

console.log (output)
   

Ответ 9

Это то, что я придумал... хотя теперь, увидев другие ответы, это решение кажется более объемным... и все еще использует forEach. Мне интересно услышать выгоду от неиспользования forEach.

var input = [["1a", "2a", "3a"], ["1b"], ["1c", "2c", "3c", "4c", "5c", "6c", "7c"],["one","two","three","four","five","six","seven"],["uno","dos","tres"],["1","2","3","4","5","6","7","8","9","10","11"],["first","second","third","fourth"]];

// sort input array by length
input = input.sort((a, b) => {
  return b.length - a.length;
});

let output = input[0];
let multiplier = 2;

document.writeln(output + "<br>");

input.forEach((arr, i1) => {
  if (i1 > 0) {
    let index = -1;
    arr.forEach((item) => {  
      index = index + multiplier;
      output.splice(index, 0, item);        
    });
    
    document.writeln(output + "<br>");
    multiplier++;
  }
});