การใช้ map () บนตัววนซ้ำ


92

สมมติว่าเรามีแผนที่: let m = new Map();โดยใช้m.values()คืนค่าตัววนซ้ำแผนที่

แต่ฉันไม่สามารถใช้forEach()หรือmap()บน iterator ที่และการดำเนินการในขณะที่ห่วงว่า iterator ดูเหมือนว่าต่อต้านรูปแบบฟังก์ชั่นตั้งแต่ ES6 map()เสนอเช่น

มีวิธีใช้map()กับ iterator หรือไม่?


ไม่ได้อยู่นอกกรอบ แต่คุณสามารถใช้ไลบรารีของบุคคลที่สามเช่นlodash mapฟังก์ชันที่รองรับแผนที่ได้เช่นกัน
อันตราย

แผนที่เองมีforEachสำหรับการวนซ้ำคู่คีย์ - ค่า
อันตราย

การแปลงตัววนซ้ำเป็นอาร์เรย์และแมปมันเหมือนได้Array.from(m.values()).map(...)ผล แต่ฉันคิดว่ามันไม่ใช่วิธีที่ดีที่สุดในการทำเช่นนี้
JiminP

ซึ่งปัญหาที่เกิดขึ้นเช่นเดียวกับคุณที่จะแก้ปัญหาด้วยการใช้ iterator ขณะอาร์เรย์จะพอดีกับที่ดีกว่าสำหรับการใช้Array#map?
Nina Scholz

1
@NinaScholz ฉันใช้ชุดทั่วไปเช่นที่นี่stackoverflow.com/a/29783624/4279201
shinzou

คำตอบ:


84

วิธีที่ง่ายที่สุดและมีประสิทธิภาพน้อยที่สุดคือ:

Array.from(m).map(([key,value]) => /* whatever */)

ยังดีกว่า

Array.from(m, ([key, value]) => /* whatever */))

Array.fromนำสิ่งที่ทำซ้ำได้หรือคล้ายอาร์เรย์และแปลงเป็นอาร์เรย์! ดังที่ Daniel ชี้ให้เห็นในความคิดเห็นเราสามารถเพิ่มฟังก์ชันการทำแผนที่ลงในการแปลงเพื่อลบการวนซ้ำและต่อมาเป็นอาร์เรย์กลาง

การใช้Array.fromจะย้ายประสิทธิภาพของคุณจากO(1)ไปO(n)เป็นที่ @hraban ชี้ให้เห็นในความคิดเห็น เนื่องจากmเป็น a Mapและไม่สามารถเป็นอนันต์ได้เราไม่ต้องกังวลเกี่ยวกับลำดับที่ไม่สิ้นสุด สำหรับกรณีส่วนใหญ่สิ่งนี้จะเพียงพอ

มีอีกสองสามวิธีในการวนรอบแผนที่

การใช้ forEach

m.forEach((value,key) => /* stuff */ )

การใช้ for..of

var myMap = new Map();
myMap.set(0, 'zero');
myMap.set(1, 'one');
for (var [key, value] of myMap) {
  console.log(key + ' = ' + value);
}
// 0 = zero
// 1 = one

แผนที่มีความยาวไม่สิ้นสุดได้หรือไม่?
ktilcu

2
@ktilcu สำหรับตัววนซ้ำ: ใช่ แผนที่บนตัววนซ้ำสามารถคิดได้ว่าเป็นการแปลงร่างบนเครื่องกำเนิดไฟฟ้าซึ่งส่งคืนตัวทำซ้ำเอง การเปิดองค์ประกอบหนึ่งเรียกตัววนซ้ำพื้นฐานเปลี่ยนองค์ประกอบและส่งคืนสิ่งนั้น
hraban

