แผนที่และกรองอาร์เรย์ในเวลาเดียวกัน


155

ฉันมีอาร์เรย์ของวัตถุที่ฉันต้องการทำซ้ำเพื่อสร้างอาร์เรย์ที่กรองใหม่ แต่ฉันต้องกรองวัตถุบางอย่างออกจากอาร์เรย์ใหม่ตามพารามิเตอร์ ฉันพยายามทำสิ่งนี้:

function renderOptions(options) {
    return options.map(function (option) {
        if (!option.assigned) {
            return (someNewObject);
        }
    });   
}

นั่นเป็นแนวทางที่ดีหรือไม่? มีวิธีที่ดีกว่านี้ไหม? ฉันเปิดให้ใช้ห้องสมุดใด ๆ เช่น lodash


สิ่งที่เกี่ยวกับวิธี "object.keys"? developer.mozilla.org/fr/docs/Web/JavaScript/Reference/…
Julo0sS


3
.reduce()เร็วกว่าการทำตาม.filter(...).map(...)คำแนะนำที่อื่น ๆ ฉันตั้งค่าการทดสอบ JSPerf เพื่อสาธิตstackoverflow.com/a/47877054/2379922
Justin L.

คำตอบ:


219

คุณควรใช้Array.reduceสิ่งนี้

var options = [
  { name: 'One', assigned: true }, 
  { name: 'Two', assigned: false }, 
  { name: 'Three', assigned: true }, 
];

var reduced = options.reduce(function(filtered, option) {
  if (option.assigned) {
     var someNewValue = { name: option.name, newProperty: 'Foo' }
     filtered.push(someNewValue);
  }
  return filtered;
}, []);

document.getElementById('output').innerHTML = JSON.stringify(reduced);
<h1>Only assigned options</h1>
<pre id="output"> </pre>


อีกทางเลือกหนึ่งตัวลดสามารถเป็นฟังก์ชันที่บริสุทธิ์เช่นนี้

var reduced = options.reduce(function(result, option) {
  if (option.assigned) {
    return result.concat({
      name: option.name,
      newProperty: 'Foo'
    });
  }
  return result;
}, []);

2
สำหรับฉันแล้วอาร์กิวเมนต์แรกfilteredคือวัตถุ ดังนั้นfiltered.pushจะไม่ได้กำหนดไว้สำหรับฉัน
Rahmathullah ม

4
ฉันยังมีปัญหากับfilteredการเป็นวัตถุ นี่เป็นเพราะฉันไม่ได้ผ่านใน "ค่าเริ่มต้น" - อาร์เรย์ที่ว่างเปล่า ( []) หลังจากฟังก์ชั่นการลด เช่นvar reduced = options.reduce(function(filtered, option) { ... }); แก้ไขไม่ ถูกต้อง var reduced = options.reduce(function(filtered, option) { ... }, []);
Jon Higgins

ไม่มีเหตุผลสำหรับreduceที่นี่คุณสามารถใช้ได้เช่นกันforEachเนื่องจากในการวนซ้ำทุกครั้งที่คุณเปลี่ยนอาเรfilteredย์ มันจะเป็นเหตุการณ์ที่อ่านได้และกะทัดรัดมากขึ้น
Marko

1
@ มาร์โคฉันแนะนำตัวลดความบริสุทธิ์เช่นกัน PTAL
thefourtheye

เห่านั่นบริสุทธิ์ในขณะนี้ +1 สำหรับความพยายามของคุณ ไม่ใช่ว่าฉันเป็นผู้สนับสนุนการเขียนโปรแกรมที่ใช้งานได้จริงไกลไปกว่านั้นฉันมองไม่เห็นประเด็น :) แต่จริงๆแล้วฉันใช้เคล็ดลับของคุณหลังจากเห็นมันแล้วเพราะมันมีประโยชน์มากในสถานการณ์ที่กำหนด แต่สำหรับงานนี้คุณอาจต้องการดูฟังก์ชั่น flatMap ฉันคิดว่ามันเป็นมาตรฐานหลังจากที่คุณตอบคำถามแล้ว (ด้วยเหตุผลนั้นอาจไม่ได้รับการสนับสนุนจากเบราว์เซอร์บางตัว) ควรมีประสิทธิภาพมากกว่าเนื่องจากการเชื่อมต่ออาร์เรย์เช่นนี้ทำให้งาน O (n ^ 2) ออกจากงาน O (n)
Marko

