Search code examples
pythonpython-itertools

How can i create a permutation with a limit on specific characters efficently?


To give you an idea of what i'm talking about. This is the code i have now:

chrs = 'ABCDEF1234567890'
with open('two.txt', 'w') as two:
    for xs in itertools.product(chrs, repeat=10):
        h = sum(x.isalpha() for x in xs)
        if h == 2: two.write(''.join(xs) + '\n')

Unfortunately this literally takes days as it creates every possible combination, most of which do not match.

EDIT: To clarify, i need a way of finding all possible combinations of the variable chrs where there are only two letters ('ABCDEF'). Without creating an entire list of all combinations and checking each one. I need to create just the ones that have two letters in them.

To go even further:

'AA12345678' = Good
'1234A123A4' = Good
'AAA1234567' = Bad
'A123456789' = Bad

I really don't know how much more i can explain just 5 lines of python code.


Solution

  • This is based on stars and bars rather than combinations and permutations.

    Start with a standard stars and bars algorithm, a refinement of my previous work:

    def stars_and_bars(star='*', bar='|', star_count=8, bar_count=2):
        if star_count == 0:
            yield (bar,) * bar_count
            return
        if bar_count == 0:
            yield (star,) * star_count
            return
        for left in range(star_count + 1):
            right = star_count - left
            assert right >= 0
            assert left + right == star_count
            for partial in stars_and_bars(star, bar, left, bar_count - 1):
                yield partial + (bar,) + (star,) * right
    

    Verify that it works:

    >>> sandb = stars_and_bars(star_count=3, bar_count=2)
    >>> for result in sandb:
    ...     print(''.join(result))
    ... 
    ||***
    |*|**
    *||**
    |**|*
    *|*|*
    **||*
    |***|
    *|**|
    **|*|
    ***||
    

    As you can see, this produces each possible arrangement of stars and bars. With 8 stars and two bars, we can use this to find all possible arrangements of numbers and letters. We'll then pass that to itertools.product():

    def combos():
        letters = 'ABCDEF'
        numbers = '1234567890'
        argument_combos = stars_and_bars(numbers, letters)
        for args in argument_combos:
            yield from itertools.product(*args)
    

    The result will be exactly what OP asked for.