การลบองค์ประกอบด้วย Array.map ใน JavaScript


97

ฉันต้องการกรองอาร์เรย์ของรายการโดยใช้map()ฟังก์ชัน นี่คือข้อมูลโค้ด:

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

ปัญหาคือการกรองรายการยังคงใช้พื้นที่ในอาร์เรย์และฉันต้องการล้างออกทั้งหมด

ความคิดใด ๆ ?

แก้ไข: ขอบคุณฉันลืมเกี่ยวกับfilter()สิ่งที่ฉันอยากเป็นจริงแล้วfilter()map()

แก้ไข 2: ขอขอบคุณที่ชี้ว่าmap()และfilter()ไม่มีการนำไปใช้ในทุกเบราว์เซอร์แม้ว่าโค้ดเฉพาะของฉันไม่ได้ตั้งใจให้ทำงานในเบราว์เซอร์


คุณสามารถอธิบายรายละเอียดได้ไหมว่าทำไมการทำซ้ำ 2 ครั้งจึงแย่ที่สุดที่ 1 ฉันหมายถึง 2 * O (n) เทียบเท่ากับ O (2 * n) สำหรับฉัน ...
Vincent Robert

คำตอบ:


109

คุณควรใช้filterวิธีนี้แทนการแมปเว้นแต่ว่าคุณต้องการเปลี่ยนรายการในอาร์เรย์นอกเหนือจากการกรอง

เช่น.

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

[แก้ไข: แน่นอนคุณสามารถทำได้sourceArray.filter(...).map(...)ทั้งตัวกรองและกลายพันธุ์]


3
mapไม่กลายพันธุ์
ขอบคุณ

16
แต่คุณสามารถกลายพันธุ์mapได้
Crazywako

ระวังสิ่งนี้ด้วย: เนื่องจาก JS ส่งข้อมูลอ้างอิงเมื่อคุณกลายพันธุ์บางอย่างด้วยแผนที่มันจะเปลี่ยนวัตถุ แต่เมื่อ MDN ย่อมาแผนที่จะส่งกลับอาร์เรย์ที่กลายพันธุ์
alexOtano

2
คำถามไม่ได้ถามวิธีกรองคำถามถามว่าจะลบบนแผนที่ได้อย่างไร
Dazzle

1
@alexOtano ไม่แผนที่ไม่กลายพันธุ์และไม่ส่งคืนอาร์เรย์ที่กลายพันธุ์ ส่งคืนอาร์เรย์ใหม่ เช่นx=[1,2,3];y = x.map(z => z*2);console.log(x,y);
Kyle Baker

43

แรงบันดาลใจจากการเขียนคำตอบนี้ฉันจึงขยายและเขียนโพสต์บล็อกในภายหลังโดยละเอียด ฉันขอแนะนำให้ตรวจสอบสิ่งนั้นหากคุณต้องการพัฒนาความเข้าใจที่ลึกซึ้งยิ่งขึ้นเกี่ยวกับวิธีคิดเกี่ยวกับปัญหานี้ - ฉันพยายามอธิบายทีละชิ้นและให้เปรียบเทียบ JSperf ในตอนท้ายโดยคำนึงถึงความเร็ว

ที่กล่าวว่าtl; dr คือสิ่งนี้: เพื่อให้บรรลุสิ่งที่คุณต้องการ (การกรองและการทำแผนที่ภายในการเรียกใช้ฟังก์ชันเดียว) คุณจะใช้Array.reduce()ไฟล์.

อย่างไรก็ตามยิ่งอ่านได้มากขึ้น และ (สำคัญน้อยกว่า) มักจะเร็วกว่าอย่างมีนัยสำคัญ2แนวทางคือเพียงใช้ตัวกรองและแผนที่ผูกติดกัน:

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

สิ่งต่อไปนี้คือคำอธิบายวิธีการArray.reduce()ทำงานและวิธีใช้เพื่อกรองและแมปให้สำเร็จในการทำซ้ำ อีกครั้งหากสิ่งนี้ย่อเกินไปฉันขอแนะนำอย่างยิ่งให้ดูบล็อกโพสต์ที่เชื่อมโยงด้านบนซึ่งเป็นบทนำที่เป็นมิตรมากขึ้นพร้อมตัวอย่างที่ชัดเจนและความก้าวหน้า


คุณให้ลดอาร์กิวเมนต์ที่เป็นฟังก์ชัน (โดยปกติจะไม่ระบุชื่อ)

ฟังก์ชันที่ไม่ระบุตัวตนนั้นใช้พารามิเตอร์สองตัว - หนึ่ง (เช่นฟังก์ชันที่ไม่ระบุชื่อที่ส่งผ่านไปยังแผนที่ / ตัวกรอง / forEach) เป็นตัวดำเนินการ มีอาร์กิวเมนต์อื่นสำหรับฟังก์ชันที่ไม่ระบุชื่อที่ส่งผ่านเพื่อลดอย่างไรก็ตามฟังก์ชันเหล่านั้นไม่ยอมรับและนั่นคือค่าที่จะส่งต่อระหว่างการเรียกใช้ฟังก์ชันซึ่งมักเรียกกันว่าบันทึกบันทึกช่วยจำ