43

ใช้ลดลุค!

function renderOptions(options) {
    return options.reduce(function (res, option) {
        if (!option.assigned) {
            res.push(someNewObject);
        }
        return res;
    }, []);   
}

30

ตั้งแต่ปี 2019 Array.prototype.flatMapเป็นตัวเลือกที่ดี

options.flatMap(o => o.assigned ? [o.name] : []);

จากหน้า MDN ที่ลิงก์ด้านบน:

flatMapสามารถใช้เป็นวิธีเพิ่มและลบรายการ (แก้ไขจำนวนรายการ) ในระหว่างแผนที่ กล่าวอีกนัยหนึ่งมันช่วยให้คุณแมปหลายรายการกับหลาย ๆ รายการ (โดยจัดการแต่ละรายการอินพุตแยกต่างหาก) แทนที่จะเป็นแบบหนึ่งต่อหนึ่งเสมอ ในแง่นี้มันทำงานเหมือนกับตัวกรองที่ตรงกันข้าม เพียงกลับอาร์เรย์ 1 องค์ประกอบเพื่อเก็บรายการอาร์เรย์หลายองค์ประกอบเพื่อเพิ่มรายการหรืออาร์เรย์ 0 องค์ประกอบเพื่อลบรายการ


5
สุดยอดทางออก! แต่ผู้อ่านทราบว่าflatMapได้รับการแนะนำเมื่อเร็ว ๆ นี้และการสนับสนุนเบราว์เซอร์ของมันจะถูกจำกัด คุณอาจต้องการที่จะใช้อย่างเป็นทางการ polyfill / shims: flatMapและแบน
Arel

24

ด้วย ES6 คุณสามารถทำมันสั้นมาก:

options.filter(opt => !opt.assigned).map(opt => someNewObject)


25
วิธีนี้จะทำสองลูปขึ้นอยู่กับว่าฟิลเตอร์ใดที่ส่งคืนได้ยังสามารถกินหน่วยความจำได้
โฮแกน

1
@hogan แต่นี่เป็นคำตอบที่ถูกต้อง (อาจไม่ใช่คำตอบที่ดีที่สุด) สำหรับคำค้นหาดั้งเดิม
27719

1
@vikramvi ขอบคุณสำหรับบันทึกย่อของคุณ สิ่งนี้คือเราสามารถบรรลุสิ่งเดียวกันได้หลายวิธีและฉันชอบสิ่งที่ดีที่สุด
โฮแกน

10

บรรทัดเดียวที่reduceมีไวยากรณ์การแพร่กระจายแฟนซี ES6 อยู่ที่นี่แล้ว!

var options = [
  { name: 'One', assigned: true }, 
  { name: 'Two', assigned: false }, 
  { name: 'Three', assigned: true }, 
];

const filtered = options
  .reduce((result, {name, assigned}) => [...result, ...assigned ? [name] : []], []);

console.log(filtered);


1
ดีจริงๆ @ Maxim! ฉันโหวตสิ่งนี้! แต่ ... ในการเพิ่มองค์ประกอบแต่ละมันจะต้องกระจายองค์ประกอบทั้งหมดในresult... เป็นเหมือนfilter(assigned).map(name)การแก้ปัญหา
0zkr PM

ทำได้ดีนี่. ฉันใช้เวลาสักครู่ในการตระหนักถึงสิ่งที่เกิดขึ้นด้วย...assigned ? [name] : []- มันอาจอ่านได้ง่ายกว่า...(assigned ? [name] : [])
johansenja

5

ฉันต้องการแสดงความคิดเห็น แต่ฉันไม่มีชื่อเสียงที่ต้องการ การปรับปรุงเล็กน้อยเพื่อคำตอบที่ดีมากของ Maxim Kuzmin เพื่อให้มีประสิทธิภาพมากขึ้น:

const options = [
  { name: 'One', assigned: true }, 
  { name: 'Two', assigned: false }, 
  { name: 'Three', assigned: true }, 
];

const filtered = options
  .reduce((result, { name, assigned }) => assigned ? result.concat(name) : result, []);

console.log(filtered);

คำอธิบาย

