Сопоставьте и отфильтруйте массив одновременно

155

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

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

Это хороший подход? Есть ли лучший метод? Я открыт, чтобы использовать любую библиотеку, такую ​​как lodash.

Даниэль Кальдерон Мори
источник
как насчет подхода "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
Джастин Л.

Ответы:

220

Вы должны использовать 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;
}, []);
thefourtheye
источник
2
Для меня первый аргумент filtered- это объект. так filtered.pushне определено для меня.
Рахматулла М
4
У меня также была проблема с тем, filteredчтобы быть объектом. Это было потому, что я не передавал «начальное значение» - пустой массив ( []) после функции Reduce. например, неверно 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
Это сделает два цикла, в зависимости от того, какие возвраты фильтра могут все еще потреблять память.
Хоган
1
@ Хоган, но это правильный ответ (может быть, не является оптимальным решением) на оригинальный запрос
vikramvi
1
@vikramvi спасибо за вашу заметку. Дело в том, что мы можем добиться того же с помощью множества способов, и я бы предпочел лучший.
Хоган
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);

объяснение

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

matsve
источник
3

Используйте сам Array.prototy.filter

function renderOptions(options) {
    return options.filter(function(option){
        return !option.assigned;
    }).map(function (option) {
        return (someNewObject);
    });   
}
AmmarCSE
источник
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функции. Он будет работать как фильтр, за исключением того, что вместо возврата true или false он будет возвращать любой объект или null / undefined.

Даниил
источник
Возможно, вы захотите проверить свои мемы ... ;-)
Arel
2

Я оптимизировал ответы со следующими пунктами:

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

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

Решение 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: Использование Reduce

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
2

Используя Reduce, вы можете сделать это в одной функции 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'];
Quelklef
источник