Разделить массив на два разных массива с помощью функционального JavaScript

мне было интересно, что было бы лучшим способом разделить массив на два разных массива с помощью JavaScript, но сохранить его в областях функционального программирования.

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

const arr = ['horse', 'elephant', 'dog', 'crocodile', 'cat'];

Я думал о разных методы:

фильтр:

const lessThanFour = arr.filter((animal) => {
    return animal.length < 4;
});
const fourAndMore = arr.filter((animal) => {
    return animal.length >= 4;
});

проблема с этим для меня заключается в том, что вам нужно дважды просмотреть свои данные, но это очень читаемо. Будет ли массовое воздействие делать это дважды, если у вас довольно большой массив?

уменьшить:

const threeFourArr = arr.reduce((animArr, animal) => {
  if (animal.length < 4) {
    return [[...animArr[0], animal], animArr[1]];
  } else {
    return  [animArr[0], [...animArr[1], animal]];
  }
}, [[], []]);

где индекс 0 массива содержит массив менее четырех, а индекс 1 содержит массив более трех.

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

мне удалось посмотреть на подобные вопросы в интернете, а также переполнение стека, но многие из них нарушают идея неизменяемости с помощью push() или у них очень нечитабельный реализации, что на мой взгляд нарушает выразительность функционального программирования.

есть ли другие способы это сделать? (функционально, конечно)

5 ответов


collateBy

я только что поделился аналогичный ответ тут

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

обратите внимание, как мы ничего не говорим о animal.length или < 4 или animals[0].push внутри collateBy. Эта процедура не знает вид данных, которые вы могли бы сопоставлять.

// generic collation procedure
const collateBy = f => g => xs => {
  return xs.reduce((m,x) => {
    let v = f(x)
    return m.set(v, g(m.get(v), x))
  }, new Map())
}

// custom collator
const collateByStrLen4 =
  // collate by length > 4 using array concatenation for like elements
  // note i'm using `[]` as the "seed" value for the empty collation
  collateBy (x=> x.length > 4) ((a=[],b)=> [...a,b])

// sample data
const arr = ['horse','elephant','dog','crocodile','cat']

// get collation
let collation = collateByStrLen4 (arr)

// output specific collation keys
console.log('greater than 4', collation.get(true))
console.log('not greater than 4', collation.get(false))

// output entire collation
console.log('all entries', Array.from(collation.entries()))

проверить, что другой ответ я опубликовал чтобы увидеть другие варианты использования. Это довольно удобная процедура.


bifilter

это еще одно решение, которое захватывает оба выхода функции фильтра, а не отбрасывает отфильтрованные значения, такие как Array.prototype.filter делает.

это в основном то, что ваш reduce реализация делает, но она абстрагируется в общий, параметризованный процедура. Это не использовать Array.prototype.push но в теле закрытия локализованная мутация обычно принимается как OK.

const bifilter = (f,xs) => {
  return xs.reduce(([T,F], x, i, arr)=> {
    if (f(x, i, arr) === false)
      return [T, [...F,x]]
    else
      return [[...T,x] ,F]
  }, [[],[]])
}

const arr = ['horse','elephant','dog','crocodile','cat']

let [truthy,falsy] = bifilter(x=> x.length > 4, arr)
console.log('greater than 4', truthy)
console.log('not greater than 4', falsy)

хотя это может быть немного более простым, это не так сильно, как collateBy. В любом случае, выберите тот, который вам нравится, адаптировать его для удовлетворения ваших потребностей, если это необходимо, и получайте удовольствие !


если это ваше собственное приложение, сходите с ума и добавьте его в Array.prototype

// attach to Array.prototype if this is your own app
// do NOT do this if this is part of a lib that others will inherit
Array.prototype.bifilter = function(f) {
  return bifilter(f,this)
}

функция, которую вы пытаетесь построить, обычно известный как partition и можно найти под этим именем во многих библиотеках, например подчеркивания.js. (Насколько я знаю, это не встроенный метод)

var threeFourArr = _.partition(animals, function(x){ return x.length < 4 });

мне это не очень нравится, потому что кажется, что структура данных даст немного проблем, видя, что это массив массивов

Ну, это единственный способ иметь функцию в Javascript, которая возвращает два различные ценности. Это выглядит немного лучше, если вы можете использовать реструктуризующее присваивание (функция ES6):

var [smalls, bigs] = _.partition(animals, function(x){ return x.length < 4 });

посмотрите на него как на возврат пары массивов вместо возврата массива массивов. "Массив массивов" предполагает, что у вас может быть переменное количество массивов.

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

изменчивость не является проблемой, если вы локализуете ее внутри одной функции. Со стороны это так же неизменяемо, как и раньше, и иногда использование некоторой изменчивости будет более идиоматичным, чем попытка сделать все чисто функциональным образом. Если бы мне пришлось кодировать функцию раздела с нуля, я бы написал что-то в этом роде:

function partition(xs, pred){
   var trues = [];
   var falses = [];
   xs.forEach(function(x){
       if(pred(x)){
           trues.push(x);
       }else{
           falses.push(x);
       }
   });
   return [trues, falses];
}

Если вы не против использования подчеркивания, есть аккуратная маленькая функция под названием метода groupBy что делает именно то, что вы ищете:

const arr = ['horse', 'elephant', 'dog', 'crocodile', 'cat'];

var results = _.groupBy(arr, function(cur) {
    return cur.length > 4;
});

const greaterThanFour = results.true;
const lessThanFour = results.false;

console.log(greaterThanFour); // ["horse", "elephant", "crocodile"]
console.log(lessThanFour); // ["dog", "cat"]

короче .reduce() версия будет:

const split = arr.reduce((animArr, animal) => {
  animArr[animal.length < 4 ? 0 : 1].push(animal);
  return animArr
}, [ [], [] ]);

которые могут сочетаться с разрушением:

const [ lessThanFour,  fourAndMore ] = arr.reduce(...)

Я не думаю, что может быть другое решение, чем возврат массива массивов или объекта, содержащего массивы. Как еще функция javascript возвращает несколько массивов после их разделения?

напишите функцию, содержащую вашу логику push для удобства чтения.

var myArr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
var x = split(myArr, v => (v <= 5));
console.log(x);

function split(array, tester) {
  const result = [
    [],
    []
  ];
  array.forEach((v, i, a) => {
    if (tester(v, i, a)) result[0].push(v);
    else result[1].push(v);
  });
  return result;
}