Bir yineleyicide map () kullanma


92

Bir Haritamız olduğunu varsayalım :, let m = new Map();kullanmak m.values()bir harita yineleyicisi döndürür.

Ancak bu yineleyiciyi kullanamıyorum forEach()ve map()bu yineleyicide bir while döngüsü uygulamak, ES6 gibi işlevler sunduğu için bir anti-model gibi görünüyor map().

Öyleyse map()bir yineleyicide kullanmanın bir yolu var mı?


Kullanıma hazır değil, ancak Harita'yı lodash mapdestekleyen işlev gibi üçüncü taraf kitaplıklarını da kullanabilirsiniz .
tehlikeli

Haritanın kendisinde, anahtar-değer çiftleri üzerinde yineleme yapmak için bir forEach vardır .
tehlikeli

Yineleyiciyi bir diziye dönüştürmek ve onun üzerinde haritalamak Array.from(m.values()).map(...)işe yarıyor, ancak bunu yapmanın en iyi yolu bu değil.
JiminP

Bir dizi kullanmak için daha uygunken bir yineleyici kullanarak çözmeniz gereken sorun Array#mapnedir?
Nina Scholz

1
@NinaScholz Burada olduğu gibi genel bir set kullanıyorum: stackoverflow.com/a/29783624/4279201
shinzou

Yanıtlar:


84

Bunu yapmanın en basit ve en az performanslı yolu şudur:

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

Daha iyisi

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

Array.fromyinelenebilir veya dizi benzeri herhangi bir şeyi alır ve bir diziye dönüştürür! Daniel'in yorumlarda belirttiği gibi, bir yinelemeyi ve ardından bir ara diziyi kaldırmak için dönüşüme bir eşleme işlevi ekleyebiliriz.

@ Hraban'ın yorumlarda belirttiği gibi kullanmak Array.fromperformansınızı 'dan' O(1)a O(n)taşır. Yana mbir olduğunu Mapve onlar sonsuz olamaz, biz sonsuz dizisi hakkında endişe gerekmez. Çoğu durumda bu yeterli olacaktır.

Bir haritada döngü oluşturmanın birkaç başka yolu vardır.

Kullanma forEach

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

Kullanma 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

Haritaların sonsuz uzunluğu olabilir mi?
ktilcu

2
Bir yineleyici için @ktilcu: evet. Bir yineleyici üzerindeki bir .map, bir yineleyicinin kendisini döndüren, jeneratör üzerindeki bir dönüşüm olarak düşünülebilir. bir öğeyi açmak, alttaki yineleyiciyi çağırır, öğeyi dönüştürür ve onu döndürür.
hraban

8
Bu cevapla ilgili sorun, bir O (1) bellek algoritmasının, daha büyük veri kümeleri için oldukça ciddi olan bir O (n) 'ye dönüştürülebilmesidir. Elbette, sonlu, akışsız yineleyiciler gerektirmenin yanı sıra. Sorunun başlığı "Bir yineleyicide map () kullanma" şeklindedir, tembel ve sonsuz dizilerin sorunun bir parçası olmadığı fikrine katılmıyorum. İnsanlar yineleyicileri tam olarak böyle kullanır. "Harita" yalnızca bir örnekti ("Söyle .."). Bu cevabın iyi yanı çok önemli olan basitliğidir.
hraban

1
@hraban Bu tartışmaya eklediğiniz için teşekkürler. Yanıtı birkaç uyarı içerecek şekilde güncelleyebilirim, böylece gelecekteki gezginler bilgileri ön ve merkezde görebilir. Söz konusu olduğunda, genellikle basit ve optimum performans arasında karar vermek zorunda kalacağız. Genelde performans yerine daha basit (hata ayıklamak, sürdürmek, açıklamak için) yanındayım.
ktilcu

