Одночасно картографуйте і фільтруйте масив


155

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

function renderOptions(options) {
    return options.map(function (option) {
        if (!option.assigned) {
            return (someNewObject);
        }
    });   
}

Це хороший підхід? Чи є кращий метод? Я відкритий для використання будь-якої бібліотеки, наприклад лодаш.


як щодо підходу "object.keys"? developer.mozilla.org/fr/docs/Web/JavaScript/Reference/…
Julo0sS

Використовуйте скорочення: developer.mozilla.org/fr/docs/Web/JavaScript/Reference/…
Ів М.

3
.reduce()це, безумовно, швидше, ніж робити те, .filter(...).map(...)що я бачив, запропонований в іншому місці. Я створив тест JSPerf продемонструвати stackoverflow.com/a/47877054/2379922
Justin L.

Відповіді:


219

Ви повинні використовувати Array.reduceдля цього.

var options = [
  { name: 'One', assigned: true }, 
  { name: 'Two', assigned: false }, 
  { name: 'Three', assigned: true }, 
];

var reduced = options.reduce(function(filtered, option) {
  if (option.assigned) {
     var someNewValue = { name: option.name, newProperty: 'Foo' }
     filtered.push(someNewValue);
  }
  return filtered;
}, []);

document.getElementById('output').innerHTML = JSON.stringify(reduced);
<h1>Only assigned options</h1>
<pre id="output"> </pre>


Як варіант, редуктор може бути чистою функцією, як це

var reduced = options.reduce(function(result, option) {
  if (option.assigned) {
    return result.concat({
      name: option.name,
      newProperty: 'Foo'
    });
  }
  return result;
}, []);

2
Для мене перший аргумент filtered- це об’єкт. так filtered.pushце для мене не визначено.
Рахматулла М

4
У мене також виникло питання з filteredтим, щоб бути об’єктом. Це було тому, що я не передавав "початкове значення" - порожній масив ( []) після функції зменшення. наприклад, неправильно var reduced = options.reduce(function(filtered, option) { ... }); правильно var reduced = options.reduce(function(filtered, option) { ... }, []);
Джон Хіггінс,

Тут немає ніяких причин reduce, ви можете так само добре використовувати, forEachоскільки в кожній ітерації ви змінюєте масив, filteredі, таким чином, він не є чистим функціоналом. Це було б подіям читабельнішою та компактнішою.
Марко

1
@Marko Я також представив чистий редуктор. PTAL.
thefourtheye

Зараз це чисто. +1 за ваші зусилля. Не те, що я є чистим прихильником функціонального програмування, далеко не в цьому, я просто не міг бачити сенсу :) Але насправді я використав ваш трюк після того, як побачив його, тому що він став таким зручним у даній ситуації. Але для цього завдання ви, можливо, захочете поглянути на функцію flatMap, я вважаю, що вона стала стандартною після того, як ви дали відповідь (також з цієї причини деякі веб-переглядачі можуть не підтримувати). Він повинен бути більш ефективним, оскільки об'єднання масивів, подібних до цього, робить завдання O (n ^ 2) поза завданням O (n).
Марко

43

Використовуй зменшення, Лука!

function renderOptions(options) {
    return options.reduce(function (res, option) {
        if (!option.assigned) {
            res.push(someNewObject);
        }
        return res;
    }, []);   
}

30

З 2019 року Array.prototype.flatMap - хороший варіант.

options.flatMap(o => o.assigned ? [o.name] : []);

Із вказаної вище сторінки MDN:

flatMapможе використовуватися як спосіб додавання та видалення елементів (змінювати кількість елементів) під час карти. Іншими словами, це дозволяє зіставляти багато елементів на багато елементів (обробляючи кожен елемент введення окремо), а не завжди один на один. У цьому сенсі він працює як протилежний фільтру. Просто поверніть 1-елементний масив для збереження елемента, багатоелементний масив для додавання елементів або 0-елементний масив для видалення елемента.


5
Чудове рішення! Але читачі пам’ятають, що flatMapнещодавно було представлено, і підтримка його браузера обмежена . Можливо, ви хочете використовувати офіційну поліфункцію / прокладки: flatMap та flat .
Арель

24

За допомогою ES6 ви можете зробити це дуже коротко:

options.filter(opt => !opt.assigned).map(opt => someNewObject)


25
Це зробить дві петлі, залежно від того, які повернення фільтрів ще можуть з'їсти пам'ять.
hogan

1
@hogan, але це правильна відповідь (можливо, не оптимальне рішення) на оригінальний запит
vikramvi

1
@vikramvi дякую за вашу замітку. Вся справа в тому, що ми можемо досягти одних і тих же речей тонами способів, і я вважаю за краще найкращий.
hogan

10

