web-dev-qa-db-fra.com

Transformer un itérateur Javascript en un tableau

J'essaie d'utiliser le nouvel objet Map de Javascript EC6, car il est déjà pris en charge par les dernières versions de Firefox et de Chrome.

Mais je trouve cela très limité dans la programmation "fonctionnelle", car il lui manque les méthodes classiques de carte, de filtrage, etc. qui fonctionneraient bien avec une paire [key, value]. Il a un forEach mais cela ne renvoie PAS le résultat du rappel.

Si je pouvais transformer son map.entries() d’un MapIterator en un simple tableau, je pourrais alors utiliser le .map, .filter standard, sans piratage supplémentaire.

Existe-t-il un "bon" moyen de transformer un itérateur Javascript en un tableau? En python, il suffit de faire list(iterator)... mais Array(m.entries()) renvoie un tableau avec le premier élément !!!

MODIFIER

J'ai oublié de préciser que je cherche une réponse qui fonctionne partout où Map fonctionne, ce qui signifie au moins Chrome et Firefox (Array.from ne fonctionne pas dans Chrome).

PS.

Je sais qu'il y a le fantastique wu.js mais sa dépendance à Traceur me met mal à l'aise ...

111
Stefano

Vous recherchez la nouvelle fonction Array.from qui convertit des itérables arbitraires en instances de tableaux:

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

Il est maintenant pris en charge dans Edge, FF, Chrome et Node 4+ .

Bien sûr, il peut être intéressant de définir map, filter et des méthodes similaires directement sur l'interface itérateur, afin d'éviter d'allouer le tableau. Vous pouvez également utiliser une fonction de générateur au lieu de fonctions d'ordre supérieur:

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()] ou Array.from(map.entries())

C'est super facile.

Quoi qu'il en soit - les itérateurs ne disposent pas de méthodes de réduction, de filtrage et similaires. Vous devez les écrire vous-même, car cela est plus performant que de convertir Map en tableau et inversement. Mais ne faites pas de sauts Carte -> Tableau -> Carte -> Tableau -> Carte -> Tableau, car cela tuerait les performances.

24
Ginden

Il n'est pas nécessaire de transformer une Map en une Array. Vous pouvez simplement créer des fonctions map et filter pour des objets 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;
}

Par exemple, vous pouvez ajouter un bang (caractère !) à la valeur de chaque entrée d’une mappe dont la clé est une primitive.

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>

Vous pouvez également ajouter des méthodes map et filter sur Map.prototype pour améliorer la lecture. Bien qu'il soit généralement déconseillé de modifier les prototypes natifs, je pense qu'une exception peut être faite dans le cas de map et filter pour 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: Dans sa réponse, Bergi créa des fonctions génératrices génériques map et filter pour tous les objets itérables. L'avantage de les utiliser est que, s'agissant de fonctions génératrices, elles n'allouent pas d'objets itérables intermédiaires.

Par exemple, mes fonctions map et filter définies ci-dessus créent de nouveaux objets Map. Par conséquent, l'appel de object.filter(primitive).map(appendBang) crée deux nouveaux objets Map:

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

La création d'objets itérables intermédiaires coûte cher. Les fonctions de générateur de Bergi résolvent ce problème. Ils n'allouent pas d'objets intermédiaires, mais permettent à un itérateur de transmettre ses valeurs paresseusement au suivant. Ce type d'optimisation est appelé fusion ou déforestation dans les langages de programmation fonctionnels et peut considérablement améliorer les performances du programme.

Le seul problème que j'ai avec les fonctions de générateur de Bergi est qu'elles ne sont pas spécifiques aux objets Map. Au lieu de cela, ils sont généralisés pour tous les objets itérables. Par conséquent, au lieu d’appeler les fonctions de rappel avec des paires (value, key) (comme je l’attendais lors du mappage sur une Map), il appelle les fonctions de rappel avec des paires (value, index). Sinon, c'est une excellente solution et je recommanderais certainement de l'utiliser par rapport aux solutions que j'ai fournies.

Ce sont donc les fonctions spécifiques du générateur que j'utiliserais pour mapper et filtrer les objets Map:

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

Ils peuvent être utilisés comme suit:

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>

Si vous voulez une interface plus fluide, vous pouvez faire quelque chose comme ceci:

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>

J'espère que cela pourra aider.

12
Aadit M Shah

Vous pouvez utiliser une bibliothèque telle que https://www.npmjs.com/package/itiriri qui implémente des méthodes de type tableau pour les iterables

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

Une petite mise à jour de 2019:

Maintenant, Array.from semble être universellement disponible et accepte en outre un deuxième argument mapFn , ce qui l’empêche de créer un tableau intermédiaire. Cela ressemble fondamentalement à ceci:

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