Я хочу найти все 5-значные строки, для которых
l0=["123","567","451"]
l1=["234","239","881"]
l2=["348","551","399"]
таким образом, должно получиться: ['12348', '12399']
.
Поэтому я написал функцию is_successor(a,b)
, которая проверяет, перекрываются ли a и b:
def is_successor(a:str,b:str)->bool:
"""tests if strings a and b overlap"""
return a[1:]==b[:2]
Затем я могу достичь своей цели, написав эту вложенную структуру цикла/проверки, которая в основном добавляется обратно к передним строкам и в результате дает все действительные строки:
pres=[]
for c in l2:
for b in l1:
if is_successor(b,c):
for a in l0:
if is_successor(a,b):
pres.append(a[0]+b[0]+c)
pres
l2
-> l0
, потому что в моих исходных данных списки становятся длиннее, чем ниже индекс, и, таким образом, я могу таким образом отфильтровать больше случаев раньше.l0,l1,l2
и проверяющий последовательность всех элементов a,b,c
одновременно, будет работать, но он проверяет гораздо больше ненужных комбинаций, чем моя текущая конструкция.Вопрос:
Как можно отвлечь этот вложенный цикл и вызов условной проверки? Есть ли питонический способ зафиксировать повторение for
-> is_successor()
?
Более крупный вход может быть:
primes=[2,3,5,7,11,13,17]
lsts=[
[
str(j).zfill(3)
for j in range(12,988)
if not j%prime
]
for prime in primes
]
Можете ли вы поделиться реалистичным большим вкладом?
@nocomment см. обновленный вопрос
Вы можете переписать его для работы с произвольным количеством списков следующим образом:
Сначала поместите элементы последнего списка в подсписки по 1 элементу (это упростит дальнейшую обработку).
ps = [[c] for c in l2]
print(ps) # [['348'], ['551'], ['399']]
Далее для каждого из оставшихся списков проверяем, какие элементы соответствуют элементам в ps
, и добавляем их в начало совпадающих элементов.
for l in [l1, l0]:
ps = [[a] + p for a in l for p in ps if is_successor(a, p[0])]
print(ps) # [['123', '234', '348'], ['123', '239', '399']]
Теперь соберите последнюю строку из списков.
pres = []
for p in ps:
pres.append(p[0]) # add entire element from l0
for el in p[1:]:
pres[-1] += el[-1] # add last character from following lists
print(pres) # ['12348', '12399']
Поскольку единственное место, где мы напрямую ссылаемся на входные списки, — это for l in [l1, l0]:
и создание исходного списка из последнего, это будет работать для любого количества списков, если эти строки будут обновлены.
Я бы использовал продукт itertools для определения функции, которая будет выполнять проверку и добавлять данные:
import itertools
def get_list(l1,l2):
#THIS FUNCTION WILL TEST ALL THE PRODUCTS AND RETURN ONLY THE CORRECT ONES
return [a+b[2:] for a,b in list(itertools.product(l1,l2)) if a[1:] == b[:2]]
то вы можете вложить функцию следующим образом:
get_list(l0,get_list(l1,l2))
вы получите ожидаемый результат: ['12348', '12399']
Обновлено: для длинного списка используйте сокращение.
назовем l
списком входных данных. В вашем конкретном случае l = list([l0,l1,l2])
Чтобы подать заявку, поменяйте местами список с конца до начала l.reverse()
и окончательный код:
from functools import reduce
reduce(lambda x,y:get_list(x,y),l)
Примечание: в этом случае вы хотите изменить входные данные функции «get_list», так как она будет сначала l2, затем l1, затем l0. Просто измените определение get_list следующим образом: def get_list(l2,l1)
Это не помогает мне решить проблему рефакторинга более глубоких вложений. Кроме того, он не имеет преимуществ исключения ненужных перестановок при добавлении большего количества списков.
это устраняет ненужные проверки при понимании списка. И вы можете легко добавить понимание списка во вложенность функций для более глубоких потребностей. и вы сохраните читабельность
Вы правы, я еще немного поигрался с кодом. Но в конце концов мне все равно придется выполнять вложенные вызовы, такие как get_list(l0, get_list(l1, ... get_list(ln, ln+1)...))
, вручную.
вы можете использовать сокращение. создать список списка, а затем применить сокращение; только что отредактировал свой ответ
Спасибо, концепция reduce()
была именно тем, что я искал.
Я изменил: a[1:] == b[:2]
на a[-2:] == b[:2]
в get_list()
и теперь это становится одной чистой строкой кода: pres=reduce(lambda lnp1,ln:get_list(ln,lnp1),reversed(l))
где ln(p1) будет списком индексов n (+1)
Если списки содержат больше элементов, я бы предложил создать словари-преемники для всех элементов в списке. Затем вы можете напрямую объединить этих преемников, не повторяя и не проверяя все комбинации.
from collections import defaultdict
l0=["123","567","451"]
l1=["234","239","881"]
l2=["348","551","399"]
def make_succ_dict(lst):
d = defaultdict(list)
for s in lst:
d[s[:2]].append(s)
return d
s1 = make_succ_dict(l1)
s2 = make_succ_dict(l2)
res = [a + b[2:] + c[2:] for a in l0 for b in s1[a[1:]] for c in s2[b[1:]]]
print(res) # ['12348', '12399']
(При этом по-прежнему используются вложенные циклы, но недопустимая комбинация не будет зацикливаться.)
Просто простой цикл:
l0=["123","567","451"]
l1=["234","239","881"]
l2=["348","551","399"]
pres = l2[:]
for L in l1, l0:
pres = [s[0] + p
for s in L
for p in pres
if s[1:] == p[:2]]
print(pres)
Я думаю, использование функций
map
иfilters
может помочь. Ссылка: book.pythontips.com/en/latest/map_filter.html