Как узнать, входят ли два элемента в один список

У меня есть несколько списков, каждый из которых содержит несколько городов. Мне нужно проверить любые два случайных элемента, если они принадлежат к одному списку.

Простой пример:

list1 = ['London', 'Manchester', 'Liverpool', 'Edimburgh']
list2 = ['Dublin', 'Cork', 'Galway']
list3 = ['Berlin', 'Munich', 'Frankfurt', 'Paris', 'Milan', 'Rome', 'Madrid', 'Barcelona', 'Lisbon', ...]
list4 = ['Washington', 'New York', 'San Francisco', 'LA', 'Boston', ...]

Ожидаемые результаты:

> in_same_group('London', 'Liverpool')
> True
>
> in_same_group('Berlin', 'Washington')
> False

Функция вызывается очень часто, поэтому скорость критическая. Самый большой список может содержать до 1000 элементов.

Каким будет наиболее эффективный способ сделать это?


Это то, что я пробовал до сих пор, но он слишком медленный:

def in_same_group(city1, city2):

    same_group = False
    for this_list in [list1, list2, list3...]:
        if city1 in this_list and city2 in this_list:
            return True

    return same_group

Ответы

Ответ 1

Диктор множеств индексов

Здесь представлено предложение Horia и мое оригинальное. Вы можете определить dict с городами как ключ и наборы индексов в качестве значений:

list1 = ['London', 'Manchester', 'Liverpool', 'Edimburgh']
list2 = ['Dublin', 'Cork', 'Galway', 'Paris', 'Rome']
list3 = ['Berlin', 'Munich', 'Frankfurt', 'Paris', 'Milan', 'Rome', 'Madrid', 'Barcelona', 'Lisbon'] 
list4 = ['Washington', 'New York', 'San Francisco', 'LA', 'Boston']
# Note that 'Paris' and 'Rome' are both in list2 and list3

groups = [list1, list2, list3, list4]

indices = {}

for i, group in enumerate(groups):
    for city in group:
        indices.setdefault(city, set()).add(i)

Структура компактна и выглядит следующим образом:

print(indices)
#{'London': {0}, 'Manchester': {0}, 'Liverpool': {0}, 'Edimburgh': {0}, 'Dublin': {1}, 'Cork': {1}, 'Galway': {1}, 'Paris': {1, 2}, 'Rome': {1, 2}, 'Berlin': {2}, 'Munich': {2}, 'Frankfurt': {2}, 'Milan': {2}, 'Madrid': {2}, 'Barcelona': {2}, 'Lisbon': {2}, 'Washington': {3}, 'New York': {3}, 'San Francisco': {3}, 'LA': {3}, 'Boston': {3}}

Для любой пары городов вы можете получить набор общих индексов благодаря установить пересечение:

def common_groups(city1, city2):
    return indices.get(city1, set()) & indices.get(city2, set())

print(common_groups('London', 'Liverpool'))
#  {0}
print(common_groups('London', 'Paris'))
#  set()
print(common_groups('Cork', 'Paris'))
# {1}
print(common_groups('Rome', 'Paris'))
# {1, 2}
print(common_groups('Rome', 'Nowhere'))
# set()

Пустое множество ложно в Python.

С городами n создание dict будет O(n), потребность в пространстве должна быть O(n), а производительность поиска будет O(1). В качестве бонуса запрос не просто возвращает логический, а набор индексов.

Наконец, благодаря установленным пересечениям этот метод также будет работать, если вы хотите проверить, что три или более города находятся в одной группе.

Ответ 2

Один из подходов заключался бы в построении карты из города на этот номер группы. Итак, вы построили что-то вроде:

mapping = {
    'London': 1,
    ...,
    'Berlin': 3
    ...
}

Тогда ваша функция in_same_group может быть:

def in_same_group(item1, item2):
    gr1 = mapping[item1]
    gr2 = mapping[item2]
    return gr1 == gr2

С точки зрения скорости это довольно быстро, так как это всего лишь два словарных поиска, которые очень быстрые в Python и одно сравнение, что опять же довольно быстро. В функции big-Oh это O(1).

Но он предполагает, что элемент является только частью одной группы. Это похоже на пример, который вы указали.

Вам нужно потратить дополнительное время и память на фактическое построение карты. Но он будет амортизирован по всем вызовам in_same_group. OTOH, вам, вероятно, не удастся создать структуру индексации, независимо от вашего подхода.


Код для построения отображения:

def build_mapping(groups):
    mapping = {}
    for i in range(0, len(groups)):
        for g in groups[i]:
            mapping[g] = i
    return mapping

Это не самый красивый код, но он выполняет свою работу.

Ответ 3

Сначала используйте наборы, а не списки (и используйте список наборов вместо отдельных переменных).

