¿Cómo puedo verificar si una lista es un subconjunto de otra?

185

Necesito verificar si una lista es un subconjunto de otra; todo lo que busco es un retorno booleano.

¿Prueba la igualdad en la lista más pequeña después de una intersección es la forma más rápida de hacer esto? El rendimiento es de suma importancia dada la cantidad de conjuntos de datos que deben compararse.

Agregar más hechos basados ​​en discusiones:

  1. ¿Alguna de las listas será la misma para muchas pruebas? Lo hace como uno de ellos es una tabla de búsqueda estática.

  2. ¿Tiene que ser una lista? No es así: la tabla de búsqueda estática puede ser cualquier cosa que funcione mejor. El dinámico es un dict del que extraemos las claves para realizar una búsqueda estática.

¿Cuál sería la solución óptima dado el escenario?

I Desconocido
fuente
Mencionas la velocidad, quizás numpy sería útil, dependiendo de tu uso.
ninMonkey
2
¿Los elementos de la lista son hashables?
wim
2
Si el orden es importante, este podría ser un buen comienzo - StackOverflow - La mejor manera de determinar si una secuencia está en otra secuencia en Python
¿necesita un subconjunto adecuado o pueden ser iguales?
törzsmókus
2
¿Por qué no establecer (list_a) .issubset (set (list_b))?
SeF

Respuestas:

127

La función de rendimiento que Python proporciona para esto es set.issubset. Sin embargo, tiene algunas restricciones que no aclaran si es la respuesta a su pregunta.

Una lista puede contener elementos varias veces y tiene un orden específico. Un conjunto no. Además, los conjuntos solo funcionan en objetos hashaable .

¿Está preguntando sobre un subconjunto o subsecuencia (lo que significa que querrá un algoritmo de búsqueda de cadenas)? ¿Alguna de las listas será la misma para muchas pruebas? ¿Cuáles son los tipos de datos contenidos en la lista? Y para el caso, ¿tiene que ser una lista?

Su otra publicación se cruza con un dict y la lista hizo los tipos más claros y recibió una recomendación para usar vistas de clave de diccionario para su funcionalidad de conjunto. En ese caso, se sabía que funcionaba porque las teclas del diccionario se comportaban como un conjunto (tanto que antes de tener conjuntos en Python usábamos diccionarios). Uno se pregunta cómo el problema se volvió menos específico en tres horas.

Yann Vernier
fuente
Me refiero solo a un subconjunto y el subconjunto funciona bien. Gracias. Sin embargo, tengo curiosidad acerca de 2 preguntas aquí. 1. ¿Alguna de las listas será la misma para muchas pruebas? Lo hace como uno de ellos es una tabla de búsqueda estática 2. ¿Necesita ser una lista? No es así: la tabla de búsqueda estática puede ser cualquier cosa que funcione mejor. El dinámico es un dict del que extraemos las claves para realizar una búsqueda estática. ¿Este hecho alterará la solución?
I Desconocido
No mucho. Las teclas de un diccionario son similares a un conjunto y ya están organizadas en una tabla hash, y por lo tanto, usar un conjunto para la parte estática no causará complicaciones adicionales. Básicamente, el hecho de que uno sea un dict significa que es posible que no necesite convertir la parte estática en un conjunto (puede verificar todo (itertools.imap (dict.has_key, mylist)) con el rendimiento O (n)).
Yann Vernier
No entiendo cómo esta (o cualquier otra solución basada en conjuntos) puede ser la respuesta aceptada aquí. La pregunta es sobre las listas y francamente creo que el subconjunto en "verificar si una lista es un subconjunto de la otra" no debe tomarse literalmente. Tras la conversión a conjuntos, cualquier información sobre elementos duplicados se pierde, sin embargo, si la lista inicial puede contenerlos, puede ser importante verificar si aparecen en la segunda lista y decir realmente que se pueden encontrar todos los elementos de una lista. dentro del otro. ¡Los sets no hacen eso!
inVader
El contexto importa; esto fue aceptado por ayudar al autor de la pregunta, y explicaba la distinción. Nos dijeron que los candidatos serían representables como conjuntos, por lo que era una tarea establecida. Su caso podría ser diferente, y la diferencia que usted menciona se resolvería usando múltiples conjuntos como colecciones.
Yann Vernier
141
>>> a = [1, 3, 5]
>>> b = [1, 3, 5, 8]
>>> c = [3, 5, 9]
>>> set(a) <= set(b)
True
>>> set(c) <= set(b)
False