3
@ktilcu Bunun yerine çağrı yapabilirsiniz Array.from(m, ([key,value]) => /* whatever */)(haritalama işlevinin içinde olduğuna dikkat edin from) ve ardından hiçbir ara dizi oluşturulmaz ( kaynak ). Hala O (1) 'den O (n)' ye hareket eder, ancak en azından yineleme ve eşleme yalnızca bir tam yinelemede gerçekleşir.
Daniel

19

Bunun üzerinden geçmek için başka bir yineleyici işlevi tanımlayabilirsiniz:

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

Şimdi sorabilirsiniz: neden Array.frombunun yerine kullanmıyorsunuz ? Bu, tüm yineleyici boyunca çalışacağından, onu (geçici) bir diziye kaydedin, tekrar yineleyin ve ardından eşlemeyi yapın. Liste çok büyükse (veya hatta potansiyel olarak sonsuzsa), bu gereksiz bellek kullanımına yol açacaktır.

Tabii ki, öğeler listesi oldukça küçükse, kullanmak Array.fromfazlasıyla yeterli olmalıdır.


Sonlu bir bellek miktarı sonsuz bir veri yapısını nasıl tutabilir?
shinzou

3
o değil, mesele bu. Bunu kullanarak, bir yineleyici kaynağını bir grup yineleyici dönüşümüne ve son olarak bir tüketici havuzuna zincirleyerek "veri akışları" oluşturabilirsiniz. Örneğin ses işleme, büyük dosyalarla çalışma, veri tabanlarında toplayıcılar vb. İçin
hraban

1
Bu cevabı beğendim. Herkes yinelenebilir öğeler üzerinde Dizi benzeri yöntemler sunan bir kitaplık önerebilir mi?
Joel Malone

1
mapIterator()iterator.return()dönüş değerinin bir sonraki değeri en az bir kez çağrılmadıkça temeldeki yineleyicinin düzgün şekilde kapatılacağını ( çağrılacağını) garanti etmez . Bakınız: repeater.js.org/docs/safety
Jaka Jančar

Neden yineleyici protokolünü sadece a yerine manuel olarak kullanıyorsunuz for .. of .. loop?
2020

11

Bu en basit ve en performanslı yol, Array.frombunu başarmak için ikinci argümanı kullanmaktır :

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

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

Bu yaklaşım, sonsuz olmayan her yinelenebilir için işe yarar. Ayrıca Array.from(map).map(...), yinelenebilirde iki kez yinelenen ve performans için daha kötü olan ayrı bir çağrı kullanmak zorunda kalmaz.


3

Yinelenebilir üzerinde bir yineleyici alabilir, ardından yinelenen her öğede eşleme geri arama işlevini çağıran başka bir yineleyici döndürebilirsiniz.

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

Sen kullanabilirsiniz itiriri bunu uygulayan dizi benzeri Iterables yöntemleri:

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

Güzel! JS'nin API'lerinin bu şekilde yapılması gerekirdi. Her zaman olduğu gibi Rust doğru anlıyor
uçan koyun

Emin ... yineleyici arayüzü için yardımcı işlevlerini her türlü için bir standardizasyon önerisi var "Her zaman olduğu gibi, Pas doğru alır" github.com/tc39/proposal-iterator-helpers içe aktararak, corejs bugün kullanabilirsiniz fromgelen fn "yeni" yineleyiciyi döndüren "core-js-pure / features / iterator".
chpio


1

Birden çok yardımcı işlevi şuraya getiren bir öneri var Iterator: https://github.com/tc39/proposal-iterator-helpers ( render )

Bugün aşağıdakilerden yararlanarak kullanabilirsiniz 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

Buradaki diğer cevaplar ... Garip. Yineleme protokolünün parçalarını yeniden uyguluyor gibi görünüyorlar. Sadece bunu yapabilirsiniz:

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

ve somut bir sonuç istiyorsanız, sadece yayma operatörünü kullanın ....

[...iterMap([1, 2, 3], x => x**2)]
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.