Как я могу проверить, является ли один список подмножеством другого?

185

Мне нужно проверить, является ли список подмножеством другого - булево возвращение - это все, что я ищу.

Является ли тестирование равенства в меньшем списке после пересечения самым быстрым способом сделать это? Производительность имеет первостепенное значение, учитывая количество наборов данных, которые необходимо сравнить.

Добавление дополнительных фактов на основе обсуждений:

  1. Будет ли один из списков одинаковым для многих тестов? Это как статическая таблица поиска.

  2. Это должен быть список? Это не так - статическая таблица поиска может быть любой, которая работает лучше всего. Динамический - это диктовка, из которой мы извлекаем ключи для статического поиска.

Каково было бы оптимальное решение с учетом сценария?

IUnknown
источник
Вы упоминаете скорость, возможно, numpy будет полезен, в зависимости от вашего использования.
ninMonkey
2
Являются ли элементы списка хешируемыми?
Вим
Вам нужно правильное подмножество, или они могут быть равны?
törzsmókus
2
Почему бы не установить (list_a) .issubset (set (list_b))?
SEF

Ответы:

127

Исполнительная функция Python обеспечивает это set.issubset. Однако у него есть несколько ограничений, из-за которых неясно, является ли это ответом на ваш вопрос.

Список может содержать элементы несколько раз и имеет определенный порядок. Набор не делает. Кроме того, наборы работают только с хэшируемыми объектами.

Вы спрашиваете о подмножестве или подпоследовательности (что означает, что вам нужен алгоритм поиска строк)? Будет ли один из списков одинаковым для многих тестов? Какие типы данных содержатся в списке? И в этом отношении, это должен быть список?

Ваш другой пост пересекается с диктовкой и списком, прояснил типы и получил рекомендацию использовать ключевые словарные представления для их функциональности, подобной множеству. В этом случае было известно, что он работает, потому что словарные ключи ведут себя как набор (настолько, что до того, как у нас были наборы в Python, мы использовали словари). Интересно, как вопрос стал менее конкретным за три часа?

Ян Вернье
источник
Я имею в виду только подмножество, а issubset работает просто отлично - спасибо. Однако мне любопытно, о двух вопросах здесь. 1. Будет ли один из списков одинаковым для многих тестов? Это как одна из статических таблиц поиска 2. Должен ли он быть списком? Это не так - статическая таблица поиска может быть любой, которая работает лучше всего. Динамический - это диктовка, из которой мы извлекаем ключи для статического поиска. Изменит ли этот факт решение?
Неизвестно
Немного. Ключи словаря подобны множеству и уже расположены в хеш-таблице, и поэтому использование набора для статической части не вызовет дополнительных сложностей. Фактически тот факт, что каждый из них является dict, означает, что вам может не потребоваться преобразовывать статическую часть в набор (вы можете проверить все (itertools.imap (dict.has_key, mylist)) с производительностью O (n)).
Ян Вернье
Я не понимаю, как это (или любое другое решение, основанное на наборах) может быть принятым ответом здесь. Вопрос о списках, и я откровенно думаю, что подмножество в «проверять, является ли один список подмножеством другого», не следует понимать буквально. При преобразовании в наборы любая информация о дублирующих элементах теряется, однако, если первоначальный список может содержать их, может быть важно проверить, присутствуют ли они и во втором списке, чтобы действительно сказать, что все элементы одного списка могут быть найдены в другой. Наборы не делают этого!
inVader
Контекст имеет значение; это было принято за помощь спрашивающему и объяснило разницу. Нам сказали, что кандидаты будут представлены в виде наборов, поэтому это была поставленная задача. Ваш случай может отличаться, и упомянутое вами различие будет решено с использованием мультимножеств, таких как collection.Counter.
Ян Вернье
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
Юлан Лю
источник
21
Это выглядит лучше и пишет проще, но самое быстрое должно быть, set(a).issubset(b) потому что в этом случае вы конвертируете только aв набор, но не так b, что экономит время. Вы можете использовать timeitдля сравнения времени, потребляемого в двух командах. Например, timeit.repeat('set(a)<set(b)', 'a = [1,3,5]; b = [1,3,5,7]', number=1000) и timeit.repeat('set(a).issubset(b)', 'a = [1,3,5]; b = [1,3,5,7]', number=1000)
Юлан Лю
8
@YulanLiu: Ненавижу разбивать его вам, но первое, что issubsetнужно сделать, это проверить, является ли аргументsetfrozensetsetset символом / , а если нет, он преобразует его во временное для сравнения, запускает проверку, а затем выбрасывает временный , Различия по срокам (если таковые имеются) будут фактором небольших различий в затратах на поиск в LEGB (поиск setво второй раз обходится дороже, чем поиск по атрибутам в существующем set), но это в основном промывка для достаточно больших входных данных.
ShadowRanger
3
Если оба списка содержат одинаковые значения, то этот из них вернет false, вместо этого должно быть установлено условие (a) <= set (b)
ssi-anik
2
Как этот ответ может быть правильным. Он попросил список, а не набор. Они совершенно разные. Что если a = [1, 3, 3, 5, 5] и b = [1, 3, 3, 3, 5]. Теория множеств не подходит для дубликатов.
Имонн Кенни
1
Я также хотел бы отметить, что если a = [1,3,5] и b = [1,3,5], set (a) <set (b) вернет False. Вы можете добавить оператор равенства для обработки этих случаев: т.е. set (a) <= set (b).
Джон
37
one = [1, 2, 3]
two = [9, 8, 5, 3, 2, 1]