>>> a = ['yes', 'no', 'hmm']
>>> b = ['yes', 'no', 'hmm', 'well']
>>> c = ['sorry', 'no', 'hmm']
>>> 
>>> set(a) <= set(b)
True
>>> set(c) <= set(b)
False
Yulan Liu
fuente
21
Esto se ve mejor y escribe más simple, pero el más rápido debería ser set(a).issubset(b) porque en este caso solo se convierte aen conjunto, pero no b, lo que ahorra tiempo. Puede usar timeitpara comparar el tiempo consumido en dos comandos. Por ejemplo, timeit.repeat('set(a)<set(b)', 'a = [1,3,5]; b = [1,3,5,7]', number=1000) y timeit.repeat('set(a).issubset(b)', 'a = [1,3,5]; b = [1,3,5,7]', number=1000)
Yulan Liu el
8
@YulanLiu: Odio decírtelo, pero lo primero que issubsethace es verificar si el argumento es un set/ frozenset, y si no lo es, lo convierte en temporal setpara comparar, ejecuta el cheque y luego tira el temporal set. Las diferencias de tiempo (si las hubiera) serían un factor de pequeñas diferencias en los costos de búsqueda de LEGB (encontrar setuna segunda vez es más costoso que la búsqueda de atributos en una existente set), pero es principalmente un lavado para entradas lo suficientemente grandes.
ShadowRanger
3
Si ambas listas contienen los mismos valores, entonces este devolverá falso, la condición debería establecerse (a) <= set (b) en su lugar
ssi-anik el
2
¿Cómo puede ser correcta esta respuesta? Pidió una lista, no un conjunto. Son completamente diferentes. ¿Qué pasa si a = [1, 3, 3, 5, 5] yb = [1, 3, 3, 3, 5]. La teoría de conjuntos es inapropiada para duplicados.
Eamonn Kenny
1
También señalaría que si a = [1,3,5] yb = [1,3,5], set (a) <set (b) devolverá False. Puede agregar el operador igual para manejar estos casos: es decir, set (a) <= set (b).
Jon
37
one = [1, 2, 3]
two = [9, 8, 5, 3, 2, 1]

all(x in two for x in one)

Explicación: Generador que crea booleanos recorriendo la lista onecomprobando si ese elemento está en la lista two. all()devuelve Truesi cada elemento es verdadero, de lo contrario False.

También hay una ventaja que alldevuelve False en la primera instancia de un elemento faltante en lugar de tener que procesar cada elemento.

voidnologo
fuente
Creo que la legibilidad y ser explícito de lo que está tratando de lograr set(one).issubset(set(two))es una gran solución. Con la solución que publiqué, debería poder usarla con cualquier objeto si tienen definidos los operadores de comparación adecuados.
voidnologo
44
Use una expresión generadora, no una lista de comprensión; el primero permitirá allun cortocircuito correctamente, el segundo realizará todas las comprobaciones incluso si fuera claro desde la primera comprobación que la prueba fallaría. Simplemente suelte los corchetes para obtener all(x in two for x in one).
ShadowRanger
¿Me equivoco o no puedes usar este método con los locales?
Homper
22

Asumiendo que los artículos son hashable

