Usuwanie elementów za pomocą Array.map w JavaScript


90

Chciałbym przefiltrować tablicę elementów za pomocą map()funkcji. Oto fragment kodu:

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

Problem polega na tym, że odfiltrowane elementy nadal zajmują miejsce w tablicy i chciałbym je całkowicie usunąć.

Dowolny pomysł?

EDIT: Dzięki, zapomniałem o filter()to, co chciałem to właściwie filter()wtedy map().

EDIT2: Dziękuję za zwrócenie uwagi map()i filter()nie są zaimplementowane we wszystkich przeglądarkach, chociaż mój konkretny kod nie był przeznaczony do uruchamiania w przeglądarce.


Czy możesz wyjaśnić, dlaczego 2 iteracje są najgorsze niż 1? Chodzi mi o to, że 2 * O (n) jest dla mnie równoważne O (2 * n) ...
— Vincent Robert

Odpowiedzi:


105

Powinieneś używać filtermetody zamiast mapować, chyba że chcesz zmodyfikować elementy w tablicy, oprócz filtrowania.

na przykład.

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

[Edycja: oczywiście zawsze możesz zrobić, sourceArray.filter(...).map(...)aby zarówno filtrować, jak i mutować]


3
mapnie mutuje
— Dziękuję

14
Ale możesz się mutować map.
— Crazywako

Ostrożnie z tym: ponieważ JS przekazuje referencję, gdy modyfikujesz coś za pomocą mapy, zmieni to obiekt, ale w przypadku MDN mapy zwracają zmutowaną tablicę.
— alexOtano

1
Pytanie nie pytało jak filtrować, pytano jak usunąć na mapie
— Dazzle

1
@alexOtano Nie, mapa nie podlega mutacji i nie zwraca zmutowanej tablicy. Zwraca nową tablicę. np.x=[1,2,3];y = x.map(z => z*2);console.log(x,y);
— Kyle Baker

40

Zainspirowany napisaniem tej odpowiedzi, później rozwinąłem i napisałem post na blogu, szczegółowo omawiając ten temat. Polecam to sprawdzić, jeśli chcesz głębiej zrozumieć, jak myśleć o tym problemie - staram się to wyjaśnić kawałek po kawałku, a na końcu podam porównanie z JSperfem, omawiając kwestie dotyczące szybkości.

To powiedziawszy, tl; dr jest takie: Aby osiągnąć to, o co prosisz (filtrowanie i mapowanie w ramach jednego wywołania funkcji), użyłbyśArray.reduce() .

Jednak bardziej czytelnym i (co mniej ważne) zwykle znacznie szybszym 2 podejściem jest po prostu użycie filtra i mapy połączonych ze sobą:

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

Poniżej znajduje się opis tego, jak Array.reduce()działa i jak można go użyć do wykonania filtrowania i mapowania w jednej iteracji. Ponownie, jeśli jest to zbyt skondensowane, gorąco polecam przeczytanie powyższego wpisu na blogu, który jest znacznie bardziej przyjaznym wprowadzeniem z jasnymi przykładami i postępem.


Podajesz redukuj argument, który jest (zwykle anonimową) funkcją.

Ta funkcja anonimowa przyjmuje dwa parametry - jeden (podobnie jak funkcje anonimowe przekazane do map / filter / forEach) jest iteracją, na której ma być wykonywana operacja. Jest jeszcze jeden argument przemawiający za przekazaniem funkcji anonimowej w celu zmniejszenia jednak faktu, że te funkcje nie akceptują i jest to wartość, która zostanie przekazana pomiędzy wywołaniami funkcji, często nazywana memo .

Zauważ, że chociaż Array.filter () przyjmuje tylko jeden argument (funkcję), Array.reduce () przyjmuje również ważny (choć opcjonalny) drugi argument: wartość początkową dla 'memo', która zostanie przekazana do tej anonimowej funkcji jako jej pierwszy argument, a następnie może być mutowany i przekazywany między wywołaniami funkcji. (Jeśli nie zostanie podany, to „memo” w pierwszym wywołaniu funkcji anonimowej będzie domyślnie pierwszą iteracją, a argument „iteratee” będzie w rzeczywistości drugą wartością w tablicy)

W naszym przypadku przekażemy pustą tablicę, aby rozpocząć, a następnie wybierzemy, czy wstawić naszą iterację do naszej tablicy, czy nie, na podstawie naszej funkcji - to jest proces filtrowania.