all(x in two for x in one)

Объяснение: Генератор создает логические значения, просматривая список, oneесли этот элемент находится в списке two. all()возвращается, Trueесли каждый элемент правдив, в противном случае False.

Есть также преимущество, которое allвозвращает False в первом случае отсутствующего элемента, а не обрабатывает каждый элемент.

voidnologo
источник
Я думаю, что удобочитаемость и ясность в отношении того, чего вы пытаетесь достичь, set(one).issubset(set(two))- это отличное решение. С решением, которое я разместил, вы сможете использовать его с любыми объектами, если у них определены правильные операторы сравнения.
voidnologo
4
Используйте выражение генератора, а не понимание списка; первый позволит allправильно закорачивать, второй будет выполнять все проверки, даже если из первой проверки будет ясно, что проверка не пройдёт. Просто бросьте квадратные скобки, чтобы получить all(x in two for x in one).
ShadowRanger
Я ошибаюсь, или вы не можете использовать этот метод с местными жителями?
Homper
22

Предполагая, что элементы являются хэшируемыми

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

Если вам не нужны дубликаты, например. [1, 2, 2]а [1, 2]затем просто используйте:

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

Является ли тестирование равенства в меньшем списке после пересечения самым быстрым способом сделать это?

.issubsetбудет самый быстрый способ сделать это. Проверка длины перед тестированием issubsetне улучшит скорость, потому что у вас все еще есть O (N + M) элементов для итерации и проверки.

jamylak
источник
6

Еще одним решением будет использование intersection.

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

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

Пересечение множеств будет содержать set one

(ИЛИ)

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

set(one) & (set(two)) == set(one)
SuperNova
источник
2
one = [1, 2, 3]
two = [9, 8, 5, 3, 2, 1]

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

Если list1 находится в списке 2:

  • (x in two for x in one)генерирует список True.

  • когда мы делаем, set(x in two for x in one)имеет только один элемент (True).

SuperNova
источник
2

Теория множеств не подходит для списков, поскольку дубликаты приводят к неправильным ответам с использованием теории множеств.

Например:

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

не имеет смысла. Да, он дает ложный ответ, но это не правильно, поскольку теория множеств просто сравнивает: 1,3,5 против 1,3,4,5. Вы должны включить все дубликаты.

Вместо этого вы должны посчитать каждое вхождение каждого элемента и выполнить проверку больше чем равно. Это не очень дорого, потому что не использует O (N ^ 2) операций и не требует быстрой сортировки.

#!/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)

Затем, запустив это, вы получите:

$ python contained.py 
b in a:  False
b in a:  True
Имонн Кенни
источник
1

Поскольку никто не рассматривал сравнение двух строк, вот мое предложение.

Вы, конечно, можете проверить, не является ли труба («|») частью обоих списков, и, возможно, автоматически выбрали другой символ, но вы поняли идею.

