уменьшить массив массивов в массив в упорядоченном порядке - PullRequest
0 голосов
/ 21 февраля 2019

Я пытаюсь использовать 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.

Ответы [ 9 ]

0 голосов
/ 21 февраля 2019

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

  • Сортировать массивы, чтобы поместить самый длинный из них.
  • 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)
   
0 голосов
/ 21 февраля 2019

Это то, что я придумал ... хотя теперь, увидев другие ответы, это решение кажется более громоздким ... и оно все еще использует 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++;
  }
});
0 голосов
/ 21 февраля 2019

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

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)));
}

Он может быть напрямую переведен в код Haskell:

collate :: [[a]] -> [a]
collate []  = []
collate xss = let yss = filter (not . null) xss
              in map head yss ++ collate (map tail yss)

Предыдущее решение потребовало больших шагов для вычисленияответ.Вот рекурсивное решение, требующее маленьких шагов для вычисления ответа:

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

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

    const [xs_, ...xss] = xss_;

    if (xs_.length === 0) return collate(xss);

    const [x, ...xs] = xs_;

    return [x, ...collate(xss.concat([xs]))];
}

Вот эквивалентный код Haskell:

collate :: [[a]] -> [a]
collate []           = []
collate ([]:xss)     = collate xss
collate ((x:xs):xss) = x : collate (xss ++ [xs])

Надеюсь, что поможет.

0 голосов
/ 21 февраля 2019

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

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

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

Реальность такова, что вы не ограничены использованием этих примитивных функциональных процедур.Вы можете написать 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" ]

interleave освободило нас от оков ограниченного мышления.Мне больше не нужно думать о моей проблеме с точки зрения деформированных частей, которые неловко сочетаются друг с другом - я не имею в виду индексы массивов, или sort, или forEach, или состояние мутации с push, или сравнение с использованием> или Math.max.И при этом я не должен думать о извращенных вещах как как массив - вау, мы действительно принимаем как должное только то, как много мы узнали о JavaScript!

Выше, это должночувствовать себя освежающе, что есть нет зависимостей.Представьте, что новичок подходит к этой программе: ему / ей нужно только изучить 1) как определить функцию, 2) деструктурировать синтаксис, 3) троичные выражения.Программы, объединенные с бесчисленными небольшими зависимостями, потребуют, чтобы учащийся ознакомился с каждой из них до того, как можно будет получить интуицию для программы.

Тем не менее, синтаксисы JavaScript для деструктурирования значений не самые красивые и иногда обмениваются.для удобства сделаны для повышения читабельности -

const interleave = ([ v, ...vs ], acc = []) =>
  v === undefined
    ? acc
: isEmpty (v)
    ? interleave (vs, acc)
: interleave
    ( [ ...vs, tail (v) ]
    , [ ...acc, head (v) ]
    )

Зависимости, которые здесь развивались, isEmpty, tail и head -

const isEmpty = xs =>
  xs.length === 0

const head = ([ x, ...xs ]) =>
  x

const tail = ([ x, ...xs ]) =>
  xs

Функциональность такая же -

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" ]

Проверьте результаты в вашем собственном браузере ниже -

const isEmpty = xs =>
  xs.length === 0
  
const head = ([ x , ...xs ]) =>
  x
  
const tail = ([ x , ...xs ]) =>
  xs
  
const interleave = ([ v, ...vs ], acc = []) =>
  v === undefined
    ? acc
: isEmpty (v)
    ? interleave (vs, acc)
: interleave
    ( [ ...vs, tail (v) ]
    , [ ...acc, head (v) ]
    )

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" ]

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

0 голосов
/ 21 февраля 2019

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

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))
0 голосов
/ 21 февраля 2019

Здесь я предоставил функцию генератора, которая выдаст значения в нужном порядке.Вы можете легко превратить это в обычную функцию, возвращающую массив, если заменить 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);
  }
}
0 голосов
/ 21 февраля 2019

Забавное решение

  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]));
0 голосов
/ 21 февраля 2019

Может быть просто простой цикл 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)

Простой, но предсказуемый и читаемый


Избегание для каждого цикла

Если вам нужно избежать forEach, вотаналогичный подход, в котором вы могли бы: получить максимальную длину дочернего массива , построить диапазон целых чисел , который был бы создан циклом for ([1,2,3,4]), отобразить каждое значениечтобы развернуть массивы, выровнять многомерный массив , а затем filter из пустых ячеек.

Сначала в дискретных шагах, а затем в один слой:

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

Несколько шагов:

var maxLen = Math.max(...input.map(arr => arr.length));
var indexes = Array(maxLen).fill().map((_,i) => i);
var pivoted = indexes.map(i => input.map(arr => arr[i] ));
var flattened = pivoted.flat().filter(el => el !== undefined);

Один вкладыш:

var output = Array(Math.max(...input.map(arr => arr.length))).fill().map((_,i) => i)
               .map(i => input.map(arr => arr[i] ))
               .flat().filter(el => el !== undefined)
0 голосов
/ 21 февраля 2019

Используйте Array.from(), чтобы создать новый массив с длиной самого длинного подмассива.Чтобы получить длину самого длинного подмассива, получите массив длин с помощью 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);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...