¿Formas de crear un conjunto en JavaScript?

82

En Eloquent JavaScript, Capítulo 4, se crea un conjunto de valores creando un objeto y almacenando los valores como nombres de propiedad, asignando valores arbitrarios (por ejemplo, verdadero) como valores de propiedad. Para comprobar si el valor ya está contenido en el conjunto, inse utiliza el operador:

var set = {};

if (!'Tom' in set) { 
  set.Tom = true;
}

¿Es este JavaScript idiomático? ¿No estaría usando una matriz aún mejor?

var set = [];

if (!'Tom' in set) { 
  set.push = 'Tom';
}
método de ayuda
fuente
1
¿Cómo esperaría que 'Tom' in setse vea una matriz para la que es verdadero? Parece que tienes suposiciones erróneas sobre algo y estoy tratando de averiguar qué .
10
Para su información, necesita parens: if(!('Tom' in set)). Actualmente significa false in setdesde !'Tom' === false.
pimvdb
ES6 tiene conjuntos, consulte la respuesta de John a continuación
Ben Taliadoros

Respuestas:

101

Los conjuntos ahora están disponibles en ES2015 (también conocido como ES6, es decir, ECMAScript 6). ES6 ha sido el estándar actual para JavaScript desde junio de 2015.

ECMAScript 6 tiene la estructura de datos Set que funciona para valores arbitrarios, es rápido y maneja NaN correctamente. - Axel Rauschmayer , Explorando ES6

Los primeros dos ejemplos del libro de Axel Rauschmayer Exploring ES6 :

Gestión de elementos individuales:

> let set = new Set();
> set.add('red')

> set.has('red')
true
> set.delete('red')
true
> set.has('red')
false

Determinar el tamaño de un conjunto y borrarlo:

> let set = new Set();
> set.add('red')
> set.add('green')

> set.size
2
> set.clear();
> set.size
0

Me volvería a la salida Explorando ES6 si desea obtener más información acerca de los conjuntos de JavaScript. El libro es de lectura gratuita en línea, pero si desea apoyar al autor, el Dr. Axel Rauschmayer , puede comprarlo por alrededor de $ 30.

Si desea utilizar Sets y ES6 ahora, puede utilizar Babel , el transpilador de ES6 a ES5 y sus polyfills.

Editar: A partir del 6 de junio de 2017, la mayoría de los principales navegadores tienen soporte completo de Set en sus últimas versiones (excepto IE 11). Esto significa que es posible que no necesite babel si no le interesa admitir navegadores antiguos. Si desea ver la compatibilidad en diferentes navegadores, incluido su navegador actual, consulte la tabla de compatibilidad ES6 de Kangax .

EDITAR:

Solo una aclaración sobre la inicialización. Los conjuntos pueden tomar cualquier iterable sincrónico en su constructor. Esto significa que pueden tomar no solo matrices, sino también cadenas e iteradores. Tomemos, por ejemplo, la siguiente inicialización de matriz y cadena de un conjunto:

const set1 = new Set(['a','a','b','b','c','c']);
console.log(...set1);
console.log(set1.size);
const set2 = new Set("aabbcc");
console.log(...set2);
console.log(set2.size);

Ambas salidas de la matriz y la cadena son iguales. Tenga en cuenta que ...set1es la sintaxis de propagación . Parece que cada elemento del iterable se agrega uno por uno al conjunto, por lo que, dado que tanto la matriz como la cadena tienen los mismos elementos y como los elementos están en el mismo orden, el conjunto se crea igual. Otra cosa a tener en cuenta sobre los conjuntos es que cuando se itera sobre ellos, el orden de iteración sigue el orden en que los elementos se insertaron en el conjunto. A continuación, se muestra un ejemplo de iteración sobre un conjunto:

const set1 = new Set(['a','a','b','b','c','c']);
for(const element of set1) {
  console.log(element);
}

Dado que puede usar cualquier iterable para inicializar un conjunto, incluso podría usar un iterador de una función generadora . Aquí hay dos ejemplos de inicializaciones de iteradores que producen el mismo resultado:

// a simple generator example
function* getLetters1 () {
  yield 'a';
  yield 'a';
  yield 'b';
  yield 'b';
  yield 'c';
  yield 'c';
}