>>> from collections import Counter
>>> not Counter([1, 2]) - Counter([1])
False
>>> not Counter([1, 2]) - Counter([1, 2])
True
>>> not Counter([1, 2, 2]) - Counter([1, 2])
False

Si no le importan los elementos duplicados, por ejemplo. [1, 2, 2]y [1, 2]luego solo usa:

>>> set([1, 2, 2]).issubset([1, 2])
True

¿Prueba la igualdad en la lista más pequeña después de una intersección es la forma más rápida de hacer esto?

.issubsetSerá la forma más rápida de hacerlo. Verificar la longitud antes de la prueba issubsetno mejorará la velocidad porque todavía tiene elementos O (N + M) para recorrer y verificar.

jamylak
fuente
6

Una solución más sería usar a intersection.

one = [1, 2, 3]
two = [9, 8, 5, 3, 2, 1]

set(one).intersection(set(two)) == set(one)

La intersección de los conjuntos contendría de set one

(O)

one = [1, 2, 3]
two = [9, 8, 5, 3, 2, 1]

set(one) & (set(two)) == set(one)
SuperNova
fuente
2
one = [1, 2, 3]
two = [9, 8, 5, 3, 2, 1]

set(x in two for x in one) == set([True])

Si list1 está en la lista 2:

  • (x in two for x in one)genera una lista de True.

  • cuando hacemos un set(x in two for x in one)solo tiene un elemento (Verdadero).

SuperNova
fuente
2

La teoría de conjuntos es inapropiada para las listas ya que los duplicados darán como resultado respuestas incorrectas utilizando la teoría de conjuntos.

Por ejemplo:

a = [1, 3, 3, 3, 5]
b = [1, 3, 3, 4, 5]
set(b) > set(a)

no tiene sentido. Sí, da una respuesta falsa, pero esto no es correcto ya que la teoría de conjuntos solo compara: 1,3,5 versus 1,3,4,5. Debe incluir todos los duplicados.

En su lugar, debe contar cada aparición de cada elemento y hacer un chequeo mayor que igual. Esto no es muy costoso, ya que no está utilizando operaciones O (N ^ 2) y no requiere ordenación rápida.

#!/usr/bin/env python

from collections import Counter

def containedInFirst(a, b):
  a_count = Counter(a)
  b_count = Counter(b)
  for key in b_count:
    if a_count.has_key(key) == False:
      return False
    if b_count[key] > a_count[key]:
      return False
  return True


a = [1, 3, 3, 3, 5]
b = [1, 3, 3, 4, 5]
print "b in a: ", containedInFirst(a, b)

a = [1, 3, 3, 3, 4, 4, 5]
b = [1, 3, 3, 4, 5]
print "b in a: ", containedInFirst(a, b)

Luego de ejecutar esto obtienes:

$ python contained.py 
b in a:  False
b in a:  True
Eamonn Kenny
fuente
1

Como nadie ha considerado comparar dos cadenas, aquí está mi propuesta.

Por supuesto, es posible que desee comprobar si la tubería ("|") no forma parte de ninguna de las listas y tal vez eligió automáticamente otro carácter, pero se le ocurrió la idea.

Usar una cadena vacía como separador no es una solución ya que los números pueden tener varios dígitos ([12,3]! = [1,23])

def issublist(l1,l2):
    return '|'.join([str(i) for i in l1]) in '|'.join([str(i) for i in l2])
y4cine
fuente
0

Disculpe si llego tarde a la fiesta. ;)

Para verificar si uno set Aes un subconjunto de set B, Pythontiene A.issubset(B)y A <= B. Solo funciona sety funciona muy bien PERO se desconoce la complejidad de la implementación interna. Referencia: https://docs.python.org/2/library/sets.html#set-objects

