web-dev-qa-db-de.com

Umwandlung eines Javascript-Iterators in ein Array

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

Ich finde es jedoch sehr eingeschränkt in der "funktionalen" Programmierung, da es keine klassischen Karten-, Filter- usw. Methoden gibt, die mit einem [key, value]-Paar gut funktionieren würden. Es hat ein forEach, aber das Callback-Ergebnis wird NICHT zurückgegeben.

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

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

EDIT

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

PS.

Ich weiß, da ist das fantastische wu.js , aber seine Abhängigkeit von Traceur schreckt mich ab ...

111
Stefano

Sie suchen nach der neuen Array.from-Funktion , die beliebige iterierbare Elemente 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 sich lohnen, map, filter und ähnliche Methoden direkt auf der Iteratorschnittstelle zu definieren, um die Zuordnung des Arrays zu vermeiden. 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;
}
159
Bergi

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

Es ist super einfach.

Jedenfalls fehlt es Iteratoren an Reduktions-, Filter- und ähnlichen Methoden. Sie müssen sie selbst schreiben, da sie perfektionierter ist, als Map in Array und zurück zu konvertieren. Springen Sie jedoch nicht Map -> Array -> Map -> Array -> Map -> Array, da dies die Leistung beeinträchtigt.

24
Ginden

Es ist nicht notwendig, eine Map in eine Array umzuwandeln. Sie können einfach map- und filter-Funktionen für Map-Objekte erstellen:

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

Zum Beispiel könnten Sie dem Wert jedes Eintrags einer Karte, deren Schlüssel ein Primitiv ist, ein Knäuel (d. H. !-Zeichen) anhängen.

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>

Sie können auch map- und filter-Methoden zu Map.prototype hinzufügen, um das Lesen zu verbessern. Obwohl es generell nicht ratsam ist, native Prototypen zu ändern, glaube ich, dass im Fall von map und filter für Map.prototype eine Ausnahme gemacht werden kann:

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 hat er generische Generierungsfunktionen map und filter für alle iterierbaren Objekte erstellt. Der Vorteil der Verwendung ist, dass sie keine Generatorobjekte sind, da sie Generatorfunktionen sind.

Beispielsweise erstellen meine oben definierten map- und filter-Funktionen neue Map-Objekte. Beim Aufruf von object.filter(primitive).map(appendBang) werden zwei neue Map-Objekte 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 einem Iterator, seine Werte träge an den nächsten weiterzuleiten. 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 den Generatorfunktionen von Bergi habe, ist, dass sie nicht spezifisch für Map-Objekte sind. Sie werden stattdessen für alle iterierbaren Objekte verallgemeinert. Anstatt die Callback-Funktionen mit (value, key)-Paaren aufzurufen (wie ich es beim Mapping über eine Map erwarten würde), ruft es die Callback-Funktionen mit (value, index)-Paaren auf. Ansonsten ist es eine ausgezeichnete Lösung, und ich würde es auf jeden Fall empfehlen, es gegenüber den von mir bereitgestellten Lösungen zu verwenden.

Das sind also die spezifischen Generatorfunktionen, die ich zum Mapping und Filtern von Map-Objekten 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 fließendere Oberflä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.

12
Aadit M Shah

Sie könnten eine Bibliothek wie https://www.npmjs.com/package/itiriri verwenden, die arrayartige Methoden für iterable 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
}
1
dimadeveatii

Ein kleines Update von 2019:

Nun scheint Array.from universell verfügbar zu sein und akzeptiert außerdem das zweite Argument mapFn , das die Erstellung eines Zwischenarrays verhindert. Das sieht grundsätzlich so aus:

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