Як я можу перевірити, чи є один список підмножиною іншого?


185

Мені потрібно перевірити, чи список є підмножиною іншого - булеве повернення - все, що я прагну.

Чи тестування рівності за меншим списком після перетину є найшвидшим способом зробити це? Продуктивність має надзвичайно важливе значення, враховуючи кількість наборів даних, які потрібно порівняти.

Додавання додаткових фактів на основі дискусій:

  1. Чи буде один із списків однаковим для багатьох тестів? Один із них - це статична таблиця пошуку.

  2. Чи потрібен це список? Це не так - статична таблиця пошуку може бути будь-якою, яка працює найкраще. Динамічний - це дікт, з якого ми дістаємо ключі для здійснення статичного пошуку.

Яке було б оптимальне рішення з огляду на сценарій?


Ви згадуєте швидкість, можливо, нудний був би корисний, залежно від вашого використання.
ninMonkey

2
Чи доступні елементи списку?
Вім


вам потрібна належна підмножина, або вони можуть бути рівними?
törzsmókus

2
Чому б не встановити (list_a) .issubset (set (list_b))?
СеФ

Відповіді:


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що потрібно зробити - це перевірити, чи аргумент є a set/ frozenset, а якщо ні, він перетворює його у тимчасовий setдля порівняння, запускає перевірку, а потім викидає тимчасовий set. Різниці в часі (якщо такі є) були б фактором невеликих різниць у витратах пошуку LEGB (пошук setвдруге коштує дорожче, ніж пошук атрибутів у існуючих set), але це здебільшого миття для достатньо великих входів.
ShadowRanger

3
Якщо обидва списки містять однакові значення, то цей повернеться неправдивим, умова повинна бути встановлена ​​(a) натомість
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якщо кожен предмет є truthy, інакше False.

Також є перевага, що allповертає False на першому екземплярі відсутнього елемента, а не обробляти кожен елемент.


Я думаю, що зрозумілість і чіткість того, що ви намагаєтесь досягти, set(one).issubset(set(two))- це чудове рішення. За допомогою рішення, яке я розмістив, ви зможете використовувати його з будь-якими об'єктами, якщо вони мають визначені належні оператори порівняння.
voidnologo

4
Використовуйте вираз генератора, а не розуміння списку; перший дозволить allкоротко замикатися належним чином, останній виконає всі перевірки, навіть якщо з першої перевірки було б зрозуміло, що тест не вдасться. Просто опустіть квадратні дужки, щоб отримати all(x in two for x in one).
ShadowRanger

Я помиляюся, або ви не можете використовувати цей метод разом із місцевими жителями?
Омпер

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), які можна переглядати та перевіряти.


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)

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.

  • коли ми робимо a set(x in two for x in one)має лише один елемент (True).


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

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, я змінив, щоб включати порожні чеки як для підмножини, так і для суперсети, щоб він повертав помилку, коли обидва порожні.
Лев Бастін

Але чому ти це робиш? Якщо A є підмножиною B, просто означає, що A не містить елементів, які не містяться в B, або, що еквівалентно, всі елементи в A також знаходяться в B. Порожній набір, таким чином, є підмножиною всіх наборів, включаючи і себе. Ваші додаткові чеки стверджують, що це не так, і ви стверджуєте, що це якось ідеально, але це суперечить усталеній термінології. Яка перевага?
Yann Vernier

Дякую @YannVernier Тепер код перевіряє, чи є даний набір "належним підмножиною" іншого набору.
Лев Бастін

Це так само погано, як і відповіді, що спираються на використання наборів . Математично кажучи, набір - це сукупність різних елементів, але ми не можемо покладатися на це припущення, перевіряючи, чи є один список частиною іншого. Якщо початковий список повинен містити дублікат, ваша функція все одно може повернути True , навіть якщо відповідний елемент буде присутній у другому списку лише один раз. Я не думаю, що це правильна поведінка при спробі порівняння списків.
inVader

0

У python 3.5 ви можете зробити a, [*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

len(set(a+b)) == len(set(a))

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

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

Якщо ви запитуєте, чи кожен елемент у спискуA має рівну кількість відповідних елементів у спискуB, спробуйте:

all(True if listA.count(item) <= listB.count(item) else False for item in listA)

Це не працює для мене. Повертає помилку, навіть якщо listA == listB
cass

@cass Я тестував лише рядки. Спробуйте це на своїй машині. pastebin.com/9whnDYq4
DevPlayer

Я мав на увазі частину "if listA у listB: повернути True", а не другу частину.
кас

@cass Поміркуйте: ['один', 'два'] в ['one', 'two'] дає помилковий. ['один', 'два'] у ['один', 'два', 'три'] дає помилкове значення. ['один', 'два'] у [['один', 'два'], 'три'] дає істину. Тож так, якщо listA == ListB, то listA у listB завжди поверне помилкове, оскільки listA повинен бути елементом списку в спискуB. Можливо, ви замислюєтеся: listA у listB означає "Чи перераховані елементи в спискуA, як список у спискуB. Це не сенс listA у listB
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))
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.