Удаление элементов с помощью Array.map в JavaScript

90

Я хотел бы отфильтровать массив элементов с помощью map()функции. Вот фрагмент кода:

var filteredItems = items.map(function(item)
{
    if( ...some condition... )
    {
        return item;
    }
});

Проблема в том, что отфильтрованные элементы по-прежнему используют пространство в массиве, и я хотел бы полностью их стереть.

Любая идея?

РЕДАКТИРОВАТЬ: Спасибо, я забыл о том filter(), что я хотел на самом деле filter()тогда map().

EDIT2: Спасибо, что указали на это, map()и filter()не реализованы во всех браузерах, хотя мой конкретный код не предназначался для запуска в браузере.

Винсент Роберт
источник
Не могли бы вы пояснить, почему 2 итерации хуже 1? Я имею в виду, 2 * O (n) для меня эквивалентно O (2 * n) ...
Винсент Роберт

Ответы:

105

Вы должны использовать filterметод, а не карту, если вы не хотите изменять элементы в массиве в дополнение к фильтрации.

например.

var filteredItems = items.filter(function(item)
{
    return ...some condition...;
});

[Edit: Конечно, вы всегда sourceArray.filter(...).map(...)можете применить фильтр и изменить]

оллией
источник
3
mapне мутирует
Спасибо,
15
Но вы можете мутировать map.
Crazywako
Будьте осторожны с этим: поскольку JS передает ссылку, когда вы что-то мутируете с помощью map, он изменяет объект, но, как стоит MDN, maps возвращает мутированный массив.
alexOtano
1
Вопрос не задал как фильтровать, задал вопрос как удалить на карте
Dazzle
1
@alexOtano Нет, карта не мутирует и не возвращает измененный массив. Он возвращает новый массив. например,x=[1,2,3];y = x.map(z => z*2);console.log(x,y);
Кайл Бейкер
40

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

Тем не менее, tl; dr заключается в следующем: чтобы выполнить то, что вы просите (фильтрация и отображение в одном вызове функции), вы должны использоватьArray.reduce() .

Однако более читаемый и (что менее важно) обычно значительно более быстрый подход 2 - это просто использовать объединенные вместе фильтр и карту:

[1,2,3].filter(num => num > 2).map(num => num * 2)

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


Вы даете reduce аргумент, который является (обычно анонимной) функцией.

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

Обратите внимание, что в то время как Array.filter () принимает только один аргумент (функцию), Array.reduce () также принимает важный (хотя и необязательный) второй аргумент: начальное значение для 'memo', которое будет передано этой анонимной функции как ее первый аргумент и впоследствии может быть изменен и передан между вызовами функций. (Если он не указан, то «memo» в первом вызове анонимной функции по умолчанию будет первым итератором, а аргумент «итерация» фактически будет вторым значением в массиве)

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

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

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

Для более полного объяснения обратитесь к документации MDN (или к моему сообщению, указанному в начале этого ответа).

Базовый пример вызова Reduce:

let array = [1,2,3];
const initialMemo = [];

array = array.reduce((memo, iteratee) => {
    // if condition is our filter
    if (iteratee > 1) {
        // what happens inside the filter is the map
        memo.push(iteratee * 2); 
    }

    // this return value will be passed in as the 'memo' argument
    // to the next call of this function, and this function will have
    // every element passed into it at some point.
    return memo; 
}, initialMemo)

console.log(array) // [4,6], equivalent to [(2 * 2), (3 * 2)]

более емкая версия:

[1,2,3].reduce((memo, value) => value > 1 ? memo.concat(value * 2) : memo, [])

Обратите внимание, что первая итерация была не больше единицы и поэтому была отфильтрована. Также обратите внимание на initialMemo, названный просто для того, чтобы прояснить его существование и привлечь к нему внимание. И снова он передается как «памятка» при первом вызове анонимной функции, а затем возвращаемое значение анонимной функции передается в качестве аргумента «памятка» следующей функции.

Другой пример классического варианта использования memo - это возврат наименьшего или наибольшего числа в массиве. Пример:

[7,4,1,99,57,2,1,100].reduce((memo, val) => memo > val ? memo : val)
// ^this would return the largest number in the list.

Пример того, как написать собственную функцию уменьшения (я нахожу, это часто помогает понять подобные функции):

test_arr = [];

