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.

Vincent Robert
źródło
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ć]

olliej
źródło
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.

Kyle Baker
źródło
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.

Patrick
źródło
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 )

vsync
źródło
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ć.

chwytać
źródło
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);
Nicolas
źródło
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' ]

gkucmierz
źródło
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;
            }
        });
Rishab
źródło