// a somewhat more commonplace generator example
// with the same output as getLetters1.
function* getLetters2 (letters, repeatTimes) {
  for(const letter of letters) {
    for(let i = 0; i < repeatTimes; ++i) { 
      yield letter;
    }
  }
}

console.log("------ getLetters1 ------");
console.log(...getLetters1());
const set3 = new Set(getLetters1());
console.log(...set3);
console.log(set3.size);

console.log("------ getLetters2 ------");
console.log(...getLetters2('abc', 2));
const set4 = new Set(getLetters2('abc', 2));
console.log(...set4);
console.log(set4.size);

Las funciones del generador de estos ejemplos podrían simplemente escribirse para que no se repitan, pero si la función del generador es más complicada y siempre que lo siguiente no afecte el rendimiento de manera demasiado negativa, puede usar el método Set para ayudar a obtener solo valores de un generador que no no repetir.

Si desea saber más sobre los sets sin leer el capítulo de su libro del Dr. Rauschmayer, puede consultar los documentos de MDN en Set . MDN también tiene más ejemplos de iterar sobre un conjunto como el uso forEachy el uso de los .keys, .valuesy .entriesmétodos. MDN también tiene ejemplos como unión de conjuntos, intersección de conjuntos, diferencia de conjuntos, diferencia de conjuntos simétricos y comprobación de superconjuntos de conjuntos. Con suerte, la mayoría de esas operaciones estarán disponibles en JavaScript sin necesidad de crear sus propias funciones que las respalden. De hecho, existe esta propuesta TC39 para nuevos métodos Set que, con suerte, deberían agregar los siguientes métodos a Set en JavaScript en algún momento futuro si la propuesta alcanza la etapa 4:

  • Set.prototype.intersection (iterable): el método crea una nueva instancia de Set mediante la operación de intersección de set.
  • Set.prototype.union (iterable): el método crea una nueva instancia de Set mediante la operación de unión de set.
  • Set.prototype.difference (iterable): el método crea un nuevo Set sin elementos presentes en iterable.
  • Set.prototype.symmetricDifference (iterable): devuelve el conjunto de elementos que se encuentran solo en this o en iterable.
  • Set.prototype.isSubsetOf (iterable)
  • Set.prototype.isDisjointFrom (iterable)
  • Set.prototype.isSupersetOf (iterable)
Juan
fuente
Además, los conjuntos en sí son iterables, por lo que puede inicializar conjuntos con otros conjuntos para hacer una copia o hacer algo como un conjunto nuevo ([... conjuntoA, ... conjuntoB]) para una operación de unión.
John
1
@LanceKind Agregué información adicional, incluida la demostración de que puede inicializar conjuntos con cualquier iterable, no solo con matrices.
John
32

Utilizo objetos dict como conjuntos. Esto funciona con cadenas y números, pero supongo que causaría problemas si quisiera tener un conjunto de objetos utilizando operadores personalizados de igualdad y comparación:

Creando un set:

var example_set = 
{
    'a':true,
    'b':true,
    'c':true
}

Prueba para su inclusión en un conjunto

if( example_set['a'] ){
    alert('"a" is in set');
}

Agregar un elemento a un conjunto

example_set['d'] = true;

Eliminar un elemento de un conjunto

delete example_set['a'];

Chris Dutrow
fuente
1
El código en el que estaba trabajando usaba una versión anterior del motor que no tenía soporte para Set. Esto ayudó.
Abhijith Madhav
16

Los conjuntos no permiten entradas duplicadas y, por lo general, no garantizan un orden predefinido. Las matrices hacen ambas cosas, violando así lo que significa ser un conjunto (a menos que realice comprobaciones adicionales).

Jon Newmuis
fuente
2
Array no filtra entradas duplicadas ... por ejemplo arr.push ({id: 1, name: "Jake"}) como NSSet en Objective-C :)
iTux
Si lo toma mot-a-mot, no. Pero puede usar la identificación como clave de matriz (mapa). arr[id] = {"name": "Jake"};
Buffalo
11

La primera forma es JavaScript idiomático.

Siempre que desee almacenar un par clave / valor, debe utilizar un objeto JavaScript. En cuanto a las matrices, existen varios problemas:

  1. El índice es un valor numérico.

  2. No es una manera fácil de verificar si un valor está en una matriz sin recorrerlo.

  3. Un conjunto no permite duplicados. Una matriz lo hace.

