Вопрос
Я хотел бы отфильтровать массив элементов с помощью функции карты (). Проблема в том, что отфильтрованные элементы все еще используют пространство в массиве, и я хотел бы полностью уничтожить их. Любая идея?
TL; доктор
Если вы хотите выполнить карту () и фильтр () в один цикл, способ сделать снижение (). Array.reduce () Может быть небольшим неразруситесь на первой встрече, поэтому вот мой лучший выстрел на нежном введении.
Уменьшить как концепция
Если вы похожи на меня, вы, возможно, использовали алгоритм Увеличения в своем уме, чтобы помочь вам сделать выбор. Слишком много ресторанов, чтобы выбрать?
[«Lucky Robot», «Рамен Тацуя», «Соль лизать», «Восточные боковые пироги»]
Один из способов справиться с этой проблемой, чтобы просто взять два из них и решить: Какой из них вы бы выбрали между этими двумя?
«Lucky Robot»> «Рамен Тацуя»? «Lucky Robot»: «Рамен Тацуя»
Хорошо, сегодня ты чувствуешь себя суши, так что повезло робот выигрывает 1. Что сейчас?
«Lucky Robot»> «Соль лизать»?
BBQ звучит слишком тяжело сегодня.
«Lucky Robot»> «Восточные боковые пироги»?
О, Snap. Вы знаете, пицца хорошо звучит хорошо.
Сделанный. Мы сократили наш выбор до одного. Теперь мы проделали простое сравнение – что, если мы хотели сравнить в зависимости от стоимости или основаны на том, является ли кто-то в нашей группе веган, или на сложный многофакторный анализ?
Уменьшить, что дает нам обобщенные алгоритмические рамки для сравнения членов группы и действуют на них как группу, сравнивая их один за другим. Мы не сравниваем каждый член с каждым членом – это было бы намного более громоздким, но мы сравниваем каждый член по прокси.
Деконструкция уменьшить ()
Так же, как фильтр карты и т. Д. Уменьшить, это чистая функция, которая не модифицирует массив, на котором он действует, но вместо этого возвращает новый массив.
Вот наша большая перспектива изображения для руководства этого учебника: точка уменьшения () заключается в том, что вы превращаете массив значений в одно конечное значение. Для этого мы возьмем все элементы, оценивая все их и внесите некоторые программические решения на основе того, как эти отдельные значения должны относиться к нашему окончательному значению.
Как это выглядит на практике?
В самой основной и распространенной форме снижение принимает один аргумент:
(Типично анонимная) функция редуктора. (Мы ссылаемся на это как редуктор отсюда.)
Таким образом, вызов для уменьшения, обычно выглядит как эквивалент этого:
let reducedOutput = sourceArray.reduce(reducer);
Теперь нам нужно понять, как написать редуктор.
Помните: как фильтр карты FOREACH и т. Д., Вы не называете уменьшение редуктора.
Как и функция, передаваемая в эти методы, первое, что нужно помнить, заключается в том, что ваш редуктор будет иметь каждый элемент вашего исходного массива, подаваемого в него, по одному за раз.
Итак, теперь мы можем расширить последний блок кода к этому:
reducer = (____, oneElementOfSourceArray) => { // do something with those two things }; reducedOutput = sourceArray.reduce(reducer);
Что этот первый аргумент? Я рад, что вы спрашивали со мной, потому что мы должны описать циклические отношения здесь, что находится в основе того, как уменьшить работу, и почему это сложно понять.
То, что происходит в этом разрыв, это возвращаемое значение из последнего вызова редуктора, который будет результатом прогресса. Это наш текущий проект нашей возможной окончательной ценности, который мы «уменьшаем» массив до – и, если это в последний раз, когда он называется, с окончательным значением массива, то это окончательное значение.
Но откуда он получает этот проект? Вы должны вернуть это. Возвращаемое значение из последнего редуктора подается в качестве аргумента следующего редуктора.
Итак, теперь мы знаем достаточно, чтобы написать это много:
reducer = (lastResultInProgress, oneElementOfSourceArray) => { // do something with those two things return newResultInProgress; }; reducedOutput = sourceArray.reduce(reducer);
Так вот вот, мы видим результингент в двух местах. Возвращаемое значение, NewResultinProgress, подается из каждого вызова редуктора к следующему редуктору вызова как LASTRESULTINPROGRESS.
Первый полный пример
sourceArray = [1,2,3]; // sum will be 6. reducer = (resultInProgress, oneElementOfSourceArray) => { resultInProgress = resultInProgress + oneElementOfSourceArray; return resultInProgress; }; sum = sourceArray.reduce(reducer); console.log(sum === 6); // true
Эта версия намеренно педантично, конечно. Как только вам удобнее уменьшить, вы можете написать это так:
sum = [1,2,3].reduce((m, v) => m + v);
В классическом использовании-сценарию снижения результингенерация начинается в качестве значения при индексе 0 в массиве, а oneelementofSourCearRay в первом вызове редуктора – это значение по индексу 1 в массиве.
Это означает, что редуктор будет вызываться дважды в нашем [1,2,3] приведенном выше примере выше. Впервые аргументы были бы (SourCearray [0], SourCearray [1]), а второй раз, когда мы увидели эквивалент (заполнителю, SourCearray [2]).
Если бы у нас было четвертое значение, следующий звонок будет (заполнителем, SourCearRay [3]).
Тогда мы вернули Placholder в качестве нашей окончательной сокращенной стоимости.
Если вы следите до этого момента, то вы понимаете, уменьшите. Поздравляю!
Из этого следует здесь некоторые детали и иллюстрации, которые приводят нас к тому, как мы можем использовать уменьшение на месте карты + фильтр и помогите нам почувствовать себя лучше, чтобы уменьшить в целом.
Расширенное использование
Осевание наших ценностей
В нашем снижении примеров до сих пор вызывается редуктор, требуется всего, что находится в индексе 0 в качестве запуска RecalityinProgress Value. Тем не менее, мы можем фактически пройти в чем-то еще для семян, что значение, в результате чего элементы SourCearray только когда-либо передаются в качестве второго аргумента для редуктора. Давайте посмотрим на небольшое изменение, которое функционально идентично нашему предыдущему примеру, но использует эту функциональность семян:
sourceArray = [1,2,3]; // sum will be 6. seedResultInProgress = 0; reducer = function(resultInProgress, oneElementOfSourceArray) { resultInProgress += oneElementOfSourceArray; return resultInProgress; }; sum = sourceArray.reduce(reducer, seedResultInProgress); console.log(sum === 6); // true
И короткая версия:
sum = [1,2,3].reduce((resultDraft, element) => resultDraft + element, 0);
Теперь первый вызов редуктора будет иметь 0 и 1, прошедший в качестве аргументов (I.e., SeedResultinProgress и SourCearray [0]) вместо 1 и 2 (то есть SourCearray [0] SourCearRay [1]).
Результат в этом случае идентичен; Уменьшите, что если наш выход будет такой же формат, что и элементы нашего входного массива (массив номеров -> номер), семена не требуется. Но эта способность семени открывает дверь к некоторым другим вариантам, мы скоро дойдем.
Фильтр до одного значения
Мы посмотрели сейчас на сокращении создания новых ценностей из старых ценностей – теперь давайте посмотрим на уменьшение работы в качестве инструмента для фильтрации массива до одного из его значений. Вместо того, чтобы уменьшить до суммы, давайте сократим массив номеров до одного по величинему члену массива.
[1,2,3,30,50,4,5,4].reduce((resultInProgress, elementOfSourceArray) => { if (elementOfSourceArray > resultInProgress) return elementOfSourceArray else return resultInProgress }, 0); // ^this would return the largest number in the list.
(Обратите внимание, что сейчас мы передаем редуктор, что более типично, в отличие от объявления его заранее.)
[7,4,1,99,57,2,1,100].reduce((memo, val) => val > memo ? val : memo);
(Еще один момент обучения: обратите внимание, мы называем первой меморандум аргумента в кратком кодексе. Это канонический термин для этого аргумента, поэтому мы начнем использовать этот термин отсюда на выходе в кратчайшие сроки.)
Фильтр для подмножества
Как только мы посмотрим, давайте посмотрим на уменьшение массива вниз на меньший массив. Допустим, мы хотим отфильтровать все значения, которые менее 3.
Для этого, потому что наш выход будет массивом, но элементы нашего массива – это цифры, нам нужно будет сеять пустой массив для начала:
sourceArray = [1,2,3,4,5]; seed = []; sourceArray.reduce((resultInProgress, elementOfSourceArray) => { if (elementOfSourceArray >= 3) { resultInProgress.push(elementOfSourceArray); } return resultInProgress; // <--always an array }, seed);
(Совет, чтобы понять короткую версию: [1,2] .Concat (3) Возвращает [1,2,3], удобная функция для программирования функционального стиля.)
[1,2,3,4,5].reduce((memo, value) => value >= 3 ? memo.concat(value) : memo, []);
Отвечая на вопрос
Теперь, когда мы знаем, как это фильтровать, как это тривиально легко посмотреть, как мы могли бы включить функциональность карты, мы просто работаем на нем перед точкой () в результате в результате RecostinProgress/Memo.
Для нашего примера мы сделаем тот же фильтр, и если он пройдет наше состояние фильтра, мы умножимся на два.
Просто короткая версия на этот раз:
reduced = [1,2,3,4,5].reduce((memo, value) => value >= 3 ? memo.concat(value * 2) : memo, []); // reduced is [6,8,10]
Шляпа это, теперь вы знаете, как фильтровать и отображаться в одном идет с уменьшением. Быть достаточно комфортно с уменьшением, чтобы почувствовать, что вы могли бы придумать, подобное этому на лету означает, что у вас есть мощный инструмент в вашем арсенале, и может привести к новым способам мышления о проблемах.
Лучший ответ?
Но вы знаете, что? Если бы я хотел как фильтр, так и карту, и это был код, о котором я заботился, я бы просто написал так:
reduced = [1,2,3,4,5] .filter(element => element >= 3) .map(element => element * 2);
Здесь мои намерения намного более очевидны. Этот код может быть воскрешен во взглядах, а имена методов направляют меня в понимании того, что должно происходить.
Уменьшить великолепно и является очень мощным инструментом для определенного класса проблем. Но если вы собираетесь использовать его вне своего основного случая использования, он становится менее очевидным, что вы делаете. Читаемость кода важнее, чем делать все в одном цикле.
А как насчет производительности?
Это также алгоритмически эквивалентно. Мы можем зацикливаться на массиве из пяти элементов один раз, и выполнять две операции на предмет, или мы можем закрутить на массив из пяти элементов дважды и делать на работу на предмет на петлю. Это всего лишь 5 1 2 против 5 2 1
Может быть соблазн думать, что накладные расходы из 5 анонимных вызовов функций VS 10 анонимных вызовов функций сделает здесь разницу здесь. Если JavaScript был ближе к металлу, это может быть так, но нужно помнить, что код JS – это просто предложение переводчика. Фактически, функции, такие как фильтр (), часто можно оптимизировать таким образом, чтобы «ручная» фильтрация в пределах функции уменьшения не может быть.
Из любопытства я решил проверить эту гипотезу в jsperf. Результат?
В этом случае это на три порядка на три порядка не кодируют логику вручную внутри уменьшения. Еще раз, это иллюстрирует принцип, который на основе языков более высокого уровня, такими как js, логика выгрузки в встроенные встроенные, как правило, для получения крючка в базовом языке низкоуровневого уровня ваш код будет скомпилирован в.
Теперь, опять же, если вы не работаете на массивных массивах в клиенте, вы должны беспокоиться с читаемостью на скорости. И если вы сделать случиться с массивными массивами в клиенте? Рассмотрим, может ли это быть более полезным для разгрузки, которая работает на сервере. Нет? Тогда пришло время подумать о скорости.
Сосредоточьтесь на создании кода, который легче читать и проще для записи. Просто так бывает, как правило, он замет ваш код быстрее.
Домашнее задание
Если вы хотите лучше почувствовать, чтобы уменьшить через практику, вот некоторые домашние задания:
Как бы вы написали редуктор для выравнивания массива массивов? Как бы вы написали свой собственный массив. Пример ответов приведен ниже, если вы застряли.
Ответы на домашнее задание
Сглаживать простой вложенный массив:
sourceArray = [ [1,2,3], [4,5,6], [7,8,9] ]; flattenedSource = sourceArray.reduce((memo, val) => memo.concat(...val), []);
Напишите свою собственную функцию уменьшения:
demoArray = [1,2,3,4,5]; // we accept an anonymous function, and an optional 'initial memo' value. demoArray.my_reduce = function(reducer, seedMemo) { // if we did not pass in a second argument, then our first memo value // will be whatever is in index zero. (Otherwise, it will // be that second argument.) const initialMemoIsIndexZero = arguments.length < 2; // here we use that logic to set the memo value accordingly. let memo = initialMemoIsIndexZero ? this[0] : seedMemo; // here we use that same boolean to decide whether the first // value we pass in as iteratee is either the first or second // element const initialIteratee = initialMemoIsIndexZero ? 1 : 0; for (var i = initialIteratee; i < this.length; i++) { // memo is either the argument passed in above, or the // first item in the list. initialIteratee is either the // first item in the list, or the second item in the list. memo = reducer(memo, this[i]); } // after we've compressed the array into a single value, // we return it. return memo; };
Менее Verbose версия:
arr._reduce = function(reducer, seedMemo) { let memo = seedMemo || this[0]; let i = seedMemo ? 0 : 1; for (;i < this.length; i++) { memo = reducer(memo, this[i]); } return memo; };
(Не забудьте не использовать функцию стрелки здесь, чтобы вы могли получить доступ к этому!)
Полная родная реализация позволяет получить доступ к вещам, таким как индекс, например, но я надеюсь, что это помогло вам получить несложное ощущение для того, чтобы суть его. Проверьте MDN для полного API.