8
ปัญหาของคำตอบนี้คือการเปลี่ยนอัลกอริทึมหน่วยความจำ O (1) ให้เป็น O (n) ซึ่งค่อนข้างร้ายแรงสำหรับชุดข้อมูลขนาดใหญ่ นอกเหนือจากแน่นอนว่าต้องมีตัววนซ้ำแบบ จำกัด และไม่สามารถสตรีมได้ ชื่อคำถามคือ "การใช้ map () บนตัววนซ้ำ" ฉันไม่เห็นด้วยว่าลำดับที่ขี้เกียจและไม่สิ้นสุดไม่ได้เป็นส่วนหนึ่งของคำถาม นั่นเป็นวิธีที่ผู้คนใช้ตัวทำซ้ำ "แผนที่" เป็นเพียงตัวอย่างเท่านั้น ("พูด .. ") สิ่งที่ดีเกี่ยวกับคำตอบนี้คือความเรียบง่ายซึ่งสำคัญมาก
hraban

1
@hraban ขอบคุณที่เพิ่มเข้ามาในการสนทนานี้ ฉันสามารถอัปเดตคำตอบเพื่อรวมข้อควรระวังบางประการเพื่อให้นักท่องเที่ยวในอนาคตมีข้อมูลอยู่ตรงกลาง เมื่อพูดถึงเรื่องนี้เรามักจะต้องตัดสินใจระหว่างประสิทธิภาพที่เรียบง่ายและประสิทธิภาพที่ดีที่สุด ฉันมักจะเข้าข้างที่ง่ายกว่า (เพื่อแก้จุดบกพร่องบำรุงรักษาอธิบาย) มากกว่าประสิทธิภาพ
ktilcu

3
@ktilcu คุณสามารถโทรแทนได้Array.from(m, ([key,value]) => /* whatever */)(สังเกตว่าฟังก์ชันการแมปอยู่ภายในfrom) จากนั้นจะไม่มีการสร้างอาร์เรย์กลาง (ที่มา ) มันยังคงย้ายจาก O (1) ไปยัง O (n) แต่อย่างน้อยการทำซ้ำและการแมปจะเกิดขึ้นในการทำซ้ำทั้งหมดเพียงครั้งเดียว
Daniel

19

คุณสามารถกำหนดฟังก์ชันวนซ้ำอื่นเพื่อวนซ้ำสิ่งนี้:

function* generator() {
    for (let i = 0; i < 10; i++) {
        console.log(i);
        yield i;
    }
}

function* mapIterator(iterator, mapping) {
    for (let i of iterator) {
        yield mapping(i);
    }
}

let values = generator();
let mapped = mapIterator(values, (i) => {
    let result = i*2;
    console.log(`x2 = ${result}`);
    return result;
});

console.log('The values will be generated right now.');
console.log(Array.from(mapped).join(','));

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

แน่นอนว่าหากรายการมีขนาดเล็กพอสมควรการใช้Array.fromควรจะเพียงพอ


หน่วยความจำจำนวน จำกัด จะมีโครงสร้างข้อมูลที่ไม่สิ้นสุดได้อย่างไร?
shinzou

3
ไม่ใช่นั่นคือประเด็น การใช้สิ่งนี้ทำให้คุณสามารถสร้าง "สตรีมข้อมูล" ได้โดยการเชื่อมโยงแหล่งที่มาของตัววนซ้ำกับการแปลงตัววนซ้ำจำนวนมากและในที่สุดก็เป็นอ่างของผู้บริโภค เช่นการประมวลผลเสียงการสตรีมการทำงานกับไฟล์ขนาดใหญ่ตัวรวบรวมบนฐานข้อมูล ฯลฯ
hraban

1
ฉันชอบคำตอบนี้ ใครช่วยแนะนำไลบรารีที่มีวิธีการเหมือน Array บน iterables ได้ไหม
Joel Malone

1
mapIterator()ไม่รับประกันว่าตัววนซ้ำที่ซ่อนอยู่จะถูกปิดอย่างถูกต้อง ( iterator.return()เรียกว่า) เว้นแต่จะมีการเรียกค่าถัดไปของค่าส่งคืนอย่างน้อยหนึ่งครั้ง ดู: repeater.js.org/docs/safety
Jaka Jančar

