Umwandlung eines Javascript-Iterators in ein Array


171

Ich versuche die neue Karte zu verwenden Objekt aus Javascript EC6 zu verwenden, da es bereits in den neuesten Firefox- und Chrome-Versionen unterstützt wird.

Aber ich finde es in der "funktionalen" Programmierung sehr eingeschränkt, weil es keine klassischen Karten-, Filter- usw. Methoden gibt, die gut mit a funktionieren würden [key, value] Paar . Es hat ein forEach, aber das gibt NICHT das Rückrufergebnis zurück.

Wenn ich es map.entries()von einem MapIterator in ein einfaches Array umwandeln könnte, könnte ich den Standard verwenden .map,.filter ohne zusätzliche Hacks.

Gibt es eine "gute" Möglichkeit, einen Javascript-Iterator in ein Array umzuwandeln? In Python ist das so einfach wie list(iterator)... aber Array(m.entries())ein Array mit dem Iterator als erstem Element zurückgeben !!!

BEARBEITEN

Ich habe vergessen anzugeben, dass ich nach einer Antwort suche, die überall dort funktioniert, wo Map funktioniert, was zumindest Chrome und Firefox bedeutet (Array.from funktioniert in Chrome nicht).

PS.

Ich weiß, dass es die fantastischen wu.js gibt, aber ihre Abhängigkeit von Traceur schreckt mich ab ...


Antworten:


246

Sie suchen nach der neuen Array.fromFunktion , die beliebige Iterables in Array-Instanzen konvertiert:

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

Es wird jetzt in Edge, FF, Chrome und Node 4+ unterstützt .

Natürlich kann es sinnvoll sein map, filterähnliche Methoden direkt auf der Iterator-Schnittstelle zu definieren , damit Sie die Zuordnung des Arrays vermeiden können. Möglicherweise möchten Sie auch eine Generatorfunktion anstelle von Funktionen höherer Ordnung verwenden:

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;
}

Ich würde erwarten, dass der Rückruf (value, key)Paare und keine (value, index)Paare empfängt .
Aadit M Shah

3
@AaditMShah: Was ist ein Schlüssel eines Iterators? Wenn Sie eine Karte iterieren würden, könnten Sie yourTransformation = function([key, value], index) { … }
natürlich

Ein Iterator hat keinen Schlüssel, aber ein MapSchlüsselwertpaar. Daher macht es meiner bescheidenen Meinung nach keinen Sinn, Allgemeines mapund filterFunktionen für Iteratoren zu definieren . Stattdessen sollte jeder iterable Objekt seine eigene haben mapund filterFunktionen. Dies ist sinnvoll, weil mapund filterstrukturerhaltende Operationen sind (vielleicht nicht, filteraber mapsicher), und daher sollten die Funktionen mapund filterdie Struktur der iterierbaren Objekte kennen, über die sie abgebildet oder gefiltert werden. Denken Sie darüber nach, in Haskell definieren wir verschiedene Instanzen von Functor. =)
Aadit M Shah

1
@ Stefano: Sie können es leicht shim ...
Bergi

1
@Incognito Ah ok, sicher ist das wahr, aber es ist genau das, wonach die Frage fragt, kein Problem mit meiner Antwort.
Bergi

45

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

Es ist super einfach.

Wie auch immer - Iteratoren haben keine Reduktions-, Filter- und ähnlichen Methoden. Sie müssen sie selbst schreiben, da dies perfekter ist als das Konvertieren von Map in Array und zurück. Aber keine Sprünge machen Map -> Array -> Map -> Array -> Map -> Array, da dies die Leistung beeinträchtigt.


1
Sofern Sie nicht etwas Wesentlicheres haben, sollte dies wirklich ein Kommentar sein. Darüber hinaus Array.fromwurde bereits von @Bergi abgedeckt.
Aadit M Shah

2
Und wie ich in meiner ursprünglichen Frage geschrieben habe, [iterator]funktioniert es nicht, weil es in Chrome ein Array mit einem einzelnen iteratorElement erstellt und [...map.entries()]in Chrome keine akzeptierte Syntax ist
Stefano

2
@Stefano Spread Operator ist jetzt Syntax in Chrome
Klesun

15

Es ist nicht nötig, ein Mapin ein zu verwandeln Array. Sie können einfach Objekte erstellen mapund filterfunktionieren 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;
}

Sie können beispielsweise einen Knall (dh ein !Zeichen) an den Wert jedes Eintrags einer Karte anhängen, deren Schlüssel ein Grundelement ist.

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>

