Javascript yineleyiciyi diziye dönüştürme


171

En son Firefox ve Chrome sürümlerinde zaten desteklendiği için Javascript EC6'dan yeni Harita nesnesini kullanmaya çalışıyorum .

Ama ben "fonksiyonel" programlamada çok sınırlı buluyorum, çünkü bir [key, value]çift ile güzel çalışır klasik harita, filtre vb yöntemleri yoktur . Bir forEach vardır ama geri arama sonucu döndürmez.

Ben onun dönüşüm olsaydı map.entries()basit bir Array içine MapIterator dan Sonra standardını kullanabilirsiniz .map, .filterhiçbir ek kesmek ile.

Javascript Iterator'ı Diziye dönüştürmenin "iyi" bir yolu var mı? Python'da bunu yapmak kadar kolay list(iterator)... ama Array(m.entries())Iterator ile bir dizi ilk öğesi olarak geri dönüyor !!!

DÜZENLE

En azından Chrome ve Firefox (Chrome'da Array.from çalışmıyor) anlamına gelen, Harita'nın çalıştığı her yerde çalışan bir yanıt aradığımı belirtmeyi unuttum.

PS.

Fantastik wu.js olduğunu biliyorum ama traceur'a olan bağımlılığı beni etkiliyor ...


Yanıtlar:


247

Sen yeni arıyorsun Array.fromRasgele yinelenebilirleri dizi örneklerine dönüştüren işlevi :

var arr = Array.from(map.entries());

Artık Edge, FF, Chrome ve Node 4+ sürümlerinde desteklenmektedir .

Tabii ki, tanımlamaya değer olabilir map, filtersen dizi tahsis önleyebilirsiniz böylece, doğrudan yineleyici arayüzünde benzer yöntemler. Ayrıca, üst düzey işlevler yerine bir jeneratör işlevi kullanmak isteyebilirsiniz:

function* map(iterable) {
    var i = 0;
    for (var item of iterable)
        yield yourTransformation(item, i++);
}
function* filter(iterable) {
    var i = 0;
    for (var item of iterable)
        if (yourPredicate(item, i++))
             yield item;
}

Geri aramanın (value, key)çiftleri değil (value, index)çiftleri almasını beklerim.
Aadit M Shah

3
@AaditMShah: Yinelemenin anahtarı nedir? Tabii ki, bir haritayı tekrarlarsanız, şunu tanımlayabilirsinizyourTransformation = function([key, value], index) { … }
Bergi

Yineleyicinin bir anahtarı yoktur ancak bir Mapanahtarın değer çiftleri vardır. Bu nedenle, alçakgönüllü görüşüme göre, yineleyiciler için genel mapve filterişlevleri tanımlamak mantıklı değildir . Bunun yerine, her yinelenebilir nesnenin kendine ait mapve filterişlevleri olmalıdır. Bu mantıklı çünkü mapve filteryapı (belki de koruyucu işlemler vardır filterama mapkesinlikle) ve dolayısıyla mapve filterişlevleri onlar üzerinde haritalama veya filtreleme olduğunu iterable nesnelerin yapısını bilmeli. Bir düşünün, Haskell'de farklı örnekleri tanımlıyoruz Functor. =)
Aadit M Shah

1
@Stefano: Bunları o şim ... kolayca
Bergi

1
@Incognito Ah tamam, bunun doğru olduğuna eminim, ama sorunun sorusu bu, cevabımla ilgili bir sorun değil.
Bergi

45

[...map.entries()] veya Array.from(map.entries())

Çok kolay.

Her neyse - yineleyiciler azaltma, filtreleme ve benzer yöntemlerden yoksundur. Bunları kendi başınıza yazmalısınız, çünkü Harita'yı diziye ve arkaya dönüştürmekten daha mükemmeldir. Ama atlamayın Harita -> Dizi -> Harita -> Dizi -> Harita -> Dizi, çünkü performansı öldürecek.


1
Daha önemli bir şey yoksa, bu gerçekten bir yorum olmalı. Ayrıca, Array.from@Bergi tarafından zaten ele alınmıştır.
Aadit M Shah

2
Ve orijinal sorumda yazdığım gibi, [iterator]çalışmıyor çünkü Chrome'da içinde tek bir iteratoröğeye sahip bir dizi oluşturur ve [...map.entries()]Chrome'da kabul edilen bir sözdizimi değildir
Stefano

2
@Stefano spread operatörü artık Chrome'da sözdizimi kabul edildi
Klesun

15

A'yı Mapan dönüştürmeye gerek yoktur Array. Nesneler için basitçe oluşturabilir mapve filterişlevleri yapabilirsiniz Map:

function map(functor, object, self) {
    var result = new Map;

    object.forEach(function (value, key, object) {
        result.set(key, functor.call(this, value, key, object));
    }, self);

    return result;
}

function filter(predicate, object, self) {
    var result = new Map;

    object.forEach(function (value, key, object) {
        if (predicate.call(this, value, key, object)) result.set(key, value);
    }, self);

    return result;
}

Örneğin !, anahtarı ilkel olan bir haritanın her girişinin değerine bir patlama (yani karakter) ekleyebilirsiniz .

var object = new Map;

object.set("", "empty string");
object.set(0,  "number zero");
object.set(object, "itself");

var result = map(appendBang, filter(primitive, object));

alert(result.get(""));     // empty string!
alert(result.get(0));      // number zero!
alert(result.get(object)); // undefined

function primitive(value, key) {
    return isPrimitive(key);
}

function appendBang(value) {
    return value + "!";
}

function isPrimitive(value) {
    var type = typeof value;
    return value === null ||
        type !== "object" &&
        type !== "function";
}
<script>
function map(functor, object, self) {
    var result = new Map;

    object.forEach(function (value, key, object) {
        result.set(key, functor.call(this, value, key, object));
    }, self || null);

    return result;
}

function filter(predicate, object, self) {
    var result = new Map;

    object.forEach(function (value, key, object) {
        if (predicate.call(this, value, key, object)) result.set(key, value);
    }, self || null);

    return result;
}
</script>

Ayrıca , daha iyi okunması için yöntemler ekleyebilir mapve ekleyebilirsiniz . Genellikle henüz yerli prototip değiştirmek için tavsiye edilmez rağmen ben bir istisna durumunda yapılabilir inanıyoruz ve için :filterMap.prototypemapfilterMap.prototype

var object = new Map;

object.set("", "empty string");
object.set(0,  "number zero");
object.set(object, "itself");

var result = object.filter(primitive).map(appendBang);

alert(result.get(""));     // empty string!
alert(result.get(0));      // number zero!
alert(result.get(object)); // undefined

function primitive(value, key) {
    return isPrimitive(key);
}

function appendBang(value) {
    return value + "!";
}

function isPrimitive(value) {
    var type = typeof value;
    return value === null ||
        type !== "object" &&
        type !== "function";
}
<script>
Map.prototype.map = function (functor, self) {
    var result = new Map;

    this.forEach(function (value, key, object) {
        result.set(key, functor.call(this, value, key, object));
    }, self || null);

    return result;
};

Map.prototype.filter = function (predicate, self) {
    var result = new Map;

    this.forEach(function (value, key, object) {
        if (predicate.call(this, value, key, object)) result.set(key, value);
    }, self || null);

    return result;
};
</script>


Edit: Bergi'nin cevabında, jenerik mapvefilter Bergi'nin cevabında, tüm tekrarlanabilir nesneler için jeneratör fonksiyonları . Bunları kullanmanın avantajı, jeneratör işlevleri oldukları için ara yinelenebilir nesneler tahsis etmemesidir.

Örneğin, yukarıda tanımlanan işlevlerim mapve filterişlevler yeni Mapnesneler oluşturur. Bu nedenle çağırmak object.filter(primitive).map(appendBang)iki yeni Mapnesne oluşturur :

var intermediate = object.filter(primitive);
var result = intermediate.map(appendBang);

Ara yinelenebilir nesneler oluşturmak pahalıdır. Bergi'nin jeneratör fonksiyonları bu sorunu çözer. Ara nesneler ayırmazlar, ancak bir yineleyicinin değerlerini tembel bir şekilde diğerine beslemesine izin verir. Bu tür bir optimizasyon, işlevsel programlama dillerinde füzyon veya ormansızlaşma olarak bilinir ve program performansını önemli ölçüde artırabilir.

Bergi'nin jeneratör işlevleriyle ilgili tek sorunum, Mapnesnelere özgü olmamasıdır . Bunun yerine, tüm yinelenebilir nesneler için genelleştirilirler. Bu nedenle, geri arama işlevlerini (value, key)çiftler ile çağırmak yerine ( bir eşleme sırasında beklediğim gibi Map), geri arama işlevlerini(value, index) . Aksi takdirde, mükemmel bir çözümdür ve sağladığım çözümler üzerinde kullanmanızı kesinlikle tavsiye ederim.

Yani bunlar eşleme ve Mapnesneleri filtrelemek için kullanacağım özel jeneratör işlevleri :

function * map(functor, entries, self) {
    var that = self || null;

    for (var entry of entries) {
        var key   = entry[0];
        var value = entry[1];

        yield [key, functor.call(that, value, key, entries)];
    }
}

function * filter(predicate, entries, self) {
    var that = self || null;

    for (var entry of entries) {
        var key    = entry[0];
        var value  = entry[1];

        if (predicate.call(that, value, key, entries)) yield [key, value];
    }
}

function toMap(entries) {
    var result = new Map;

    for (var entry of entries) {
        var key   = entry[0];
        var value = entry[1];

        result.set(key, value);
    }

    return result;
}

function toArray(entries) {
    var array = [];

    for (var entry of entries) {
        array.push(entry[1]);
    }

    return array;
}

Bunlar aşağıdaki gibi kullanılabilir:

var object = new Map;

object.set("", "empty string");
object.set(0,  "number zero");
object.set(object, "itself");

var result = toMap(map(appendBang, filter(primitive, object.entries())));

alert(result.get(""));     // empty string!
alert(result.get(0));      // number zero!
alert(result.get(object)); // undefined

var array  = toArray(map(appendBang, filter(primitive, object.entries())));

alert(JSON.stringify(array, null, 4));

function primitive(value, key) {
    return isPrimitive(key);
}

function appendBang(value) {
    return value + "!";
}

function isPrimitive(value) {
    var type = typeof value;
    return value === null ||
        type !== "object" &&
        type !== "function";
}
<script>
function * map(functor, entries, self) {
    var that = self || null;

    for (var entry of entries) {
        var key   = entry[0];
        var value = entry[1];

        yield [key, functor.call(that, value, key, entries)];
    }
}

function * filter(predicate, entries, self) {
    var that = self || null;

    for (var entry of entries) {
        var key    = entry[0];
        var value  = entry[1];

        if (predicate.call(that, value, key, entries)) yield [key, value];
    }
}

function toMap(entries) {
    var result = new Map;

    for (var entry of entries) {
        var key   = entry[0];
        var value = entry[1];

        result.set(key, value);
    }

    return result;
}

function toArray(entries) {
    var array = [];

    for (var entry of entries) {
        array.push(entry[1]);
    }

    return array;
}
</script>

Daha akıcı bir arayüz istiyorsanız, böyle bir şey yapabilirsiniz:

var object = new Map;

object.set("", "empty string");
object.set(0,  "number zero");
object.set(object, "itself");

var result = new MapEntries(object).filter(primitive).map(appendBang).toMap();

alert(result.get(""));     // empty string!
alert(result.get(0));      // number zero!
alert(result.get(object)); // undefined

var array  = new MapEntries(object).filter(primitive).map(appendBang).toArray();

alert(JSON.stringify(array, null, 4));

function primitive(value, key) {
    return isPrimitive(key);
}

function appendBang(value) {
    return value + "!";
}

function isPrimitive(value) {
    var type = typeof value;
    return value === null ||
        type !== "object" &&
        type !== "function";
}
<script>
MapEntries.prototype = {
    constructor: MapEntries,
    map: function (functor, self) {
        return new MapEntries(map(functor, this.entries, self), true);
    },
    filter: function (predicate, self) {
        return new MapEntries(filter(predicate, this.entries, self), true);
    },
    toMap: function () {
        return toMap(this.entries);
    },
    toArray: function () {
        return toArray(this.entries);
    }
};

function MapEntries(map, entries) {
    this.entries = entries ? map : map.entries();
}

function * map(functor, entries, self) {
    var that = self || null;

    for (var entry of entries) {
        var key   = entry[0];
        var value = entry[1];

        yield [key, functor.call(that, value, key, entries)];
    }
}

function * filter(predicate, entries, self) {
    var that = self || null;

    for (var entry of entries) {
        var key    = entry[0];
        var value  = entry[1];

        if (predicate.call(that, value, key, entries)) yield [key, value];
    }
}

function toMap(entries) {
    var result = new Map;

    for (var entry of entries) {
        var key   = entry[0];
        var value = entry[1];

        result.set(key, value);
    }

    return result;
}

function toArray(entries) {
    var array = [];

    for (var entry of entries) {
        array.push(entry[1]);
    }

    return array;
}
</script>

Umarım yardımcı olur.


teşekkürler! @Bergi'ye iyi cevap işareti vermek çünkü "Array.from" u bilmiyordum ve bu en önemli nokta. Aramızda da çok ilginç bir tartışma var!
Stefano

1
@Stefano Cevabımı, Mapuzmanların mapve filterişlevleri kullanarak nesneleri doğru bir şekilde dönüştürmek için jeneratörlerin nasıl kullanılabileceğini göstermek için düzenledim . Bergi'nin cevabı, nesnelerin anahtarları kaybolduğu için nesneleri dönüştürmek için kullanılamayan tüm yinelenebilir nesneler için genel mapve filterişlevlerin kullanımını gösterir . MapMap
Aadit M Shah

Vay, düzenlemenizi gerçekten çok seviyorum. Burada kendi cevabımı yazdım: stackoverflow.com/a/28721418/422670 (bu soru yinelenen olarak kapatıldığından beri eklendi) çünkü Array.fromChrome'da çalışmıyor (Harita ve yineleyiciler çalışıyor!). Ama yaklaşımın çok benzer olduğunu görebiliyorsunuz ve sadece grubunuza "toArray" fonksiyonunu ekleyebilirsiniz!
Stefano

1
@Stefano Gerçekten. Nasıl bir toArrayfonksiyon ekleneceğini göstermek için cevabımı düzenledim .
Aadit M Shah

7

2019'dan küçük bir güncelleme:

Şimdi Array.from evrensel olarak kullanılabilir görünüyor ve ayrıca, bir ara dizi yaratmasını engelleyen ikinci bir mapFn argümanını kabul ediyor . Bu temelde şöyle görünür:

Array.from(myMap.entries(), entry => {...});

ile bir cevap Array.fromzaten mevcut olduğundan, bu cevap için bir yorum veya talep edilen düzenleme için daha uygundur ... ama teşekkürler!
Stefano

1

Yinelenebilirler için dizi benzeri yöntemler uygulayan https://www.npmjs.com/package/itiriri gibi bir kütüphane kullanabilirsiniz :

import { query } from 'itiriri';

const map = new Map();
map.set(1, 'Alice');
map.set(2, 'Bob');

const result = query(map)
  .filter([k, v] => v.indexOf('A') >= 0)
  .map([k, v] => `k - ${v.toUpperCase()}`);

for (const r of result) {
  console.log(r); // prints: 1 - ALICE
}

Bu lib inanılmaz görünüyor ve yinelenen @dimadeveatii atlamak için eksik boru - yazmak için çok teşekkürler, yakında deneyeceğim :-)
Angelos Pikoulas

0

Sen alabilirsiniz Dizilerin dizisi (anahtar ve değer):

[...this.state.selected.entries()]
/**
*(2) [Array(2), Array(2)]
*0: (2) [2, true]
*1: (2) [3, true]
*length: 2
*/

Ve sonra, örneğin harita yineleyiciye sahip tuşlar gibi, içeriden değerleri kolayca alabilirsiniz.

[...this.state.selected[asd].entries()].map(e=>e[0])
//(2) [2, 3]

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.