Se me ocurrió un algoritmo para verificar si list Aes un subconjunto de los list Bsiguientes comentarios.

  • Para reducir la complejidad de encontrar un subconjunto, considero apropiado sortprimero ambas listas antes de comparar elementos para calificar para el subconjunto.
  • Me ayudó a breakla loopcuando el valor del elemento de la segunda lista B[j]es mayor que el valor del elemento de la primera lista A[i].
  • last_index_jse utiliza para comenzar de loopnuevo list Bdonde lo dejó por última vez. Ayuda a evitar comenzar las comparaciones desde el principio list B(que es, como puede suponer innecesario, comenzar list Bdesde el index 0siguiente iterations).
  • La complejidad será O(n ln n)cada una para ordenar ambas listas y O(n)para verificar el subconjunto.
    O(n ln n) + O(n ln n) + O(n) = O(n ln n).

  • El código tiene muchas printdeclaraciones para ver qué sucede en cada uno iterationde los loop. Estos son solo para entender.

Compruebe si una lista es un subconjunto de otra lista

is_subset = True;

A = [9, 3, 11, 1, 7, 2];
B = [11, 4, 6, 2, 15, 1, 9, 8, 5, 3];

print(A, B);

# skip checking if list A has elements more than list B
if len(A) > len(B):
    is_subset = False;
else:
    # complexity of sorting using quicksort or merge sort: O(n ln n)
    # use best sorting algorithm available to minimize complexity
    A.sort();
    B.sort();

    print(A, B);

    # complexity: O(n^2)
    # for a in A:
    #   if a not in B:
    #       is_subset = False;
    #       break;

    # complexity: O(n)
    is_found = False;
    last_index_j = 0;

    for i in range(len(A)):
        for j in range(last_index_j, len(B)):
            is_found = False;

            print("i=" + str(i) + ", j=" + str(j) + ", " + str(A[i]) + "==" + str(B[j]) + "?");

            if B[j] <= A[i]:
                if A[i] == B[j]:
                    is_found = True;
                last_index_j = j;
            else:
                is_found = False;
                break;

            if is_found:
                print("Found: " + str(A[i]));
                last_index_j = last_index_j + 1;
                break;
            else:
                print("Not found: " + str(A[i]));

        if is_found == False:
            is_subset = False;
            break;

print("subset") if is_subset else print("not subset");

Salida

[9, 3, 11, 1, 7, 2] [11, 4, 6, 2, 15, 1, 9, 8, 5, 3]
[1, 2, 3, 7, 9, 11] [1, 2, 3, 4, 5, 6, 8, 9, 11, 15]
i=0, j=0, 1==1?
Found: 1
i=1, j=1, 2==1?
Not found: 2
i=1, j=2, 2==2?
Found: 2
i=2, j=3, 3==3?
Found: 3
i=3, j=4, 7==4?
Not found: 7
i=3, j=5, 7==5?
Not found: 7
i=3, j=6, 7==6?
Not found: 7
i=3, j=7, 7==8?
not subset
Hamza Rashid
fuente
Si los ordena, ya no hay ninguna razón para usar una lista en lugar de un conjunto ...
LtWorf
0

El siguiente código verifica si un conjunto dado es un "subconjunto apropiado" de otro conjunto

 def is_proper_subset(set, superset):
     return all(x in superset for x in set) and len(set)<len(superset)
Leo Bastin
fuente
Gracias @YannVernier He modificado para incluir cheques vacíos tanto para el subconjunto como para el superconjunto, por lo que devuelve falso cuando ambos están vacíos.
Leo Bastin
¿ Pero por qué haces esto? Para que A sea un subconjunto de B, simplemente significa que A no contiene elementos que no estén en B, o de manera equivalente, todos los elementos en A también están en B. El conjunto vacío es, por lo tanto, un subconjunto de todos los conjuntos, incluido él mismo. Sus controles adicionales afirman que no lo es, y usted afirma que esto es de alguna manera ideal, pero es contrario a la terminología establecida. Cual es la ventaja?
Yann Vernier
Gracias @YannVernier Ahora el código verifica si un conjunto dado es un "subconjunto apropiado" de otro conjunto.
Leo Bastin
Esto es tan malo como las respuestas que dependen del uso de conjuntos . Si bien matemáticamente hablando, un conjunto es una colección de elementos distintos, podemos y no debemos confiar en esa suposición al verificar si una lista es parte de otra. Si la lista inicial contuviera duplicados, su función aún podría devolver True , incluso si el elemento en cuestión está presente en la segunda lista solo una vez. No creo que este sea el comportamiento correcto al intentar comparar listas.
inVader
0