// we accept an anonymous function, and an optional 'initial memo' value.
test_arr.my_reducer = function(reduceFunc, initialMemo) {
    // 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] : initialMemo;

    // 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 = reduceFunc(memo, this[i]);
        // or, more technically complete, give access to base array
        // and index to the reducer as well:
        // memo = reduceFunc(memo, this[i], i, this);
    }

    // after we've compressed the array into a single value,
    // we return it.
    return memo;
}

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

Кайл Бейкер
источник
2
молодец! Я много лет хотел сделать что-то подобное. Решил попробовать придумать красивый и хороший, натуральный javascript!
jemiloii
Другая полезность reduceзаключается в том, что, в отличие от filter+ map, обратному вызову может быть передан аргумент индекса, который является индексом исходного массива, а не отфильтрованного.
congusbongus
@KyleBaker Ссылка на ваше сообщение в блоге ведет на ненайденную страницу. Не могли бы вы обновить ссылку? Благодарность!
Тим Филип
10

Это не то, что делает карта. Вам действительно нужен Array.filter . Или, если вы действительно хотите удалить элементы из исходного списка, вам нужно обязательно сделать это с помощью цикла for.

Патрик
источник
6

Метод фильтра массива

var arr = [1, 2, 3]

// ES5 syntax
arr = arr.filter(function(item){ return item != 3 })

// ES2015 syntax
arr = arr.filter(item => item != 3)

console.log( arr )

vsync
источник
1
вы также можете сделатьvar arr = [1,2,"xxx", "yyy"]; arr = arr.filter(function(e){ return e!="xxx" }) console.log(arr)
jack blank
Вы вернулись через 4 года, чтобы добавить огромный текст? минус один
Спасибо
@ user633183 Кого вы имеете в виду? что за "огромный текст"? Ваш комментарий неясен. Вы уверены, что комментируете нужное место ...?
vsync
2

Однако вы должны отметить, что Array.filterне во всех браузерах поддерживается, поэтому вы должны создать прототип:

//This prototype is provided by the Mozilla foundation and
//is distributed under the MIT license.
//http://www.ibiblio.org/pub/Linux/LICENSES/mit.license

if (!Array.prototype.filter)
{
    Array.prototype.filter = function(fun /*, thisp*/)
    {
        var len = this.length;

        if (typeof fun != "function")
            throw new TypeError();

        var res = new Array();
        var thisp = arguments[1];

        for (var i = 0; i < len; i++)
        {
            if (i in this)
            {
                var val = this[i]; // in case fun mutates this

                if (fun.call(thisp, val, i, this))
                   res.push(val);
            }
        }

        return res;
    };
}

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

ggasp
источник
2
Если вы действительно собираетесь использовать этот метод полифилла, используйте подходящий полифил или, что еще лучше, библиотеку вроде Modernizr . В противном случае вы, скорее всего, столкнетесь с запутанными ошибками в малоизвестных браузерах, о которых вы не заметите, пока они не проработают слишком долго.
Кайл Бейкер
0

следующий оператор очищает объект с помощью функции карты.

var arraytoclean = [{v:65, toberemoved:"gronf"}, {v:12, toberemoved:null}, {v:4}];
arraytoclean.map((x,i)=>x.toberemoved=undefined);
console.dir(arraytoclean);
Николас
источник
0

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

https://gist.github.com/gkucmierz/8ee04544fa842411f7553ef66ac2fcf0

// array intersection that correctly handles also duplicates

const intersection = (a1, a2) => {
  const cnt = new Map();
  a2.map(el => cnt[el] = el in cnt ? cnt[el] + 1 : 1);
  return a1.filter(el => el in cnt && 0 < cnt[el]--);
};

const l = console.log;
l(intersection('1234'.split``, '3456'.split``)); // [ '3', '4' ]
l(intersection('12344'.split``, '3456'.split``)); // [ '3', '4' ]
l(intersection('1234'.split``, '33456'.split``)); // [ '3', '4' ]
l(intersection('12334'.split``, '33456'.split``)); // [ '3', '3', '4' ]

Gkucmierz
источник
0

Сначала вы можете использовать карту, а с цепочкой вы можете использовать фильтр

state.map(item => {
            if(item.id === action.item.id){   
                    return {
                        id : action.item.id,
                        name : item.name,
                        price: item.price,
                        quantity : item.quantity-1
                    }

            }else{
                return item;
            }
        }).filter(item => {
            if(item.quantity <= 0){
                return false;
            }else{
                return true;
            }
        });
Ришаб
источник