Sử dụng map () trên một trình lặp


90

Giả sử chúng ta có Bản đồ: let m = new Map(); , sử dụng m.values()trả về trình lặp bản đồ.

Nhưng tôi không thể sử dụng forEach()hoặcmap() trên trình lặp đó và việc triển khai một vòng lặp while trên trình lặp đó có vẻ giống như một mẫu chống vì ES6 cung cấp các chức năng như map().

Vậy có cách nào để sử dụng map()trên trình lặp không?


Không ngoa, nhưng bạn có thể sử dụng các thư viện của bên thứ ba như lodash mapchức năng hỗ trợ cả Bản đồ.
nguy hiểm

Bản thân bản đồ có forEach để lặp lại các cặp khóa-giá trị của nó.
nguy hiểm

Chuyển đổi trình lặp thành một mảng và ánh xạ trên đó giống như Array.from(m.values()).map(...)hoạt động, nhưng tôi nghĩ đó không phải là cách tốt nhất để làm điều này.
JiminP

bạn phải giải quyết vấn đề nào bằng cách sử dụng trình vòng lặp trong khi một mảng sẽ phù hợp hơn để sử dụng Array#map?
Nina Scholz

1
@NinaScholz Tôi đang sử dụng một tập hợp chung như ở đây: stackoverflow.com/a/29783624/4279201
shinzou

Câu trả lời:


82

Cách đơn giản nhấtít hiệu quả nhất để làm điều này là:

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

Tốt hơn

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

Array.fromlấy bất kỳ thứ nào có thể lặp lại hoặc giống mảng và chuyển đổi nó thành một mảng! Như Daniel đã chỉ ra trong các nhận xét, chúng ta có thể thêm một hàm ánh xạ vào chuyển đổi để loại bỏ một lần lặp và sau đó là một mảng trung gian.

Sử dụng Array.fromsẽ di chuyển hiệu suất của bạn từ O(1)đến O(n)như điểm @hraban ra trong các ý kiến. Kể từ khi mlà mộtMap , và chúng không thể là vô hạn nên chúng ta không phải lo lắng về một chuỗi vô hạn. Đối với hầu hết các trường hợp, điều này là đủ.

Có một số cách khác để xem qua bản đồ.

Sử dụng forEach

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

Sử dụng 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

Bản đồ có thể có độ dài vô hạn không?
ktilcu,

2
@ktilcu cho một trình lặp: vâng. .map trên một trình lặp có thể được coi như một biến đổi trên trình tạo, nó trả về chính một trình lặp. việc bật một phần tử sẽ gọi trình lặp bên dưới, biến đổi phần tử và trả về phần tử đó.
hraban

7
Vấn đề với câu trả lời này là nó biến những gì có thể là một thuật toán bộ nhớ O (1) thành một thuật toán O (n), điều này khá nghiêm trọng đối với các bộ dữ liệu lớn hơn. Tất nhiên, bên cạnh việc yêu cầu các trình vòng lặp hữu hạn, không thể phát trực tiếp. Tiêu đề của câu hỏi là "Sử dụng map () trên trình lặp", tôi không đồng ý rằng chuỗi lười biếng và vô hạn không phải là một phần của câu hỏi. Đó chính xác là cách mọi người sử dụng trình vòng lặp. "Bản đồ" chỉ là một ví dụ ("Nói .."). Điều tốt về câu trả lời này là tính đơn giản của nó, điều này rất quan trọng.
hraban

1
@hraban Cảm ơn bạn đã thêm vào cuộc thảo luận này. Tôi có thể cập nhật câu trả lời để bao gồm một số cảnh báo để những khách du lịch tương lai có thông tin trước và trung tâm. Khi nói đến nó, chúng ta thường sẽ phải đưa ra quyết định giữa hiệu suất đơn giản và tối ưu. Tôi thường sẽ chọn đơn giản hơn (để gỡ lỗi, duy trì, giải thích) hơn hiệu suất.
ktilcu,