Один рядок reduceіз синтаксисом вигадливого поширення ES6 є тут!

var options = [
  { name: 'One', assigned: true }, 
  { name: 'Two', assigned: false }, 
  { name: 'Three', assigned: true }, 
];

const filtered = options
  .reduce((result, {name, assigned}) => [...result, ...assigned ? [name] : []], []);

console.log(filtered);


1
Дуже приємно @Maxim! Я це схвалюю! Але ... на кожному доданому елементі він повинен поширити всі елементи у result... - це як filter(assigned).map(name)рішення
0zkr PM

Хороший. Знадобилося мені секунду, щоб зрозуміти, що відбувається ...assigned ? [name] : []- це може бути читабельніше, як...(assigned ? [name] : [])
johansenja

5

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

const options = [
  { name: 'One', assigned: true }, 
  { name: 'Two', assigned: false }, 
  { name: 'Three', assigned: true }, 
];

const filtered = options
  .reduce((result, { name, assigned }) => assigned ? result.concat(name) : result, []);

console.log(filtered);

Пояснення

Замість того, щоб поширювати весь результат знову і знову для кожної ітерації, ми додаємо до масиву лише тоді, коли насправді потрібно вставити значення.


3

Використовуйте сам Array.prototy.filter

function renderOptions(options) {
    return options.filter(function(option){
        return !option.assigned;
    }).map(function (option) {
        return (someNewObject);
    });   
}

2

У якийсь момент, чи не простіше (або так само просто) використовувати forEach

var options = [
  { name: 'One', assigned: true }, 
  { name: 'Two', assigned: false }, 
  { name: 'Three', assigned: true }, 
];

var reduced = []
options.forEach(function(option) {
  if (option.assigned) {
     var someNewValue = { name: option.name, newProperty: 'Foo' }
     reduced.push(someNewValue);
  }
});

document.getElementById('output').innerHTML = JSON.stringify(reduced);
<h1>Only assigned options</h1>
<pre id="output"> </pre>

Однак було б добре , якби там був malter()або fap()функція , яка поєднує в mapі filterфункції. Він буде працювати як фільтр, за винятком того, що замість того, щоб повертати істинне чи помилкове, він повертав би будь-який об'єкт або null / undefined.


Ви можете перевірити свої меми ... ;-)
Арель

2

Я оптимізував відповіді з наступними пунктами:

  1. Переписування if (cond) { stmt; }якcond && stmt;
  2. Використовуйте функції стрілок ES6

Я представлю два рішення, одне використовує forEach , а друге використовує зменшення :

Рішення 1: Використання forEach

var options = [
  { name: 'One', assigned: true }, 
  { name: 'Two', assigned: false }, 
  { name: 'Three', assigned: true }, 
];
var reduced = []
options.forEach(o => {
  o.assigned && reduced.push( { name: o.name, newProperty: 'Foo' } );
} );
console.log(reduced);

Рішення 2: Використання скорочення

var options = [
  { name: 'One', assigned: true }, 
  { name: 'Two', assigned: false }, 
  { name: 'Three', assigned: true }, 
];
var reduced = options.reduce((a, o) => {
  o.assigned && a.push( { name: o.name, newProperty: 'Foo' } );
  return a;
}, [ ] );
console.log(reduced);

Я залишаю за вами вирішити, на яке рішення йти.


1
Чому б на землі ви не використовували cond && stmt;? Це набагато складніше для читання і не дає ніяких переваг.
jlh

1

Використовуючи зменшення, ви можете зробити це в одній функції Array.prototype. Це отримає всі парні числа з масиву.

var arr = [1,2,3,4,5,6,7,8];

var brr = arr.reduce((c, n) => {
  if (n % 2 !== 0) {
    return c;
  }
  c.push(n);
  return c;
}, []);

document.getElementById('mypre').innerHTML = brr.toString();
<h1>Get all even numbers</h1>
<pre id="mypre"> </pre>

Ви можете використовувати той самий метод і узагальнити його для своїх об'єктів, як цей.

var arr = options.reduce(function(c,n){
  if(somecondition) {return c;}
  c.push(n);
  return c;
}, []);

arr тепер буде містити відфільтровані об'єкти.


0

Безпосереднє використання .reduceможе бути важко читати, тому я рекомендую створити функцію, яка генерує редуктор для вас:

function mapfilter(mapper) {
  return (acc, val) => {
    const mapped = mapper(val);
    if (mapped !== false)
      acc.push(mapped);
    return acc;
  };
}

Використовуйте його так:

const words = "Map and filter an array #javascript #arrays";
const tags = words.split(' ')
  .reduce(mapfilter(word => word.startsWith('#') && word.slice(1)), []);
console.log(tags);  // ['javascript', 'arrays'];
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.