master_list = []
master_list.append(set(['London', 'Manchester', 'Liverpool', 'Edimburgh']))
master_list.append(set(['Dublin', 'Cork', 'Galway']))
master_list.append(set(['Berlin', 'Munich', 'Frankfurt', 'Paris', 'Milan', 'Rome', 'Madrid', 'Barcelona', 'Lisbon', ...]))
master_list.append(set(['Washington', 'New York', 'San Francisco', 'LA', 'Boston', ...]))

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

Во-вторых, постройте dict, который отображает каждый элемент в его набор:

# E.g., index['London'] == set(['London', 'Manchester', ...])
index = dict((item, s) for s in master_list for item in s)

Теперь вам просто нужно проверить, принадлежат ли оба элемента одному набору.

def in_same_group(i1, i2):
    return index[i1] is index[i2]

Ответ 4

Вы можете выполнять итерацию по спискам и определять, находятся ли в ней поисковые запросы. Затем верните булево значение только что сформированного списка.

def search(s1, s2):
   list1 = ['London', 'Manchester', 'Liverpool', 'Edimburgh']
   list2 = ['Dublin', 'Cork', 'Galway']
   list3 = ['Berlin', 'Munich', 'Frankfurt', 'Paris', 'Milan', 'Rome', 'Madrid', 'Barcelona', 'Lisbon']
   list4 = ['Washington', 'New York', 'San Francisco', 'LA', 'Boston']
   return bool([i for i in [list1, list2, list3, list4] if s1 in i and s2 in i])

Ответ 5

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

  • одно исследование словаря
  • в исследованиях сдерживания одиночного набора.

Поскольку эти обращения оптимизированы для словарей и наборов, исследование должно быть таким же быстрым, как это возможно

Код может быть:

import collections

h = collections.defaultdict(set)

lists = [list1, list2, list3, list4]
for l in lists:
    for town in l:
        for other in l:
            if town != other:
                h[town].add(other)

Теперь эта функция проста:

def in_same_group(t1, t2):
    return t2 in h[t1]

Ответ 6

Привет, вы можете попробовать использовать Pandas для этого

import pandas as pd


list1 = ['London', 'Manchester', 'Liverpool', 'Edimburgh']

list2 = ['Dublin', 'Cork', 'Galway']

list3 = ['Berlin', 'Munich', 'Frankfurt', 'Paris', 'Milan', 'Rome', 'Madrid', 'Barcelona', 'Lisbon']

list4 = ['Washington', 'New York', 'San Francisco', 'LA', 'Boston']


----------


a = pd.Series(list(list1))

b = pd.Series(list(list2))

c = pd.Series(list(list3))

d = pd.Series(list(list4))

lists = [a,b,c,d]


----------


for i in lists:

    if (i.isin(['London']).any()) and (i.isin(['Manchester']).any()) == True:
        print('Same Group')
    else:
        print('Different Group')

Результаты

Same Group

Различные группы

Различные группы

Различные группы

Ответ 7

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

Кроме того, если один список намного больше всех остальных, есть потенциал для ускорения работы.

Я думаю, что следующее может быть быстрее, , если один из списков намного больше остальных. Предполагается, что list3 намного больше остальных.

list1 = ['London', 'Manchester', 'Liverpool', 'Edimburgh']
list2 = ['Dublin', 'Cork', 'Galway']
list3 = ['Berlin', 'Munich', 'Frankfurt', 'Paris', 'Milan', 'Rome', 'Madrid', 'Barcelona', 'Lisbon', ...] #assuming list3 is much larger than all others
list4 = ['Washington', 'New York', 'San Francisco', 'LA', 'Boston', ...]

Возможна:

def in_same_group(city1, city2):

    for group in [list1, list2, list4]: #note that list3, the largest, is skipped
            if city1 in group and city2 not in group:
                return False

    return True #if this point is reached, both cities belong to the biggest group

Ответ 8

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

Попробуйте следующее:

In [20]: city_lists = {city_name:list1 for city_name in list1}
In [21]: city_lists.update({city_name:list2 for city_name in list2})
In [22]: city_lists
Out[22]: 
{'Cork': ['Dublin', 'Cork', 'Galway'],
 'Dublin': ['Dublin', 'Cork', 'Galway'],
 'Edimburgh': ['London', 'Manchester', 'Liverpool', 'Edimburgh'],
 'Galway': ['Dublin', 'Cork', 'Galway'],
 'Liverpool': ['London', 'Manchester', 'Liverpool', 'Edimburgh'],
 'London': ['London', 'Manchester', 'Liverpool', 'Edimburgh'],
 'Manchester': ['London', 'Manchester', 'Liverpool', 'Edimburgh']}
In [23]: city_lists['Cork'] is city_lists['Dublin']
Out[23]: True
In [24]: city_lists['Cork'] is city_lists['London']
Out[24]: False

Это очень эффективно. Если вы визуализируете код с http://pythontutor.com, вы увидите, что dict содержит ссылки на исходные списки, но сами списки не копируются.