3
@ktilcu Thay vào đó, bạn có thể gọi Array.from(m, ([key,value]) => /* whatever */)(lưu ý rằng hàm ánh xạ nằm bên trong from) và sau đó không có mảng trung gian nào được tạo ( nguồn ). Nó vẫn di chuyển từ O (1) đến O (n), nhưng ít nhất sự lặp lại và ánh xạ xảy ra chỉ trong một lần lặp đầy đủ.
Daniel

18

Bạn có thể xác định một hàm lặp khác để lặp lại điều này:

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

function* mapIterator(iterator, mapping) {
    while (true) {
        let result = iterator.next();
        if (result.done) {
            break;
        }
        yield mapping(result.value);
    }
}

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(','));

Bây giờ bạn có thể hỏi: tại sao không chỉ sử dụng Array.fromthay thế? Vì điều này sẽ chạy qua toàn bộ trình lặp, hãy lưu nó vào một mảng (tạm thời), lặp lại nó và sau đó thực hiện ánh xạ. Nếu danh sách rất lớn (hoặc thậm chí có khả năng vô hạn), điều này sẽ dẫn đến việc sử dụng bộ nhớ không cần thiết.

Tất nhiên, nếu danh sách các mặt hàng khá nhỏ, thì việc sử dụng Array.fromphải là quá đủ.


Làm thế nào để một lượng bộ nhớ hữu hạn có thể chứa một cấu trúc dữ liệu vô hạn?
shinzou

3
nó không, đó là vấn đề. Sử dụng điều này, bạn có thể tạo "luồng dữ liệu" bằng cách liên kết nguồn trình vòng lặp với một loạt các phép biến đổi trình vòng lặp và cuối cùng là phần chìm của người tiêu dùng. Ví dụ: xử lý âm thanh phát trực tuyến, làm việc với các tệp lớn, trình tổng hợp trên cơ sở dữ liệu, v.v.
hraban

1
Tôi thích câu trả lời này. Có ai có thể giới thiệu một thư viện cung cấp các phương thức giống Mảng trên các tệp lặp không?
Joel Malone

1
mapIterator()không đảm bảo rằng trình lặp bên dưới sẽ được đóng ( iterator.return()được gọi) đúng cách trừ khi giá trị trả về tiếp theo được gọi ít nhất một lần. Xem: repeatater.js.org/docs/safety
Jaka Jančar

Tại sao bạn lại sử dụng thủ công giao thức trình lặp thay vì chỉ một for .. of .. loop?
cowlicks

11

Cách đơn giản và hiệu quả nhất này là sử dụng đối số thứ hai Array.fromđể đạt được điều này:

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

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

Cách tiếp cận này hoạt động cho bất kỳ khả năng lặp lại không vô hạn . Và nó tránh phải sử dụng một cuộc gọi riêng biệt Array.from(map).map(...)sẽ lặp lại hai lần và sẽ kém hơn đối với hiệu suất.


3

Bạn có thể truy xuất một trình vòng lặp qua trình có thể lặp, sau đó trả về một trình vòng lặp khác gọi hàm gọi lại ánh xạ trên mỗi phần tử được lặp lại.

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

Bạn có thể sử dụng itiriri để triển khai các phương thức giống mảng cho các tệp lặp:

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();

Đẹp! Đây là cách mà các API của JS nên được thực hiện. Như mọi khi, Rust làm đúng: doc.rust-lang.org/std/iter/trait.Iterator.html
cừu bay


0

Các câu trả lời khác ở đây là ... Kỳ lạ. Họ dường như đang thực hiện lại các phần của giao thức lặp lại. Bạn chỉ có thể làm điều này:

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

và nếu bạn muốn có một kết quả cụ thể, chỉ cần sử dụng toán tử spread ... .

[...iterMap([1, 2, 3], x => x**2)]
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.