เหตุใดคุณจึงใช้โปรโตคอลวนซ้ำด้วยตนเองแทนที่จะใช้เพียง a for .. of .. loop?
cowlicks

11

วิธีที่ง่ายและมีประสิทธิภาพมากที่สุดนี้คือการใช้อาร์กิวเมนต์ที่สองเพื่อArray.fromให้บรรลุสิ่งนี้:

const map = new Map()
map.set('a', 1)
map.set('b', 2)

Array.from(map, ([key, value]) => `${key}:${value}`)
// ['a:1', 'b:2']

วิธีนี้ใช้ได้กับการทำซ้ำที่ไม่สิ้นสุด และหลีกเลี่ยงการใช้การเรียกแยกต่างหากArray.from(map).map(...)ซึ่งจะทำให้เกิดการย้ำซ้ำได้สองครั้งและจะแย่ลงสำหรับประสิทธิภาพ


3

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

const map = (iterable, callback) => {
  return {
    [Symbol.iterator]() {
      const iterator = iterable[Symbol.iterator]();
      return {
        next() {
          const r = iterator.next();
          if (r.done)
            return r;
          else {
            return {
              value: callback(r.value),
              done: false,
            };
          }
        }
      }
    }
  }
};

// Arrays are iterable
console.log(...map([0, 1, 2, 3, 4], (num) => 2 * num)); // 0 2 4 6 8

2

คุณสามารถใช้itiririที่ใช้วิธีการเหมือนอาร์เรย์สำหรับ iterables:

import { query } from 'itiriri';

let m = new Map();
// set map ...

query(m).filter([k, v] => k < 10).forEach([k, v] => console.log(v));
let arr = query(m.values()).map(v => v * 10).toArray();

ดี! นี่คือวิธีการทำ API ของ JS เช่นเคย Rust ทำให้ถูกต้อง: doc.rust-lang.org/std/iter/trait.Iterator.html
แกะบิน

"เช่นเคย Rust ทำให้ถูกต้อง" แน่นอน ... มีข้อเสนอมาตรฐานสำหรับฟังก์ชันตัวช่วยทุกประเภทสำหรับอินเทอร์เฟซตัววนซ้ำgithub.com/tc39/proposal-iterator-helpersคุณสามารถใช้งานได้ในวันนี้กับ corejs โดยการนำเข้าfromfn จาก "core-js-pure / features / iterator" ซึ่งส่งคืนตัววนซ้ำ "ใหม่"
chpio

2

ดูที่https://www.npmjs.com/package/fluent-iterable

ทำงานร่วมกับ iterables ทั้งหมด (แผนที่ฟังก์ชันเครื่องกำเนิดไฟฟ้าอาร์เรย์) และการวนซ้ำแบบไม่ตรงกัน

const map = new Map();
...
console.log(fluent(map).filter(..).map(..));

1

มีข้อเสนอที่นำฟังก์ชั่นตัวช่วยหลายตัวไปที่Iterator: https://github.com/tc39/proposal-iterator-helpers ( แสดงผล )

คุณสามารถใช้งานได้แล้ววันนี้โดยใช้core-js:

import { from as iterFrom } from "core-js-pure/features/iterator";

// or if it's working for you:
// import iterFrom from "core-js-pure/features/iterator/from";

let m = new Map();

m.set("13", 37);
m.set("42", 42);

const arr = iterFrom(m.values())
  .map((val) => val * 2)
  .toArray();

// prints "[74, 84]"
console.log(arr);

0

คำตอบอื่น ๆ ที่นี่คือ ... ดูเหมือนว่าพวกเขาจะนำส่วนต่างๆของโปรโตคอลการทำซ้ำมาใช้ใหม่ คุณสามารถทำได้:

function* mapIter(iterable, callback) {
  for (let x of iterable) {
    yield callback(x);
  }
}

และหากคุณต้องการผลลัพธ์ที่เป็นรูปธรรมให้ใช้ตัวดำเนินการสเปร...

[...iterMap([1, 2, 3], x => x**2)]
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.