Видалення елементів за допомогою Array.map у JavaScript


90

Я хотів би відфільтрувати масив елементів за допомогою map()функції. Ось фрагмент коду:

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

Проблема в тому, що відфільтровані елементи все ще використовують простір у масиві, і я хотів би повністю їх знищити.

Будь-яка ідея?

EDIT: Дякую, я забув про filter()те, що я хотів насправді filter()тоді map().

EDIT2: Дякуємо, що вказали, що map()і filter()не впроваджені у всіх браузерах, хоча мій конкретний код не був призначений для запуску в браузері.


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

Відповіді:


105

Вам слід використовувати filterметод, а не map, якщо ви не хочете мутувати елементи в масиві, крім фільтрації.

напр.

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

[Редагувати: Звичайно, ви завжди можете зробити sourceArray.filter(...).map(...)як фільтрацію, так і мутацію]


3
mapне мутує
Дякую,

14
Але ви можете мутувати 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(), як це працює, і як його можна використовувати для фільтрації та картографування за одну ітерацію. Знову ж таки, якщо це занадто стисло, я настійно рекомендую переглянути допис у блозі, зв’язаний вище, що є набагато більш дружнім вступом з чіткими прикладами та прогресом.


Ви даєте аргумент зменшення, який є (як правило, анонімною) функцією.

Ця анонімна функція приймає два параметри - один (як-от анонімні функції, передані в map / filter / forEach) є ітератором, яким слід керувати. Існує ще один аргумент для анонімної функції, переданої для зменшення, однак те, що ці функції не приймають, і це значення, яке буде передаватися між викликами функцій, яке часто називають нагадуванням .

Зауважте, що хоча Array.filter () приймає лише один аргумент (функцію), Array.reduce () також приймає важливий (хоча необов’язковий) другий аргумент: початкове значення для „memo”, яке буде передано в цю анонімну функцію як її перший аргумент, а згодом може бути мутованим і передаватися між викликами функцій. (Якщо його не вказано, тоді 'memo' у першому анонімному виклику функції за замовчуванням буде першим ітератором, а аргумент 'iteratee' насправді буде другим значенням у масиві)

У нашому випадку для початку ми передамо порожній масив, а потім виберемо, вводити ітератор у наш масив чи ні, виходячи з нашої функції - це процес фільтрації.

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

Це дозволяє виконувати фільтр і карту за одну ітерацію, зменшуючи кількість необхідних ітерацій вдвічі - однак виконуючи вдвічі більше роботи за кожну ітерацію, отже, насправді нічого не зберігається, крім викликів функцій, які не так дорогі в JavaScript .

Для більш повного пояснення зверніться до документів MDN (або до мого повідомлення, на яке посилається на початку цієї відповіді).

Основний приклад зменшення дзвінка:

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, [])

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

Іншим прикладом класичного варіанту використання для пам'ятки було б повернення найменшого чи найбільшого числа в масиві. Приклад:

[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, зворотний виклик може передаватися аргументу індексу, який є індексом вихідного масиву, а не відфільтрованого.
конгусбонгус

@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 )


1
Ви також можете зробитиvar arr = [1,2,"xxx", "yyy"]; arr = arr.filter(function(e){ return e!="xxx" }) console.log(arr)
джек порожній

Ви повернулися через 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;
    };
}

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


2
Якщо ви дійсно маєте намір заповнити цей метод, будь ласка, скористайтеся відповідним поліфілом або, ще краще, такою бібліотекою, як Modernizr . В іншому випадку, ви, ймовірно, зіткнетесь із заплутаними помилками з незрозумілими браузерами, про які ви не зрозумієте, поки вони не працюють надто довго.
Kyle Baker

0

наступне твердження очищає об'єкт за допомогою функції map.

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' ]


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;
            }
        });
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.