Использование пустой строки в качестве разделителя не является решением, поскольку числа могут иметь несколько цифр ([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
источник
0

Простите, если я опаздываю на вечеринку. ;)

Чтобы проверить, является ли один set Aиз подмножеств set B, Pythonимеет A.issubset(B)и A <= B. Он работает setтолько и отлично работает, НО сложность внутренней реализации неизвестна. Ссылка: https://docs.python.org/2/library/sets.html#set-objects

Я придумал алгоритм для проверки list Aподмножества list Bсо следующими замечаниями.

  • Чтобы уменьшить сложность поиска подмножества, я считаю целесообразным sortсначала оба списка, прежде чем сравнивать элементы, чтобы претендовать на подмножество.
  • Это помогло мне , когда значение элемента второго списка больше , чем значение элемента из первого списка .breakloopB[j]A[i]
  • last_index_jиспользуется для начала loopс того места, list Bгде он остановился в последний раз. Это помогает избежать запуска сравнений с самого начала list B(что, как вы могли догадаться , ненужными, чтобы начать list Bс index 0в последующем iterations.)
  • Сложность будет O(n ln n)каждая для сортировки обоих списков и O(n)для проверки подмножества.
    O(n ln n) + O(n ln n) + O(n) = O(n ln n),

  • Код имеет много printутверждений, чтобы увидеть, что происходит на каждом iterationиз loop. Они предназначены только для понимания.

Проверьте, является ли один список подмножеством другого списка

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");

Вывод

[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
Хамза Рашид
источник
Если вы сортируете их, то больше нет причин использовать список вместо набора ...
LtWorf
0

Код ниже проверяет, является ли данный набор «правильным подмножеством» другого набора

 def is_proper_subset(set, superset):
     return all(x in superset for x in set) and len(set)<len(superset)
Лео Бастин
источник
Спасибо @YannVernier Я изменил, чтобы включить пустые проверки как для подмножества, так и для надмножества, поэтому он возвращает false, когда оба пусты.
Лев Бастин
Но почему ты это делаешь? Если A быть подмножеством B, просто означает, что A не содержит элементов, которых нет в B, или эквивалентно, все элементы в A также находятся в B. Следовательно, пустой набор является подмножеством всех наборов, включая самого себя. Ваши дополнительные проверки утверждают, что это не так, и вы утверждаете, что это как-то идеально, но это противоречит установленной терминологии. В чем преимущество?
Ян Вернье
Спасибо @YannVernier Теперь код проверяет, является ли данный набор «правильным подмножеством» другого набора.
Лев Бастин
Это так же плохо, как ответы, основанные на использовании наборов . Математически говоря, набор представляет собой набор отдельных элементов, мы можем и не должны полагаться на это предположение при проверке, является ли один список частью другого. Если бы первоначальный список содержал дубликат, ваша функция могла бы вернуть True , даже если рассматриваемый элемент присутствует во втором списке только один раз. Я не думаю, что это правильное поведение при попытке сравнить списки.
inVader
0

В Python 3.5 вы можете сделать, [*set()][index]чтобы получить элемент. Это гораздо более медленное решение, чем другие методы.

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

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

[*result][0] == True

или просто с лен и установить

len(set(a+b)) == len(set(a))
SuperNova
источник
0

Вот как я знаю, если один список является подмножеством другого, последовательность имеет значение для меня в моем случае.

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
источник
0

Большинство решений считают, что списки не имеют дубликатов. Если в ваших списках есть дубликаты, вы можете попробовать это:

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

Это гарантирует, что подсписок никогда не имеет элементов, отличных от списка, или большего количества общего элемента.

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
Игнасио Алорре
источник
-1

Если вы спрашиваете, содержится ли один список в другом списке, то:

>>>if listA in listB: return True

Если вы спрашиваете, имеет ли каждый элемент в listA равное количество совпадающих элементов в listB, попробуйте:

all(True if listA.count(item) <= listB.count(item) else False for item in listA)
DevPlayer
источник
Это не работает для меня. Возвращает false, даже если listA == listB
cass
@cass Я тестировал только со строками. Попробуйте это на своей машине. pastebin.com/9whnDYq4
DevPlayer
Я имел в виду часть «if listA in listB: return True», а не вторую.
касс
@cass Подумайте: ['one', 'two'] в ['one', 'two'] выдает False. ['one', 'two'] в ['one', 'two', 'three'] выдает False. ['one', 'two'] в [['one', 'two'], 'three'] возвращает True. Так что да, если listA == ListB, то listA в listB всегда будет возвращать False, потому что listA должен быть элементом списка в listB. Возможно, вы думаете: listA в listB означает «Являются ли элементы в списке A перечисленными как элементы в списке B. Это не означает, что listA в списке B
DevPlayer
@ Cass Ах, я вижу, как мой пост сбивает с толку. Оригинальный пост попросил проверить, чтобы listA был подмножеством listB. Технически мой пост ошибочен, если исходить из оригинального вопроса. Для правильности этого вопроса нужно было бы спросить «если listA в [item0, item2, listA, item3, listA,]». Не "элементы в ['a', 'b', 'c'] в ['d', 'c', 'f', 'a', 'b', 'a']".
DevPlayer
-2

Если a2 is subset of a1тоLength 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
источник