Justin Niessner
fuente
¿Qué sería mejor asignar al valor de la propiedad?
helpermethod
1
Es posible eliminar elementos duplicados de matrices de JavaScript. stackoverflow.com/a/12166248/975097
Anderson Green
9

Si desea crear un conjunto a partir de una matriz, simplemente haga:

let arr = [1, 1, 2, 1, 3];
let mySet = new Set(arr); // Set { 1, 2, 3 }

Esta es una sintaxis de azúcar que me gustaba bastante cuando programaba en Python, muy contento de que ES6 finalmente hizo posible hacer lo mismo.

NOTA: entonces me doy cuenta de que lo que dije no respondió directamente a su pregunta. La razón por la que tiene este "truco" en ES5 es porque el tiempo de búsqueda en un objeto por claves es significativamente más rápido (O (1)) que en una matriz (O (n)). En aplicaciones de rendimiento crítico, puede sacrificar este poco de legibilidad o intuición para un mejor rendimiento.

Pero bueno, bienvenido a 2017, donde ahora puede usar el conjunto adecuado en todos los principales navegadores modernos.

benjaminz
fuente
6

Establece en ES6/ ES2015:

ES6/ ES2015ahora tiene conjuntos integrados. Un conjunto es una estructura de datos que permite el almacenamiento de valores únicos de cualquier tipo, ya sean valores primitivos o referencias a objetos. Un conjunto se puede declarar utilizando el ES6constructor de conjuntos integrado de la siguiente manera:

const set = new Set([1, 2, 3, 4, 5]);

Al crear un conjunto utilizando el constructor de conjuntos, nuestro objeto de conjunto recién creado hereda del Set.prototype. Esto tiene todo tipo de métodos y propiedades auxiliares. Esto le permite hacer fácilmente las siguientes cosas:

Ejemplo:

const set = new Set([1, 2, 3, 4, 5]);

// checkout the size of the set
console.log('size is: ' + set.size);

// has method returns a boolean, true if the item is in the set
console.log(set.has(1));

// add a number
set.add(6);

// delete a number
set.delete(1);

// iterate over each element using a callback
set.forEach((el) => {
  console.log(el);
});

// remove all the entries from the set
set.clear();

Compatibilidad del navegador:

Todos los navegadores principales ahora son totalmente compatibles con los conjuntos, excepto IE, donde faltan algunas funciones. Para obtener una referencia exacta, consulte los documentos de mdn .

Willem van der Veen
fuente
@Velojet: Ok, bastante justo.
kjhughes
3

Hay dos problemas con el uso de objetos simples de JavaScript para emular conjuntos: primero, un objeto puede tener una propiedad heredada que arruinaría el operador "in" y segundo, solo puede almacenar valores escalares de esta manera, hacer un conjunto de objetos no es posible. Por lo tanto, una implementación realista de Sets debería proporcionar métodos addy en containslugar de inasignaciones simples y de propiedades.

georg
fuente
@kojiro: los objetos se convierten en cadenas cuando se usan como claves:set={};set[{x:1}]=123;alert(set[{z:99}])
georg
3

Puede probar Buckets , es una biblioteca de estructura de datos de JavaScript y tiene todo lo que necesita para manipular conjuntos.

Daniel
fuente
¿Proporciona un método addAll (matriz)?
jorrebor
1

Creación y uso básico del objeto Set 🔷

let mySet = new Set()

mySet.add(2)         // Set {2}
mySet.add(7)         // Set {2, 7}
mySet.add(7)         // Set {2, 7}
mySet.add('my text') // Set {2, 7, 'my text'}
let myObj = { a: 1, b: 2 }
mySet.add(myObj)     // Set {2, 7, 'my text', {...}}
mySet.has(2)         // true
mySet.has(myObj)     // true
mySet.size           // 4

Iteración

for (let item of mySet) console.log(item)  // 2, 7, 'my text', {a:1, b:2}
mySet.forEach(value => console.log(value)) // 2, 7, 'my text', {a:1, b:2}

Convertir a matriz

var myArr = Array.from(mySet)             // [2, 7, 'my text', {a:1, b:2}]

❕ La característica más distintiva que ofrece Set es que cada valor en el objeto Set debe ser único. Por lo que no puede agregar valores duplicados.

Hasan Sefa Ozalp
fuente