โปรดทราบว่าในขณะที่ Array.filter () ใช้เพียงอาร์กิวเมนต์เดียว (ฟังก์ชัน) Array.reduce () ยังใช้อาร์กิวเมนต์ที่สองที่สำคัญ (แม้ว่าจะเป็นทางเลือก) ซึ่งเป็นค่าเริ่มต้นสำหรับ 'บันทึก' ที่จะถูกส่งไปยังฟังก์ชันที่ไม่ระบุชื่อนั้นเป็นของ อาร์กิวเมนต์แรกและต่อมาสามารถกลายพันธุ์และส่งต่อระหว่างการเรียกใช้ฟังก์ชัน (หากไม่ได้ให้มา 'บันทึก' ในการเรียกใช้ฟังก์ชันที่ไม่ระบุชื่อตัวแรกโดยค่าเริ่มต้นจะเป็นตัวทำซ้ำตัวแรกและอาร์กิวเมนต์ 'iteratee' จะเป็นค่าที่สองในอาร์เรย์)

ในกรณีของเราเราจะส่งอาร์เรย์ว่างเปล่าเพื่อเริ่มต้นจากนั้นเลือกว่าจะฉีด iteratee ของเราลงในอาร์เรย์ของเราหรือไม่ตามฟังก์ชันของเรา - นี่คือกระบวนการกรอง

สุดท้ายเราจะส่งคืน 'อาร์เรย์ที่อยู่ระหว่างดำเนินการ' ของเราในการเรียกใช้ฟังก์ชันแบบไม่ระบุตัวตนแต่ละครั้งและการลดจะใช้ค่าส่งคืนนั้นและส่งเป็นอาร์กิวเมนต์ (เรียกว่าบันทึก) ไปยังการเรียกฟังก์ชันถัดไป

สิ่งนี้ช่วยให้ตัวกรองและแผนที่สามารถเกิดขึ้นได้ในการวนซ้ำครั้งเดียวลดจำนวนการทำซ้ำที่ต้องการลงครึ่งหนึ่ง - เพียงแค่ทำงานซ้ำสองครั้งในแต่ละครั้งดังนั้นจึงไม่มีอะไรบันทึกไว้นอกจากการเรียกใช้ฟังก์ชันซึ่งมีราคาไม่แพงนักในจาวาสคริปต์ .

สำหรับคำอธิบายที่สมบูรณ์ยิ่งขึ้นโปรดดูเอกสาร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, [])

สังเกตว่า iteratee แรกไม่มากกว่าหนึ่งและถูกกรอง สังเกต initialMemo ด้วยซึ่งตั้งชื่อเพื่อให้การมีอยู่ของมันชัดเจนและดึงดูดความสนใจไปที่มัน อีกครั้งจะถูกส่งเป็น 'บันทึก' ไปยังการเรียกใช้ฟังก์ชันที่ไม่ระบุชื่อครั้งแรกจากนั้นค่าที่ส่งคืนของฟังก์ชันที่ไม่ระบุชื่อจะถูกส่งเป็นอาร์กิวเมนต์ 'บันทึก' ไปยังฟังก์ชันถัดไป

อีกตัวอย่างหนึ่งของกรณีการใช้งานแบบคลาสสิกสำหรับบันทึกช่วยจำจะส่งคืนตัวเลขที่น้อยที่สุดหรือมากที่สุดในอาร์เรย์ ตัวอย่าง:

[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
แจ่ม! ฉันอยากทำอะไรแบบนี้มาหลายปีแล้ว ตัดสินใจที่จะลองคิดหาวิธีที่ดีและว้าวจาวาสคริปต์ที่เป็นธรรมชาติ!
jemiloii

ประโยชน์อีกประการหนึ่งreduceคือไม่เหมือนกับfilter+ mapการเรียกกลับสามารถส่งผ่านอาร์กิวเมนต์ดัชนีที่เป็นดัชนีของอาร์เรย์ดั้งเดิมไม่ใช่ของที่กรอง
Congusbongus

@KyleBaker ลิงก์ไปยังบล็อกโพสต์ของคุณจะไปยังหน้าที่ไม่พบ รบกวนอัพเดทลิงค์หน่อยได้ไหม ขอบคุณ!
Tim Philip

10

นั่นไม่ใช่สิ่งที่แผนที่ทำ คุณอยากArray.filter หรือถ้าคุณต้องการลบองค์ประกอบออกจากรายการเดิมจริงๆคุณจะต้องทำโดยใช้ for loop


7

วิธีการกรองอาร์เรย์

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
หากคุณจริงๆตั้งใจจะ Polyfill วิธีการนี้โปรดใช้ polyfill ที่เหมาะสมหรือดีกว่ายังห้องสมุดเช่นModernizr มิฉะนั้นคุณอาจพบข้อบกพร่องที่ทำให้สับสนกับเบราว์เซอร์ที่คลุมเครือซึ่งคุณจะไม่รู้ตัวจนกว่าจะใช้งานได้นานเกินไป
Kyle Baker

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


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;
            }
        });
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.