Объединение списков в цепочку в Python с сопоставлением условий

#python #list

#Python #Список

Вопрос:

Предпосылка:

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

У меня есть двенадцать списков с тысячами парных записей в каждом списке, и мне нужно найти все возможные цепочки записей в списках.

Порядок списков относительно друг друга фиксирован — list3 следует за list2 следует за list1 и т.д. У меня есть решение, но оно явно старомодное и не очень Pythonic — и для его запуска требуется вечность. Мой последний запуск занял 3 часа и 40 минут, что совершенно неосуществимо.

Итак, я ищу любые решения, которые были бы более эффективными и (надеюсь) ускорили процесс до чего-то управляемого.

Формат ввода:

Входные данные форматируются как 2D-списки, например:

 l1 = [ ['SHORT', 'FILM'], ['LEASE', 'BACK'], ['SHELF', 'LIFE'], ['HOLDS', 'FAST'], ... ]
l2 = [ ['BOAT', 'DECK'], ['FAST', 'FOOD'], ['FILM', 'PROP'], ['CHOW', 'LINE'], ... ]
l3 = [ ['FOOD', 'DRIVE'], ['PROP', 'PLANE'], ['GOAL', 'LINES'], ['WRAP', 'PARTY'], ... ]
.
.
.
l12 = [ [ ...
 

Вывод:

Мне нужно найти все возможные цепочки слов, которые соответствуют второму слову каждой пары в списке с первым словом в следующем списке и т. Д., Последовательно соединяя все пути.

Код, который у меня есть (сокращен только до трех списков для краткости), выглядит следующим образом:

 l1 = [['SHORT', 'FILM'], ['LEASE', 'BACK'], ['SHELF', 'LIFE'], ['HOLDS', 'FAST']]
l2 = [['BOAT', 'DECK'], ['FAST', 'FOOD'], ['FILM', 'PROP'], ['CHOW', 'LINE']]
l3 = [['FOOD', 'DRIVE'], ['PROP', 'PLANE'], ['GOAL', 'LINES'], ['WRAP', 'PARTY']]

ans = []

for i in range(len(l1)):
        for j in range(len(l2)):
                if  l1[i][1] == l2[j][0]:
                        for k in range(len(l3)):
                                if l2[j][1] == l3[k][0]:
                                        item = [l1[i][0], l1[i][1], l2[j][1], l3[k][1]]
                                        ans.append(item)

print(ans)
 

Что дает результат::

 [['SHORT', 'FILM', 'PROP', 'PLANE'], ['HOLDS', 'FAST', 'FOOD', 'DRIVE']]
 

Есть предложения по более эффективному и быстрому (!) способу сделать это?

ДОПОЛНИТЕЛЬНАЯ ИНФОРМАЦИЯ И ОГРАНИЧЕНИЯ

Я узнал более подробную информацию об этом, которая предоставляет дополнительные ограничения, которые изменят код. Во-первых, на самом деле нет 12 списков, есть три списка, которые повторяются (по порядку) 4 раза: list1, list2, list3, list1, list2, list3 и т.д.

Кроме того, список должен быть «обведен» обратно в конце, чтобы второе слово пары в list12 (такое же, как list3) совпадало с первым словом в паре list1 (l12[j][1] == l1[k][0]) .

Например, для того, чтобы цепочка:

 HORSE BACK FLIP PHONE HOME RUNS SHORT FILM PROP PLANE RIDE HIGH
 

чтобы быть допустимым решением, HIGH HORSE оно должно быть в list12 / list3.

Кроме того, поскольку три списка повторяются четыре раза и обводятся по кругу, четыре цикла

 HORSE BACK FLIP PHONE HOME RUNS SHORT FILM PROP PLANE RIDE HIGH
PHONE HOME RUNS SHORT FILM PROP PLANE RIDE HIGH HORSE BACK FLIP
SHORT FILM PROP PLANE RIDE HIGH HORSE BACK FLIP PHONE HOME RUNS
PLANE RIDE HIGH HORSE BACK FLIP PHONE HOME RUNS SHORT FILM PROP
 

считаются одним и тем же циклом слов, и дубликаты должны быть удалены.

Фрагмент кода, который я использую, является:

 for i in range(len(list1)):
  for j in range(len(list2)):
    if  list1[i][1] == list2[j][0]:
      for k in range(len(list3)):
        if list2[j][1] == list3[k][0]:
          for l in range(len(list1)):
            if list3[k][1] == list1[l][0]:
              for m in range(len(list2)):
                if list1[l][1] == list2[m][0]:
                  for n in range(len(list3)):
                    if list2[m][1] == list3[n][0]:
                      for o in range(len(list1)):
                        if list3[n][1] == list1[o][0]:
                          for p in range(len(list2)):
                            if list1[o][1] == list2[p][0]:
                              for q in range(len(list3)):
                                if list2[p][1] == list3[q][0]:
                                  for r in range(len(list1)):
                                    if list3[q][1] == list1[r][0]:
                                      for s in range(len(list2)):
                                        if list1[r][1] == list2[s][0]:
                                          for t in range(len(list3)):
                                            if list2[s][1] == list3[t][0] and list3[t][1] == list1[i][0]:
                                              item = [list1[i][0], list1[i][1], list2[j][1], list3[k][1], list1[l][1], list2[m][1], list3[n][1], list1[o][1], list2[p][1], list3[q][1], list1[r][1], list2[s][1]]
                                              ans.append(item)
 

Что дает мне все циклы, но не удаляет дубликаты. И … для завершения требуется несколько часов.

Есть предложения?

Комментарии:

1. Даже не заглядывая глубже, я могу сказать вам, чтобы вы начали изучать протокол итерации. Python позволяет избежать использования индексов

2. Отображается ли каждое слово только один раз в первой позиции в одном списке?

3. @Steve Нет. В списках может быть любая комбинация слов. И слова могут повторяться либо в первой, либо во второй позициях. Однако два слова в каждой паре никогда не будут одним и тем же словом. Например, вы не увидите [‘BLUE’, ‘BLUE’] . И в парах слов в каждом списке нет дубликатов. Но одни и те же пары слов могут отображаться в отдельных списках.

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

5. Если вы не можете сделать это самостоятельно, я мог бы попробовать, если вы можете предоставить мне набор данных, в котором есть несколько повторяющихся первых слов, и вы также можете дать мне ожидаемый результат. Чем больше тестовых данных вы можете мне предоставить, тем лучше, если вы можете дать мне ожидаемый результат, в котором вы уверены. Я делаю TDD довольно религиозно, так что правильный ожидаемый результат для меня — чистое золото.

Ответ №1:

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

Вот как это сделать для приведенного вами сокращенного примера. Это должно работать так же хорошо (на самом деле намного лучше) для ваших 12 длинных списков:

 l1 = [['SHORT', 'FILM'], ['LEASE', 'BACK'], ['SHELF', 'LIFE'], ['HOLDS', 'FAST']]
l2 = [['BOAT', 'DECK'], ['FAST', 'FOOD'], ['FILM', 'PROP'], ['CHOW', 'LINE']]
l3 = [['FOOD', 'DRIVE'], ['PROP', 'PLANE'], ['GOAL', 'LINES'], ['WRAP', 'PARTY']]

# Define a list of our lists so we can iterate over them
lists = [l1, l2, l3]

# For each list except the first one, create an equivalent dictionary where the first value
# in each list entry is a key, and the second value is the corresponding value for that key.
dicts = [{e[0]:e[1] for e in l} for l in lists[1:]]

# Attempt to find a full chain for one of the pairs in list #1
def do_one_chain(pair):
    chain = []
    chain.extend(pair)
    for d in dicts:
        if pair[1] not in d:
            return None
        pair = (pair[1], d[pair[1]])
        chain.append(pair[1])
    return chain

# Iterate over our first list...
ans = []
for pair in l1:
    # Apply our search function to a pair from list #1
    r = do_one_chain(pair)
    # If a chain was found, add it to the answer list
    if r:
        ans.append(r)

# Print the found chains
print(ans)
 

Результат:

 [['SHORT', 'FILM', 'PROP', 'PLANE'], ['HOLDS', 'FAST', 'FOOD', 'DRIVE']]
 

Что приятно в этом, так это то, что решение не только намного эффективнее, но и намного чище и компактнее. Ни один из кодов не изменяется при добавлении дополнительных уровней. Вам просто нужно добавить новые уровни в lists список. Я знаю, что это то, что вы искали, опасаясь необходимости растягивать эти вложенные if операторы до 12 уровней, верно?

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

Комментарии:

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

2. Конечно. Это не проблема. Мое ожидание было бы противоположным, но это не имеет значения. В любом случае, как вы уже видите, это так же просто. — Я только что попробовал это с образцами данных. К сожалению, нет примеров, которые идут на два уровня, но не на три. Думаю, я мог бы это изменить 🙂

3. @lightalchemist Я просто попытался изменить данные так, чтобы получилась двухуровневая цепочка. Это работает просто отлично. Но я также понимаю, что OP сообщает нам результат, который он хочет, что я и делаю, не возвращая короткие цепочки. Он дает нам ожидаемый результат, и он не включает ничего, кроме полных цепочек. Все еще возможно, что он хочет исключить цепочки только одной длины, но включить что-нибудь длиннее. Однако просто возврат chain вместо None не будет соответствовать его указанному результату, так как все цепочки с одной длиной отображаются в списке.

4. @Steve. Это было полезно. Меня интересуют только полноразмерные (из 12 слов) цепочки. Не цепочки частичной длины. Кроме того, я обнаружил больше ограничений, которые могут существенно изменить это. Я отредактирую свой вопрос с дополнительными ограничениями.

5. @Steve Прошлой ночью я запустил свою версию 12-уровневого кода вложенных циклов с тремя списками, которые у меня есть. Длина списка: list1 (6 614); list2 (8 513); и list3 (231). Мой код занял 3 часа и 30 минут и обнаружил 5916 возможных циклов, включая дубликаты (я еще не понял, как удалить дубликаты), что дало 1479 уникальных циклов.