Man könnte auch hinzufügen mapund filterMethoden auf , Map.prototypeum es besser zu machen lesen. Obwohl es im Allgemeinen nicht ratsam ist, native Prototypen zu modifizieren, glaube ich, dass eine Ausnahme im Fall von mapund filterfür Folgendes gemacht werden kann Map.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: In Bergis Antwort schuf er generische mapundfilter Generatorfunktionen für alle iterierbaren Objekte. Der Vorteil ihrer Verwendung besteht darin, dass sie, da sie Generatorfunktionen sind, keine iterierbaren Zwischenobjekte zuweisen.

Zum Beispiel erstellen meine mapund filterdie oben definierten Funktionen neue MapObjekte. Daher werden durch das Aufrufen object.filter(primitive).map(appendBang)zwei neue MapObjekte erstellt:

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

Das Erstellen von iterierbaren Zwischenobjekten ist teuer. Bergis Generatorfunktionen lösen dieses Problem. Sie ordnen keine Zwischenobjekte zu, sondern ermöglichen es einem Iterator, seine Werte träge dem nächsten zuzuführen. Diese Art der Optimierung wird in funktionalen Programmiersprachen als Fusion oder Entwaldung bezeichnet und kann die Programmleistung erheblich verbessern.

Das einzige Problem, das ich mit Bergis Generatorfunktionen habe, ist, dass sie nicht objektspezifisch Mapsind. Stattdessen werden sie für alle iterierbaren Objekte verallgemeinert. Anstatt die Rückruffunktionen mit (value, key)Paaren aufzurufen (wie ich es bei der Zuordnung über a erwarten würde Map), werden daher die Rückruffunktionen mit (value, index)Paaren aufgerufen . Ansonsten ist es eine ausgezeichnete Lösung und ich würde definitiv empfehlen, sie gegenüber den von mir bereitgestellten Lösungen zu verwenden.

Dies sind also die spezifischen Generatorfunktionen, die ich zum Zuordnen und Filtern von MapObjekten verwenden würde:

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;
}

Sie können wie folgt verwendet werden:

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>

Wenn Sie eine flüssigere Benutzeroberfläche wünschen, können Sie Folgendes tun:

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>

Hoffentlich hilft das.


es tut danke! @Bergi das gute Antwortzeichen geben, weil ich das "Array.from" nicht kannte und das die Antwort auf den Punkt ist. Sehr interessante Diskussion auch zwischen Ihnen!
Stefano

1
@Stefano Ich habe meine Antwort bearbeitet, um zu zeigen, wie Generatoren verwendet werden können, um MapObjekte mithilfe von Spezialfunktionen mapund filterFunktionen korrekt zu transformieren . Bergis Antwort zeigt die Verwendung von Generika mapund filterFunktionen für alle iterierbaren Objekte, die nicht zum Transformieren von MapObjekten verwendet werden können, da die Schlüssel des MapObjekts verloren gehen.
Aadit M Shah

Wow, ich mag deine Bearbeitung wirklich. Am Ende habe ich hier meine eigene Antwort geschrieben: stackoverflow.com/a/28721418/422670 (dort hinzugefügt, da diese Frage als Duplikat geschlossen wurde), da Array.fromdies in Chrome nicht funktioniert (während Map und Iteratoren dies tun!). Aber ich kann sehen, dass der Ansatz sehr ähnlich ist und Sie könnten einfach die "toArray" -Funktion zu Ihrem Haufen hinzufügen!
Stefano

1
@ Stefano In der Tat. Ich habe meine Antwort bearbeitet, um zu zeigen, wie eine toArrayFunktion hinzugefügt wird.
Aadit M Shah

7

Ein kleines Update von 2019:

Jetzt scheint Array.from universell verfügbar zu sein und akzeptiert außerdem ein zweites Argument mapFn , das verhindert, dass ein Zwischenarray erstellt wird. Das sieht im Grunde so aus:

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

Da bereits eine Antwort mit Array.fromvorhanden ist, ist dies eher als Kommentar oder angeforderte Bearbeitung dieser Antwort geeignet ... aber danke!
Stefano

1

Sie können eine Bibliothek wie https://www.npmjs.com/package/itiriri verwenden , die Array-ähnliche Methoden für Iterables implementiert:

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
}

Diese Bibliothek sieht fantastisch aus und der fehlende Kanal, um zu iterables @dimadeveatii zu springen - vielen Dank für das Schreiben, ich werde es bald versuchen :-)
Angelos Pikoulas

0

Sie können das Array von Arrays (Schlüssel und Wert) erhalten:

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

Und dann können Sie ganz einfach Werte von innen abrufen, wie zum Beispiel die Schlüssel mit dem Karteniterator.

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

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.