Na koniec zwrócimy naszą „tablicę w toku” przy każdym wywołaniu funkcji anonimowej, a reduktor pobierze tę zwróconą wartość i przekaże ją jako argument (zwany memo) do następnego wywołania funkcji.

Pozwala to filtrować i mapować w jednej iteracji, zmniejszając liczbę wymaganych iteracji o połowę - ale po prostu wykonując dwa razy więcej pracy w każdej iteracji, więc nic nie jest tak naprawdę zapisywane poza wywołaniami funkcji, które nie są tak drogie w javascript .

Pełniejsze wyjaśnienie można znaleźć w dokumentach MDN (lub w moim poście, do którego odwołuje się na początku tej odpowiedzi).

Podstawowy przykład połączenia 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)]

bardziej zwięzła wersja:

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

Zauważ, że pierwsza iteracja nie była większa niż jeden, więc została odfiltrowana. Zwróć także uwagę na początkoweMemo, nazwane tylko po to, aby wyjaśnić jego istnienie i zwrócić na nie uwagę. Ponownie jest przekazywana jako „memo” do pierwszego wywołania funkcji anonimowej, a następnie zwracana wartość funkcji anonimowej jest przekazywana jako argument „memo” do następnej funkcji.

Innym przykładem klasycznego przypadku użycia dla memo byłoby zwrócenie najmniejszej lub największej liczby w tablicy. Przykład:

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

Przykład, jak napisać własną funkcję redukuj (uważam, że często pomaga to zrozumieć takie funkcje):

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;
}

Rzeczywista implementacja umożliwia na przykład dostęp do takich elementów, jak indeks, ale mam nadzieję, że pomoże ci to w nieskomplikowanym odczuciu jego istoty.


2
znakomity! Od lat chciałem coś takiego zrobić. Postanowiłem spróbować wymyślić ładny i sposób i wow, naturalny skrypt javascript!
— jemiloii

Inną użytecznością reducejest to, że w przeciwieństwie do filter+ map, do wywołania zwrotnego można przekazać argument indeksu, który jest indeksem oryginalnej tablicy, a nie przefiltrowanej.
— congusbongus

@KyleBaker Odsyłacz do Twojego posta na blogu prowadzi do strony, której nie znaleziono. Czy możesz zaktualizować link? Dzięki!
— Tim Philip

10

To nie jest to, co robi mapa. Naprawdę chcesz Array.filter . Lub jeśli naprawdę chcesz usunąć elementy z oryginalnej listy, będziesz musiał zrobić to bezwzględnie za pomocą pętli for.


6

Metoda filtru tablicy

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
możesz też zrobićvar arr = [1,2,"xxx", "yyy"]; arr = arr.filter(function(e){ return e!="xxx" }) console.log(arr)
— pusty jack

Wróciłeś 4 lata później, aby dodać obszerny tekst? minus jeden
— Dziękuję

@ user633183 Kogo polecasz? jaki „wielki tekst”? Twój komentarz jest niejasny. Czy na pewno komentujesz we właściwym miejscu ...?
— vsync,

2

Musisz jednak pamiętać, że Array.filternie jest obsługiwany we wszystkich przeglądarkach, więc musisz utworzyć prototyp:

//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;
    };
}

Robiąc to, możesz prototypować dowolną metodę, której możesz potrzebować.


2
Jeśli naprawdę zamierzasz wykonać polyfill tą metodą, użyj odpowiedniego polyfill lub jeszcze lepiej biblioteki takiej jak Modernizr . W przeciwnym razie prawdopodobnie napotkasz mylące błędy w mało znanych przeglądarkach, z których nie zdasz sobie sprawy, dopóki nie będą one zbyt długo w produkcji.
— Kyle Baker

0

Poniższa instrukcja czyści obiekt za pomocą funkcji map.

var arraytoclean = [{v:65, toberemoved:"gronf"}, {v:12, toberemoved:null}, {v:4}];
arraytoclean.map((x,i)=>x.toberemoved=undefined);
console.dir(arraytoclean);

0

Właśnie napisałem przecięcie tablicy, które poprawnie obsługuje również duplikaty

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

Najpierw możesz użyć mapy, a przy łańcuchach możesz użyć filtra

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;
            }
        });
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.