En informática , un conjunto es un tipo de dato abstracto que puede almacenar valores distintos, sin ningún orden en particular . Es una implementación informática del concepto matemático de conjunto finito . A diferencia de la mayoría de los demás tipos de colecciones , en lugar de recuperar un elemento específico de un conjunto, normalmente se comprueba si un valor pertenece a él.
Algunas estructuras de datos de conjuntos están diseñadas para conjuntos estáticos o fijos que no cambian después de su creación. Los conjuntos estáticos solo permiten operaciones de consulta sobre sus elementos, como comprobar si un valor dado pertenece al conjunto o enumerar los valores en un orden arbitrario. Otras variantes, denominadas conjuntos dinámicos o mutables , permiten además la inserción y eliminación de elementos.
Un multiconjunto es un tipo especial de conjunto en el que un elemento puede aparecer varias veces.
teoría de tipos
En la teoría de tipos , los conjuntos se identifican generalmente con su función indicadora (función característica): en consecuencia, un conjunto de valores de tipopuede denotarse poro(Los subtipos y subconjuntos pueden modelarse mediante tipos de refinamiento , y los conjuntos cociente pueden reemplazarse por setoides ). La función característicade un conjuntose define como:
En teoría, muchas otras estructuras de datos abstractas pueden considerarse estructuras de conjuntos con operaciones adicionales o axiomas adicionales impuestos a las operaciones estándar. Por ejemplo, un montón abstracto puede considerarse una estructura de conjuntos con una operación que devuelve el elemento de menor valor.min(S)
Operaciones
Operaciones básicas de la teoría de conjuntos
Se pueden definir las operaciones del álgebra de conjuntos :
union(S,T): devuelve la unión de los conjuntos S y T.intersection(S,T): devuelve la intersección de los conjuntos S y T.difference(S,T): devuelve la diferencia de los conjuntos S y T.subset(S,T): un predicado que comprueba si el conjunto S es un subconjunto del conjunto T.
Conjuntos estáticos
Las operaciones típicas que puede proporcionar una estructura de conjunto estático S son:
is_element_of(x,S): comprueba si el valor x está en el conjunto S.is_empty(S): comprueba si el conjunto S está vacío.size(S)o : devuelve el número de elementos en S .cardinality(S)iterate(S): devuelve una función que devuelve un valor más de S en cada llamada, en algún orden arbitrario.enumerate(S): devuelve una lista que contiene los elementos de S en algún orden arbitrario.build(x1,x2,…,xn,): crea una estructura de conjunto con valores x 1 , x 2 ,..., x n .create_from(collection): crea una nueva estructura de conjunto que contiene todos los elementos de la colección dada o todos los elementos devueltos por el iterador dado .
Conjuntos dinámicos
Las estructuras de conjuntos dinámicos suelen añadir:
create(): crea una nueva estructura de conjunto, inicialmente vacía.create_with_capacity(n): crea una nueva estructura de conjunto, inicialmente vacía pero capaz de contener hasta n elementos.
add(S,x): agrega el elemento x a S , si aún no está presente.remove(S, x): elimina el elemento x de S , si está presente.capacity(S): devuelve el número máximo de valores que S puede contener.
Algunas estructuras de conjuntos pueden permitir solo algunas de estas operaciones. El costo de cada operación dependerá de la implementación y, posiblemente, también de los valores específicos almacenados en el conjunto y del orden en que se inserten.
Operaciones adicionales
Hay muchas otras operaciones que pueden (en principio) definirse en términos de lo anterior, tales como:
pop(S): devuelve un elemento arbitrario de S , eliminándolo de S. [ 1 ]pick(S): devuelve un elemento arbitrario de S. [ 2 ] [ 3 ] [ 4 ] Funcionalmente, el mutadorpoppuede interpretarse como el par de selectores(pick, rest),donderestdevuelve el conjunto que consta de todos los elementos excepto el elemento arbitrario. [ 5 ] Puede interpretarse en términos deiterate. [ a ]map(F,S): devuelve el conjunto de valores distintos que resultan de aplicar la función F a cada elemento de S.filter(P,S): devuelve el subconjunto que contiene todos los elementos de S que satisfacen un predicado P dado .fold(A0,F,S): devuelve el valor A | S | después de aplicar para cada elemento e de S, alguna operación binaria F. F debe ser asociativa y conmutativa para que esto esté bien definido.Ai+1 := F(Ai, e)clear(S): eliminar todos los elementos de S.equal(S1', S2'): comprueba si los dos conjuntos dados son iguales (es decir, contienen todos y solo los mismos elementos).hash(S): devuelve un valor hash para el conjunto estático S tal que si entoncesequal(S1, S2)hash(S1) = hash(S2)
Se pueden definir otras operaciones para conjuntos con elementos de un tipo especial:
sum(S): devuelve la suma de todos los elementos de S para alguna definición de "suma". Por ejemplo, sobre números enteros o reales, puede definirse como .fold(0, add, S)collapse(S): dado un conjunto de conjuntos, devuelve la unión. [ 6 ] Por ejemplo,collapse({{1}, {2, 3}}) == {1, 2, 3}. Puede considerarse un tipo desum.flatten(S): dado un conjunto que consta de conjuntos y elementos atómicos (elementos que no son conjuntos), devuelve un conjunto cuyos elementos son los elementos atómicos del conjunto original de nivel superior o elementos de los conjuntos que contiene. En otras palabras, elimina un nivel de anidamiento, comocollapse,pero permite átomos. Esto se puede hacer una sola vez o aplanando recursivamente para obtener un conjunto de solo elementos atómicos. [ 7 ] Por ejemplo,flatten({1, {2, 3}}) == {1, 2, 3}.nearest(S,x): devuelve el elemento de S que está más cerca en valor de x (según alguna métrica ).min(S), : devuelve el elemento mínimo/máximo de S.max(S)
Implementaciones
Los conjuntos se pueden implementar utilizando diversas estructuras de datos , que proporcionan diferentes compensaciones de tiempo y espacio para diversas operaciones. Algunas implementaciones están diseñadas para mejorar la eficiencia de operaciones muy especializadas, como nearesto union. Las implementaciones descritas como de "uso general" suelen esforzarse por optimizar las operaciones element_of, add, y delete. Una implementación simple es usar una lista , ignorando el orden de los elementos y teniendo cuidado de evitar valores repetidos. Esto es simple pero ineficiente, ya que operaciones como la pertenencia a un conjunto o la eliminación de elementos son O ( n ), ya que requieren escanear toda la lista. [ b ] Los conjuntos a menudo se implementan utilizando estructuras de datos más eficientes, en particular varios tipos de árboles , tries o tablas hash .
Como los conjuntos pueden interpretarse como una especie de mapa (mediante la función indicadora), se suelen implementar de la misma forma que los mapas (parciales) ( arreglos asociativos ) –en este caso, donde el valor de cada par clave-valor tiene el tipo de unidad o un valor centinela (como 1)–, es decir, un árbol de búsqueda binaria autoequilibrado para conjuntos ordenados (que tiene O(log n) para la mayoría de las operaciones), o una tabla hash para conjuntos no ordenados (que tiene O(1) en el caso promedio, pero O(n) en el peor de los casos, para la mayoría de las operaciones). Se puede utilizar una tabla hash lineal ordenada [ 8 ] para proporcionar conjuntos ordenados de forma determinista.
Además, en lenguajes que admiten mapas pero no conjuntos, los conjuntos se pueden implementar en términos de mapas. Por ejemplo, un modismo de programación común en Perl que convierte un array en un hash cuyos valores son el valor centinela 1, para usarlo como un conjunto, es:
mis %elementos = mapa { $_ => 1 } @elementos ;Otros métodos populares incluyen los arreglos . En particular, un subconjunto de los enteros del 1 al n se puede implementar de manera eficiente como un arreglo de bits de n bits , que también admite operaciones de unión e intersección muy eficientes. Un mapa de Bloom implementa un conjunto de forma probabilística, utilizando una representación muy compacta, pero con un pequeño riesgo de falsos positivos en las consultas.
Las operaciones de conjuntos booleanos pueden implementarse en términos de operaciones más elementales ( pop, clear, y add), pero los algoritmos especializados pueden producir límites de tiempo asintóticos más bajos. Si los conjuntos se implementan como listas ordenadas, por ejemplo, el algoritmo ingenuo para tomará un tiempo proporcional a la longitud m de S por la longitud n de T ; mientras que una variante del algoritmo de fusión de listas hará el trabajo en un tiempo proporcional a m + n . Además, existen estructuras de datos de conjuntos especializadas (como la estructura de datos de unión-búsqueda ) que están optimizadas para una o más de estas operaciones, a expensas de otras.union(S,T)
Soporte de idiomas
Uno de los primeros lenguajes en admitir conjuntos fue Pascal ; muchos lenguajes ahora lo incluyen, ya sea en el lenguaje base o en una biblioteca estándar .
- En C++ , la Biblioteca de Plantillas Estándar (STL) proporciona la
setclase de plantilla, que normalmente se implementa mediante un árbol de búsqueda binaria (por ejemplo, un árbol rojo-negro ); la STL de SGIhash_settambién proporciona la clase de plantilla, que implementa un conjunto mediante una tabla hash. C++11 admite launordered_setclase de plantilla, que se implementa mediante una tabla hash. En los conjuntos, los elementos mismos son las claves, a diferencia de los contenedores secuenciados, donde se accede a los elementos mediante su posición (relativa o absoluta). Los elementos de un conjunto deben tener un orden débil estricto. - La biblioteca estándar de Rust proporciona los tipos genéricos
HashSety .BTreeSet - Java ofrece la
Setinterfaz para admitir conjuntos (con laHashSetclase que la implementa utilizando una tabla hash) y laSortedSetsubinterfaz para admitir conjuntos ordenados (con laTreeSetclase que la implementa utilizando un árbol de búsqueda binaria). - El framework Foundation de Apple (parte de Cocoa ) proporciona las clases Objective-C
NSSet,NSMutableSet,NSCountedSet,NSOrderedSet, yNSMutableOrderedSet. Las API de CoreFoundation proporcionan los tipos CFSet y CFMutableSet para su uso en C. - Python tiene
settiposfrozensetincorporados desde la versión 2.4, y desde Python 3.0 y 2.7, admite literales de conjuntos no vacíos usando una sintaxis de llaves, por ejemplo:{x, y, z}; los conjuntos vacíos deben crearse usandoset(), porque Python usa{}para representar el diccionario vacío. - El .NET Framework proporciona las clases genéricas
HashSetqueSortedSetimplementan laISetinterfaz genérica. - La biblioteca de clases de Smalltalk
Setincluye yIdentitySet, utilizando igualdad e identidad para la prueba de inclusión respectivamente. Muchos dialectos proporcionan variaciones para almacenamiento comprimido (NumberSet,CharacterSet), para ordenamiento (OrderedSet,SortedSet, etc.) o para referencias débiles (WeakIdentitySet). - La biblioteca estándar de Ruby
setincluye un módulo que contieneSetclasesSortedSetque implementan conjuntos utilizando tablas hash, lo que permite la iteración en orden ordenado. - La biblioteca estándar de OCaml
Setcontiene un módulo que implementa una estructura de datos de conjunto funcional utilizando árboles de búsqueda binaria. - La implementación de Haskell en GHC proporciona un módulo que implementa conjuntos inmutables utilizando árboles de búsqueda binaria. [ 9 ]
Data.Set - El paquete Tcllib de Tcl proporciona un módulo de conjuntos que implementa una estructura de datos de conjuntos basada en listas de TCL.
- La biblioteca estándar de Swift
Setcontiene un tipo, desde Swift 1.2. - JavaScript se introdujo
Setcomo un objeto integrado estándar con el estándar ECMAScript 2015 [ 10 ] . - La biblioteca estándar de Erlang
setstiene un módulo. - Clojure tiene una sintaxis literal para conjuntos hash, y también implementa conjuntos ordenados.
- LabVIEW ofrece soporte nativo para conjuntos desde la versión 2019.
- Ada proporciona los
Ada.Containers.Hashed_SetspaquetesAda.Containers.Ordered_Sets.
Como se indicó en la sección anterior, en los lenguajes que no admiten directamente conjuntos pero sí matrices asociativas , los conjuntos se pueden emular utilizando matrices asociativas, usando los elementos como claves y un valor ficticio como valores, que se ignoran.
Conjunto múltiple
Una generalización del concepto de conjunto es el de multiconjunto o bolsa , similar a un conjunto pero que permite valores repetidos ("iguales") (duplicados). Esto se utiliza en dos sentidos distintos: los valores iguales se consideran idénticos y simplemente se cuentan, o bien se consideran equivalentes y se almacenan como elementos distintos. Por ejemplo, dada una lista de personas (por nombre) y edades (en años), se podría construir un multiconjunto de edades, que simplemente cuenta el número de personas de una edad determinada. Alternativamente, se puede construir un multiconjunto de personas, donde dos personas se consideran equivalentes si tienen la misma edad (aunque pueden ser personas diferentes y tener nombres distintos), en cuyo caso cada par (nombre, edad) debe almacenarse, y al seleccionar una edad determinada se obtienen todas las personas de esa edad.
Formalmente, en informática, es posible que los objetos se consideren "iguales" bajo alguna relación de equivalencia , pero distintos bajo otra. Algunos tipos de implementaciones de multiconjuntos almacenan los objetos iguales distintos como elementos separados en la estructura de datos; mientras que otros la reducen a una sola versión (la primera encontrada) y mantienen un contador entero positivo que indica la multiplicidad del elemento.
Al igual que con los conjuntos, los multiconjuntos se pueden implementar de forma natural utilizando tablas hash o árboles, lo que da como resultado diferentes características de rendimiento.
El conjunto de todas las bolsas de tipo T viene dado por la expresión bolsa T. Si por multiconjunto se consideran idénticos los elementos iguales y simplemente se cuentan, entonces un multiconjunto puede interpretarse como una función del dominio de entrada a los enteros no negativos ( números naturales ), generalizando la identificación de un conjunto con su función indicadora. En algunos casos, un multiconjunto en este sentido de conteo puede generalizarse para permitir valores negativos, como en Python.
- La biblioteca de plantillas estándar de C++ implementa multiconjuntos ordenados y no ordenados. Proporciona la
multisetclase para el multiconjunto ordenado, como una especie de contenedor asociativo , que implementa este multiconjunto utilizando un árbol de búsqueda binaria autoequilibrado . Proporciona launordered_multisetclase para el multiconjunto no ordenado, como una especie de contenedor asociativo no ordenado , que implementa este multiconjunto utilizando una tabla hash . El multiconjunto no ordenado es estándar desde C++11 ; anteriormente, la STL de SGI proporcionaba lahash_multisetclase, que fue copiada y finalmente estandarizada. - Para Java , las bibliotecas de terceros proporcionan funcionalidad de conjuntos múltiples:
- Apache Commons Collections proporciona las interfaces
BagySortedBag, con clases de implementación comoHashBagyTreeBag. - Google Guava proporciona la
Multisetinterfaz, con clases de implementación comoHashMultisetyTreeMultiset.
- Apache Commons Collections proporciona las interfaces
- Apple proporciona la
NSCountedSetclase como parte de Cocoa y losCFBagtiposCFMutableBagcomo parte de CoreFoundation . - La biblioteca estándar de Python incluye
collections.Counter, que es similar a un multiconjunto. - Smalltalk incluye la
Bagclase, que puede instanciarse para usar identidad o igualdad como predicado para la prueba de inclusión.
Cuando no se dispone de una estructura de datos multiconjunto, una solución alternativa es utilizar un conjunto regular, pero sobrescribir el predicado de igualdad de sus elementos para que siempre devuelva "distinto de" en objetos distintos (sin embargo, esto seguirá sin poder almacenar múltiples ocurrencias del mismo objeto) o utilizar una matriz asociativa que asigne los valores a sus multiplicidades enteras (esto no podrá distinguir entre elementos iguales en absoluto).
Operaciones típicas en bolsas:
contains(B, x): comprueba si el elemento x está presente (al menos una vez) en la bolsa B.is_sub_bag(B1, B2): comprueba si cada elemento de la bolsa B 1 aparece en B 1 no más veces de lo que aparece en la bolsa B 2 ; a veces se denota como B 1 ⊑ B 2 .count(B, x): devuelve el número de veces que el elemento x aparece en la bolsa B ; a veces se denota como B # x .scaled_by(B, n): dado un número natural n , devuelve una bolsa que contiene los mismos elementos que la bolsa B , excepto que cada elemento que aparece m veces en B aparece n * m veces en la bolsa resultante; a veces se denota como n ⊗ B.union(B1, B2): devuelve una bolsa que contiene solo aquellos valores que aparecen en la bolsa B 1 o en la bolsa B 2 , excepto que el número de veces que aparece un valor x en la bolsa resultante es igual a ( B 1 # x) + ( B 2 # x); a veces se denota como B 1 ⊎ B 2 .
Multiconjuntos en SQL
En las bases de datos relacionales , una tabla puede ser un conjunto (matemático) o un multiconjunto, dependiendo de la presencia de restricciones de unicidad en algunas columnas (lo que la convierte en una clave candidata ).
SQL permite seleccionar filas de una tabla relacional: esta operación generalmente produce un multiconjunto, a menos que DISTINCTse utilice la palabra clave para forzar que todas las filas sean diferentes, o que la selección incluya la clave primaria (o una clave candidata).
En ANSI SQL, la MULTISETpalabra clave se puede utilizar para transformar una subconsulta en una expresión de colección:
SELECCIONAR expresión1 , expresión2 ... DE nombre_tabla ...es una selección general que se puede utilizar como expresión de subconsulta de otra consulta más general, mientras que
MULTISET ( SELECCIONAR expresión1 , expresión2 ... DE nombre_tabla ...)Transforma la subconsulta en una expresión de colección que se puede utilizar en otra consulta o asignar a una columna del tipo de colección adecuado.
Véase también
Notas
- ↑ Por ejemplo, en Python
pickse puede implementar en una clase derivada de la función integradasetde la siguiente manera:clase Set ( set ): def pick ( self ): return next ( iter ( self ))
- ↑ La inserción de elementos se puede realizar en tiempo O (1) simplemente insertándolos al final, pero si se evitan los duplicados, esto lleva tiempo O ( n ).
Referencias
- ↑ Python: pop()
- ↑ Gestión y procesamiento de estructuras de datos complejas: Tercer taller sobre sistemas de información e inteligencia artificial, Hamburgo, Alemania, del 28 de febrero al 2 de marzo de 1994. Actas, ed. Kai v. Luck, Heinz Marburger, pág. 76
- ↑ Problema de Python 7212 : Recuperar un elemento arbitrario de un conjunto sin eliminarlo; ver msg106593 sobre el nombre estándar
- ↑ Característica Ruby #4553 : Agregar Set#pick y Set#pop
- ↑ Síntesis inductiva de programas funcionales: planificación universal, plegado de programas finitos y abstracción de esquemas mediante razonamiento analógico, Ute Schmid , Springer, 21 de agosto de 2003, pág. 240
- ↑ Tendencias recientes en la especificación de tipos de datos: 10.º Taller sobre especificación de tipos de datos abstractos, en conjunto con el 5.º Taller COMPASS, S. Margherita, Italia, 30 de mayo - 3 de junio de 1994. Artículos seleccionados, Volumen 10, ed. Egidio Astesiano, Gianna Reggio, Andrzej Tarlecki, pág. 38.
- ↑ Ruby: flatten()
- ↑ Wang, Thomas (1997), Tabla hash lineal ordenada , archivado del original el 12 de enero de 2006
- ↑ Stephen Adams, " Conjuntos eficientes: un acto de equilibrio " , Journal of Functional Programming 3(4):553-562, octubre de 1993. Recuperado el 11 de marzo de 2015.
- ↑ "Especificación del lenguaje ECMAScript 2015 – ECMA-262 6.ª edición" . www.ecma-international.org . Consultado el 11 de julio de 2017 .
- Tipos de datos
- Tipos de datos compuestos
- Tipos de datos abstractos