Поділ масиву на функцію фільтра


91

У мене є масив Javascript, який я хотів би розділити на два залежно від того, чи повертається функція, що викликається для кожного елемента, trueабо false. За суті, це array.filter, але я хотів би також мати під рукою ті елементи , які були відфільтровані з .

В даний час я планую використовувати array.forEachі викликати функцію предикат для кожного елемента. Залежно від того, чи це правда чи хибність, я висуну поточний елемент на один із двох нових масивів. Чи є більш елегантний чи інший кращий спосіб це зробити? array.filterДе буде штовхати елемент на інший масив , перш ніж він повертається false, наприклад?


Якщо ви можете опублікувати зразок коду, це допоможе дати вам кращу відповідь!
Mark Pieszak - Trilon.io

Незалежно від того, яку реалізацію ви використовуєте, Javascript завжди повинен буде: перебирати елементи, запускати функцію, штовхати елемент у масив. Я не думаю , що це спосіб зробити це більш ефективним.
TheZ

3
Ви можете робити все, що завгодно, коли передається зворотний виклик, .filterале такі побічні ефекти важко відстежити та зрозуміти. Просто перегляньте масив і натисніть на той чи інший масив.
Фелікс Клінг,

Відповіді:


75

За допомогою ES6 ви можете використовувати синтаксис поширення із зменшенням:

function partition(array, isValid) {
  return array.reduce(([pass, fail], elem) => {
    return isValid(elem) ? [[...pass, elem], fail] : [pass, [...fail, elem]];
  }, [[], []]);
}

const [pass, fail] = partition(myArray, (e) => e > 5);

Або в одному рядку:

const [pass, fail] = a.reduce(([p, f], e) => (e > 5 ? [[...p, e], f] : [p, [...f, e]]), [[], []]);

8
Для мене розділ lodash або просто для кожного було б легше зрозуміти, але навіть так, приємні зусилля
Тоні Лі

15
Це створить два нових масиви для кожного елемента оригіналу. Хоча один масив буде мати лише два елементи, інший зростає із збільшенням розміру масиву. Таким чином, це буде дуже повільно і витратить багато пам'яті. (Ви могли б зробити те саме натисканням, і це було б ефективніше.)
Стюарт Шехтер

Дякую, врятували мій час!
7urkm3n

Це дійсно корисно, браза. Примітка для інших: нижче є кілька більш читабельних версій.
Раффі

38

Ви можете використовувати lodash.partition

var users = [
  { 'user': 'barney',  'age': 36, 'active': false },
  { 'user': 'fred',    'age': 40, 'active': true },
  { 'user': 'pebbles', 'age': 1,  'active': false }
];

_.partition(users, function(o) { return o.active; });
// → objects for [['fred'], ['barney', 'pebbles']]

// The `_.matches` iteratee shorthand.
_.partition(users, { 'age': 1, 'active': false });
// → objects for [['pebbles'], ['barney', 'fred']]

// The `_.matchesProperty` iteratee shorthand.
_.partition(users, ['active', false]);
// → objects for [['barney', 'pebbles'], ['fred']]

// The `_.property` iteratee shorthand.
_.partition(users, 'active');
// → objects for [['fred'], ['barney', 'pebbles']]

або ramda.partition

R.partition(R.contains('s'), ['sss', 'ttt', 'foo', 'bars']);
// => [ [ 'sss', 'bars' ],  [ 'ttt', 'foo' ] ]

R.partition(R.contains('s'), { a: 'sss', b: 'ttt', foo: 'bars' });
// => [ { a: 'sss', foo: 'bars' }, { b: 'ttt' }  ]

16

Ви можете використовувати зменшення для нього:

function partition(array, callback){
  return array.reduce(function(result, element, i) {
    callback(element, i, array) 
      ? result[0].push(element) 
      : result[1].push(element);

        return result;
      }, [[],[]]
    );
 };

Оновлення. Використовуючи синтаксис ES6, ви також можете зробити це за допомогою рекурсії:

