Search code examples
python

Pythonic approach to avoid nested loops for string concatenation


I want to find all 5-digit strings, for which

  • the first three digits are in my first list,
  • the second trough fourth are in my second and
  • the third to fifth are in my last list:
l0=["123","567","451"]
l1=["234","239","881"]
l2=["348","551","399"]

should thus yield: ['12348', '12399'].

I have therefore written a function is_successor(a,b) that tests if a and b overlap:

def is_successor(a:str,b:str)->bool:
    """tests if strings a and b overlap"""
    return a[1:]==b[:2]

I can then achieve my goal by writing this nested loop/check structure, that basically appends back to front strings and resulting in all valid strings:

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
  • I know I could write it as a list comprehension, but for my original data i have more nested lists and i loose readability even in a list comprehension.
  • I start from l2 -> l0 because in my original data the lists become longer, the lower the index and thus I can filter out more cases early this way.
  • A single loop trough all combinations of l0,l1,l2 and checking the succession of all items a,b,c simultaneously would work but it test way more unnecessary combinations than my current construct does.

Question:

How can this nested loop and conditional checking call be abstracted? Is there a pythonic way to capture the repetition of for -> is_successor()?

A larger input might be:

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
]

Solution

  • I would use itertools product to define a function that will perform the check annd append the data:

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

    then you can nest the function like this:

    get_list(l0,get_list(l1,l2))
    

    you will get the expected result: ['12348', '12399']

    EDIT: for a long list use reduce.

    let s call l the list of list of inputs. In your specific case l = list([l0,l1,l2])

    Reverse the list to apply from the end till the beginning l.reverse()

    and the final code:

    from functools import reduce 
    reduce(lambda x,y:get_list(x,y),l)
    

    Note: in this case you want to change the inputs of the "get_list" function as it will go l2 then l1 then l0.Just change the def of get_list like this: def get_list(l2,l1)