Pythonic подход, позволяющий избежать вложенных циклов для объединения строк

Я хочу найти все 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
]

Я думаю, использование функций map и filters может помочь. Ссылка: book.pythontips.com/en/latest/map_filter.html

Tyson Z 30.07.2024 09:35

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

no comment 30.07.2024 14:04

@nocomment см. обновленный вопрос

DuesserBaest 30.07.2024 14:39
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
2
3
92
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

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

Сначала поместите элементы последнего списка в подсписки по 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)

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

DuesserBaest 30.07.2024 11:16

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

AlexVI 30.07.2024 11:35

Вы правы, я еще немного поигрался с кодом. Но в конце концов мне все равно придется выполнять вложенные вызовы, такие как get_list(l0, get_list(l1, ... get_list(ln, ln+1)...)), вручную.

DuesserBaest 30.07.2024 11:38

вы можете использовать сокращение. создать список списка, а затем применить сокращение; только что отредактировал свой ответ

AlexVI 30.07.2024 12:41

Спасибо, концепция reduce() была именно тем, что я искал.

DuesserBaest 31.07.2024 09:53

Я изменил: 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)

DuesserBaest 31.07.2024 10:01

Если списки содержат больше элементов, я бы предложил создать словари-преемники для всех элементов в списке. Затем вы можете напрямую объединить этих преемников, не повторяя и не проверяя все комбинации.

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)

Попробуйте это онлайн!

Другие вопросы по теме