function partition([current, ...tail], f, [left, right] = [[], []]) {
    if(current === undefined) {
        return [left, right];
    }
    if(f(current)) {
        return partition(tail, f, [[...left, current], right]);
    }
    return partition(tail, f, [left, [...right, current]]);
}

3
IMHO, першим рішенням (push) є краща продуктивність із збільшенням розміру масиву.
ToolmakerSteve

@ToolmakerSteve, чи не могли б Ви докладніше пояснити, чому? Я також прочитав коментар до
головної

2
@buncis. Перший підхід досліджує кожен елемент один раз, просто підштовхуючи його до відповідного масиву. Другий підхід будує [...left, current]або [...right, current]- для кожного елемента. Я не знаю точних внутрішніх елементів, але я впевнений, що побудова дорожча, ніж просто насування елемента на масив. Крім того, як правило, рекурсія дорожча за ітерацію , оскільки вона передбачає створення "кадрового кадру" кожного разу.
ToolmakerSteve

14

Це дуже схоже на метод РубіEnumerable#partition .

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

З огляду на це, можливо, більш «елегантно» створювати метод Arrayдля виконання цієї функції. У цьому прикладі функція фільтра виконується в контексті вихідного масиву (тобто thisбуде вихідним масивом), і вона отримує елемент та індекс елемента як аргументи (подібно до методу jQueryeach ):

Array.prototype.partition = function (f){
  var matched = [],
      unmatched = [],
      i = 0,
      j = this.length;

  for (; i < j; i++){
    (f.call(this, this[i], i) ? matched : unmatched).push(this[i]);
  }

  return [matched, unmatched];
};

console.log([1, 2, 3, 4, 5].partition(function (n, i){
  return n % 2 == 0;
}));

//=> [ [ 2, 4 ], [ 1, 3, 5 ] ]

11
Для сучасного читача, ПРОСИМО, не додайте методи до глобальних стандартних об'єктів бібліотеки. Це небезпечно і, можливо, буде перезаписано, що призведе до загадкової та розбитої поведінки. Проста стара функція, належним чином застосована, набагато безпечніша, і виклик myFunc (масив) не менш "елегантний", ніж array.myFunc ().
Еммет Р.

14

Я придумав цього маленького хлопця. Він використовує для кожного з усього, що ви описали, але, на мій погляд, він виглядає чистим і лаконічним.

//Partition function
function partition(array, filter) {
  let pass = [], fail = [];
  array.forEach((e, idx, arr) => (filter(e, idx, arr) ? pass : fail).push(e));
  return [pass, fail];
}

//Run it with some dummy data and filter
const [lessThan5, greaterThanEqual5] = partition([0,1,4,3,5,7,9,2,4,6,8,9,0,1,2,4,6], e => e < 5);

//Output
console.log(lessThan5);
console.log(greaterThanEqual5);


1
Це рішення набагато краще, ніж деякі з тих, хто має більше голосів, IMHO. Легко читається, робить один прохід через масив, не розподіляє та не розподіляє масиви результатів розділів. Мені також подобається, що він виставляє всі три загальні значення фільтра до функції фільтра (значення, індекс та весь масив). Це зробить цю функцію набагато більш багаторазовою.
speckledcarp

Цей мені також дуже подобається за його простоту та елегантність. Сказавши це, я виявив, що це значно повільніше, ніж простий forцикл, як у старій відповіді @qwertymk. Наприклад, для масиву, що містить 100 000 елементів, це було вдвічі повільніше в моїй системі.
tromgy

9

У функції фільтра ви можете пересувати свої помилкові елементи в іншу змінну зовні функції:

var bad = [], good = [1,2,3,4,5];
good = good.filter(function (value) { if (value === false) { bad.push(value) } else { return true});

Звичайно value === false має бути справжнє порівняння;)

Але він робить майже ту саму операцію, як forEach. Я думаю, вам слід скористатися forEachдля кращої читабельності коду.


Погоджений із вищезазначеним плакатом ... введення такого роду логіки у функцію фільтру здається трохи роздутим і важким для управління.
theUtherSide

