¿La forma más sencilla de fusionar mapas/conjuntos de ES6?

Resuelto jameslk asked hace 9 años • 15 respuestas

¿ Existe una forma sencilla de fusionar mapas ES6 (como Object.assign)? Y ya que estamos en eso, ¿qué pasa con los conjuntos ES6 (como Array.concat)?

jameslk avatar Aug 14 '15 08:08 jameslk
Aceptado

Para conjuntos:

var merged = new Set([...set1, ...set2, ...set3])

Para mapas:

var merged = new Map([...map1, ...map2, ...map3])

Tenga en cuenta que si varios mapas tienen la misma clave, el valor del mapa combinado será el valor del último mapa combinado con esa clave.

Oriol avatar Aug 14 '2015 01:08 Oriol

Por razones que no entiendo, no se puede agregar directamente el contenido de un conjunto a otro con un método integrado. Operaciones como unión, intersección, fusión, etc. son operaciones de conjuntos bastante básicas, pero no están integradas. Afortunadamente, puedes construirlos todos tú mismo con bastante facilidad.


[Agregado en 2021] : ahora existe una propuesta para agregar nuevos métodos Set/Map para este tipo de operaciones, pero el momento de implementación no está claro de inmediato. Parecen estar en la Etapa 2 (etapa de borrador) del proceso de especificaciones.

[Agregado en 2023] - La propuesta se encuentra ahora en la Etapa 3 (etapa de Candidato) del proceso de especificaciones.


Para implementar una operación de fusión usted mismo (fusionar el contenido de un conjunto con otro o un mapa con otro), puede hacerlo con una sola .forEach()línea:

var s = new Set([1,2,3]);
var t = new Set([4,5,6]);

t.forEach(s.add, s);
console.log(s);   // 1,2,3,4,5,6

Y, por ejemplo Map, podrías hacer esto:

var s = new Map([["key1", 1], ["key2", 2]]);
var t = new Map([["key3", 3], ["key4", 4]]);

t.forEach(function(value, key) {
    s.set(key, value);
});

O, en la sintaxis de ES6:

t.forEach((value, key) => s.set(key, value));

[Agregado en 2021]

Dado que ahora existe una propuesta oficial para nuevos métodos Set, puede usar este polyfill para el .union()método propuesto que funcionaría en las versiones ES6+ de ECMAScript. Tenga en cuenta que, según la especificación, esto devuelve un nuevo conjunto que es la unión de otros dos conjuntos. No fusiona el contenido de un conjunto con otro y esto implementa la verificación de tipos que se especifica en la propuesta .

if (!Set.prototype.union) {
    Set.prototype.union = function(iterable) {
        if (typeof this !== "object") {
            throw new TypeError("Must be of object type");
        }
        const Species = this.constructor[Symbol.species];
        const newSet = new Species(this);
        if (typeof newSet.add !== "function") {
            throw new TypeError("add method on new set species is not callable");
        }
        for (item of iterable) {
            newSet.add(item);
        }
        return newSet;
    }
}

O bien, aquí hay una versión más completa que modela el proceso ECMAScript para obtener el constructor de especies de manera más completa y se ha adaptado para ejecutarse en versiones anteriores de Javascript que quizás ni siquiera tengan Symbolo no tengan Symbol.speciesconfigurado:

if (!Set.prototype.union) {
    Set.prototype.union = function(iterable) {
        if (typeof this !== "object") {
            throw new TypeError("Must be of object type");
        }
        const Species = getSpeciesConstructor(this, Set);
        const newSet = new Species(this);
        if (typeof newSet.add !== "function") {
            throw new TypeError("add method on new set species is not callable");
        }
        for (item of iterable) {
            newSet.add(item);
        }
        return newSet;
    }
}

function isConstructor(C) {
    return typeof C === "function" && typeof C.prototype === "object";
}

function getSpeciesConstructor(obj, defaultConstructor) {
    const C = obj.constructor;
    if (!C) return defaultConstructor;
    if (typeof C !== "function") {
        throw new TypeError("constructor is not a function");
    }

    // use try/catch here to handle backward compatibility when Symbol does not exist
    let S;
    try {
        S = C[Symbol.species];
        if (!S) {
            // no S, so use C
            S = C;
        }
    } catch (e) {
        // No Symbol so use C
        S = C;
    }
    if (!isConstructor(S)) {
        throw new TypeError("constructor function is not a constructor");
    }
    return S;
}

Para su información, si desea una subclase simple del objeto integrado Setque contenga un .merge()método, puede usar esto:

// subclass of Set that adds new methods
// Except where otherwise noted, arguments to methods
//   can be a Set, anything derived from it or an Array
// Any method that returns a new Set returns whatever class the this object is
//   allowing SetEx to be subclassed and these methods will return that subclass
//   For this to work properly, subclasses must not change behavior of SetEx methods
//
// Note that if the contructor for SetEx is passed one or more iterables, 
// it will iterate them and add the individual elements of those iterables to the Set
// If you want a Set itself added to the Set, then use the .add() method
// which remains unchanged from the original Set object.  This way you have
// a choice about how you want to add things and can do it either way.

class SetEx extends Set {
    // create a new SetEx populated with the contents of one or more iterables
    constructor(...iterables) {
        super();
        this.merge(...iterables);
    }
    
    // merge the items from one or more iterables into this set
    merge(...iterables) {
        for (let iterable of iterables) {
            for (let item of iterable) {
                this.add(item);
            }
        }
        return this;        
    }
    
    // return new SetEx object that is union of all sets passed in with the current set
    union(...sets) {
        let newSet = new this.constructor(...sets);
        newSet.merge(this);
        return newSet;
    }
    
    // return a new SetEx that contains the items that are in both sets
    intersect(target) {
        let newSet = new this.constructor();
        for (let item of this) {
            if (target.has(item)) {
                newSet.add(item);
            }
        }
        return newSet;        
    }
    
    // return a new SetEx that contains the items that are in this set, but not in target
    // target must be a Set (or something that supports .has(item) such as a Map)
    diff(target) {
        let newSet = new this.constructor();
        for (let item of this) {
            if (!target.has(item)) {
                newSet.add(item);
            }
        }
        return newSet;        
    }
    
    // target can be either a Set or an Array
    // return boolean which indicates if target set contains exactly same elements as this
    // target elements are iterated and checked for this.has(item)
    sameItems(target) {
        let tsize;
        if ("size" in target) {
            tsize = target.size;
        } else if ("length" in target) {
            tsize = target.length;
        } else {
            throw new TypeError("target must be an iterable like a Set with .size or .length");
        }
        if (tsize !== this.size) {
            return false;
        }
        for (let item of target) {
            if (!this.has(item)) {
                return false;
            }
        }
        return true;
    }
}

module.exports = SetEx;

Esto debe estar en su propio archivo setex.js que luego puede require()ingresar en node.js y usarlo en lugar del conjunto integrado.

jfriend00 avatar Aug 14 '2015 01:08 jfriend00