แทนที่จะกระจายผลลัพธ์ทั้งหมดซ้ำไปซ้ำมาสำหรับการวนซ้ำแต่ละครั้งเราจะต่อท้ายอาร์เรย์เท่านั้นและเมื่อมีค่าที่จะแทรกจริงเท่านั้น



2

ในบางจุดมันไม่ง่ายกว่า (หรือง่ายเหมือนกัน) ในการใช้ forEach

var options = [
  { name: 'One', assigned: true }, 
  { name: 'Two', assigned: false }, 
  { name: 'Three', assigned: true }, 
];

var reduced = []
options.forEach(function(option) {
  if (option.assigned) {
     var someNewValue = { name: option.name, newProperty: 'Foo' }
     reduced.push(someNewValue);
  }
});

document.getElementById('output').innerHTML = JSON.stringify(reduced);
<h1>Only assigned options</h1>
<pre id="output"> </pre>

อย่างไรก็ตามมันจะดีถ้ามีmalter()หรือfap()ฟังก์ชั่นที่รวมmapและfilterฟังก์ชั่น มันจะทำงานเหมือนตัวกรองยกเว้นแทนที่จะส่งกลับจริงหรือเท็จมันจะกลับวัตถุใด ๆ หรือเป็นโมฆะ / ไม่ได้กำหนด


คุณอาจต้องการตรวจสอบ memes ของคุณ ... ;-)
Arel

2

ฉันเพิ่มประสิทธิภาพคำตอบด้วยประเด็นต่อไปนี้:

  1. เขียนใหม่if (cond) { stmt; }เป็นcond && stmt;
  2. ใช้ฟังก์ชั่นลูกศร ES6

ฉันจะนำเสนอสองวิธีหนึ่งใช้forEachอีกวิธีใช้ลด :

โซลูชันที่ 1: ใช้ forEach

var options = [
  { name: 'One', assigned: true }, 
  { name: 'Two', assigned: false }, 
  { name: 'Three', assigned: true }, 
];
var reduced = []
options.forEach(o => {
  o.assigned && reduced.push( { name: o.name, newProperty: 'Foo' } );
} );
console.log(reduced);

โซลูชันที่ 2: การใช้ลด

var options = [
  { name: 'One', assigned: true }, 
  { name: 'Two', assigned: false }, 
  { name: 'Three', assigned: true }, 
];
var reduced = options.reduce((a, o) => {
  o.assigned && a.push( { name: o.name, newProperty: 'Foo' } );
  return a;
}, [ ] );
console.log(reduced);

ฉันปล่อยให้คุณตัดสินใจเลือกวิธีแก้ปัญหา


1
ทำไมคุณถึงต้องใช้บนโลกcond && stmt;? นี่เป็นเรื่องยากมากที่จะอ่านและไม่มีประโยชน์ใด ๆ เลย
jlh

1

การใช้การลดคุณสามารถทำได้ในฟังก์ชัน Array.prototype นี่จะดึงตัวเลขทั้งหมดจากอาร์เรย์

var arr = [1,2,3,4,5,6,7,8];

var brr = arr.reduce((c, n) => {
  if (n % 2 !== 0) {
    return c;
  }
  c.push(n);
  return c;
}, []);

document.getElementById('mypre').innerHTML = brr.toString();
<h1>Get all even numbers</h1>
<pre id="mypre"> </pre>

คุณสามารถใช้วิธีการเดียวกันและทำให้เป็นวัตถุทั่วไปเช่นนี้

var arr = options.reduce(function(c,n){
  if(somecondition) {return c;}
  c.push(n);
  return c;
}, []);

arr ตอนนี้จะมีวัตถุที่ถูกกรอง


0

การใช้งานโดยตรง.reduceสามารถอ่านได้ยากดังนั้นฉันขอแนะนำให้สร้างฟังก์ชันที่สร้างตัวลดสำหรับคุณ:

function mapfilter(mapper) {
  return (acc, val) => {
    const mapped = mapper(val);
    if (mapped !== false)
      acc.push(mapped);
    return acc;
  };
}

ใช้มันอย่างนั้น:

const words = "Map and filter an array #javascript #arrays";
const tags = words.split(' ')
  .reduce(mapfilter(word => word.startsWith('#') && word.slice(1)), []);
console.log(tags);  // ['javascript', 'arrays'];
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.