En python 3.5 puedes hacer una [*set()][index]para obtener el elemento. Es una solución mucho más lenta que otros métodos.

one = [1, 2, 3]
two = [9, 8, 5, 3, 2, 1]

result = set(x in two for x in one)

[*result][0] == True

o solo con len y set

len(set(a+b)) == len(set(a))
SuperNova
fuente
0

Así es como sé si una lista es un subconjunto de otra, la secuencia me importa en mi caso.

def is_subset(list_long,list_short):
    short_length = len(list_short)
    subset_list = []
    for i in range(len(list_long)-short_length+1):
        subset_list.append(list_long[i:i+short_length])
    if list_short in subset_list:
        return True
    else: return False
Mindee
fuente
0

La mayoría de las soluciones consideran que las listas no tienen duplicados. En caso de que sus listas tengan duplicados, puede intentar esto:

def isSubList(subList,mlist):
    uniqueElements=set(subList)
    for e in uniqueElements:
        if subList.count(e) > mlist.count(e):
            return False     
    # It is sublist
    return True

Asegura que la sublista nunca tenga elementos diferentes a la lista o una mayor cantidad de un elemento común.

lst=[1,2,2,3,4]
sl1=[2,2,3]
sl2=[2,2,2]
sl3=[2,5]

print(isSubList(sl1,lst)) # True
print(isSubList(sl2,lst)) # False
print(isSubList(sl3,lst)) # False
Ignacio Alorre
fuente
-1

Si está preguntando si una lista está "contenida" en otra lista, entonces:

>>>if listA in listB: return True

Si está preguntando si cada elemento de la lista A tiene el mismo número de elementos coincidentes en la lista B, intente:

all(True if listA.count(item) <= listB.count(item) else False for item in listA)
DevPlayer
fuente
Esto no funciona para mi. Devuelve falso incluso si listA == listB
cass
@cass Solo he probado con cadenas. Prueba esto en tu máquina. pastebin.com/9whnDYq4
DevPlayer
Me refería a la parte "if listA in listB: return True", no a la segunda parte.
Cass
@cass Considere: ['uno', 'dos'] en ['uno', 'dos'] produce False. ['uno', 'dos'] en ['uno', 'dos', 'tres'] produce False. ['uno', 'dos'] en [['' uno ',' dos '],' tres '] produce Verdadero. Entonces sí, si listA == ListB, entonces listA en listB siempre devolverá False porque listA necesitaría ser un elemento de lista dentro de listB. Quizás esté pensando: listA en listB significa "¿Están los elementos en la lista A listados como elementos en la lista B. Ese no es el significado de la lista A en la lista B
DevPlayer
@cass Ah, veo cómo mi publicación confunde. La publicación original solicitó probar la lista A como un subconjunto de la lista B. Técnicamente, mi publicación es incorrecta según la pregunta de la publicación original. Para que sea correcta, la pregunta debería haber preguntado "if listA in [item0, item2, listA, item3, listA,]". No "elementos en ['a', 'b', 'c'] en ['d', 'c', 'f', 'a', 'b', 'a']".
DevPlayer
-2

Si a2 is subset of a1, entoncesLength of set(a1 + a2) == Length of set(a1)

a1 = [1, 2, 3, 4, 5]
a2 = [1, 2, 3]

len(set(a1)) == len(set(a1 + a2))
SuperNova
fuente