Я думаю, що фільтр має сенс, ви хочете видалити їх з оригінального масиву, тому це фільтр
Мохімі

5

Легко читається.

const partition = (arr, condition) => {
    const trues = arr.filter(el => condition(el));
    const falses = arr.filter(el => !condition(el));
    return [trues, falses];
};

// sample usage
const nums = [1,2,3,4,5,6,7]
const [evens, odds] = partition(nums, (el) => el%2 == 0)

6
Недоліком є ​​те, що ви робите 2 петлі, а не лише одну
Лоран,

прихильність за читабельність, для малого масиву це
зрозуміліше

4

Спробуйте це:

function filter(a, fun) {
    var ret = { good: [], bad: [] };
    for (var i = 0; i < a.length; i++)
        if (fun(a[i])
            ret.good.push(a[i]);
        else
            ret.bad.push(a[i]);
    return ret;
}

ДЕМО


Вказуючи на те, що функція фільтрування масивів насправді не підтримується у всіх браузерах (Я ДИВУ НА ВАС
СТАРШИХ IE

4

Як що до цього?

[1,4,3,5,3,2].reduce( (s, x) => { s[ x > 3 ].push(x); return s;} , {true: [], false:[]} )

Можливо, це ефективніше, ніж оператор розповсюдження

Або трохи коротший, але потворніший

[1,4,3,5,3,2].reduce( (s, x) => s[ x > 3 ].push(x)?s:s , {true: [], false:[]} )


2

Тут використовується багато відповідей Array.prototype.reduce для створення змінного акумулятора і справедливо зазначають, що для великих масивів це ефективніше, ніж, скажімо, використання оператора розширення для копіювання нового масиву на кожній ітерації. Недоліком є ​​те, що він не такий гарний, як "чистий" вираз із використанням короткого лямбда-синтаксису.

Але спосіб обійти це використання оператора коми. У мовах, подібних до С, кома - це оператор, який завжди повертає правий операнд. Ви можете використовувати це для створення виразу, який викликає функцію void і повертає значення.

function partition(array, predicate) {
    return array.reduce((acc, item) => predicate(item)
        ? (acc[0].push(item), acc)
        : (acc[1].push(item), acc), [[], []]);
}

Якщо ви скористаєтесь тим фактом, що логічний вираз неявно передає число 0 і 1, і ви можете зробити його ще більш стислим, хоча я не думаю, що це так читабельно:

function partition(array, predicate) {
    return array.reduce((acc, item) => (acc[+!predicate(item)].push(item), acc), [[], []]);
}

Використання:

const [trues, falses] = partition(['aardvark', 'cat', 'apple'], i => i.startsWith('a'));
console.log(trues); // ['aardvark', 'apple']
console.log(falses); // ['cat']

0

ОДНОЛІНЕВИЙ розділ

const partition = (a,f)=>a.reduce((p,q)=>(p[+!f(q)].push(q),p),[[],[]]);

ДЕМО

// to make it consistent to filter pass index and array as arguments
const partition = (a, f) =>
    a.reduce((p, q, i, ar) => (p[+!f(q, i, ar)].push(q), p), [[], []]);

console.log(partition([1, 2, 3, 4, 5], x => x % 2 === 0));
console.log(partition([..."ABCD"], (x, i) => i % 2 === 0));

Для машинопису

const partition = <T>(
  a: T[],
  f: (v: T, i?: number, ar?: T[]) => boolean
): [T[], T[]] =>
  a.reduce((p, q, i, ar) => (p[+!f(q, i, ar)].push(q), p), [[], []]);

-1

У підсумку я зробив це, тому що це легко зрозуміти (і повністю набрано машинописом).

const partition = <T>(array: T[], isValid: (element: T) => boolean): [T[], T[]] => {
  const pass: T[] = []
  const fail: T[] = []
  array.forEach(element => {
    if (isValid(element)) {
      pass.push(element)
    } else {
      fail.push(element)
    }
  })
  return [pass, fail]
}

// usage
const [pass, fail] = partition([1, 2, 3, 4, 5], (element: number) => element > 3)
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.