57

I am generating all possible three letters keywords e.g. aaa, aab, aac.... zzy, zzz below is my code:

alphabets = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']

keywords = []
for alpha1 in alphabets:
    for alpha2 in alphabets:
        for alpha3 in alphabets:
            keywords.append(alpha1+alpha2+alpha3)

Can this functionality be achieved in a more sleek and efficient way?

agf
  • 171,228
  • 44
  • 289
  • 238
Aamir Rind
  • 38,793
  • 23
  • 126
  • 164

8 Answers8

103
keywords = itertools.product(alphabets, repeat = 3)

See the documentation for itertools.product. If you need a list of strings, just use

keywords = [''.join(i) for i in itertools.product(alphabets, repeat = 3)]

alphabets also doesn't need to be a list, it can just be a string, for example:

from itertools import product
from string import ascii_lowercase
keywords = [''.join(i) for i in product(ascii_lowercase, repeat = 3)]

will work if you just want the lowercase ascii letters.

agf
  • 171,228
  • 44
  • 289
  • 238
  • 1
    If you want to generate each character combination on the fly without taking up a lot of memory, you can change `[''.join(i) for i in product(ascii_lowercase, repeat = 3)]` to `(''.join(i) for i in product(ascii_lowercase, repeat = 3))` and iterate through each one in a `for-in` loop – TestinginProd Jan 16 '18 at 01:30
  • 1
    @DCIndieDev: Even better, make it `map(''.join, product(ascii_lowercase, repeat=3))`; it's lazy (on Python 3) just like the generator expression, but on the CPython reference interpreter, thanks to the way `map` works (it applies the function, then releases the argument immediately before yielding the result), it enables an optimization in `product` that reuses the same `tuple` for each result rather than building and discarding one each time (a similar optimization is used by many lazy `tuple` producers, e.g. `zip`, bit it only applies when the result is `map`-ed or unpacked to names). – ShadowRanger Nov 18 '21 at 16:05
19

You could also use map instead of the list comprehension (this is one of the cases where map is still faster than the LC)

>>> from itertools import product
>>> from string import ascii_lowercase
>>> keywords = map(''.join, product(ascii_lowercase, repeat=3))

This variation of the list comprehension is also faster than using ''.join

>>> keywords = [a+b+c for a,b,c in product(ascii_lowercase, repeat=3)]
John La Rooy
  • 295,403
  • 53
  • 369
  • 502
  • 5
    With `join` you don't have to change it if you change the value of `repeat` -- add some cliche about premature optimization here. – agf Aug 16 '11 at 06:26
  • a+b+c will work only if 3 letters combination you have to make. – Vin Oct 20 '20 at 05:48
6
from itertools import combinations_with_replacement

alphabets = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']

for (a,b,c) in combinations_with_replacement(alphabets, 3):
    print a+b+c
agf
  • 171,228
  • 44
  • 289
  • 238
Asterisk
  • 3,534
  • 2
  • 34
  • 53
  • 5
    This isn't actually the same. Try it with two letters -- you get 26 combinations with `a` as the first letter, then 25 for `b`, etc., down to only `zz` for `z` as the first letter. That is, you don't get both `ab` and `ba`, or to use the example in the OP, you don't get `zzy`, because you already got `yzz`. – agf Oct 06 '11 at 20:12
  • what is needed is actually a permutation with replacement (with repetitions), not combination. And a permutation with replacement is just a Cartesian product. – Andrew Anderson Dec 08 '22 at 09:55
4

You can also do this without any external modules by doing simple calculation.
The PermutationIterator is what you are searching for.

def permutation_atindex(_int, _set, length):
    """
    Return the permutation at index '_int' for itemgetter '_set'
    with length 'length'.
    """
    items = []
    strLength = len(_set)
    index = _int % strLength
    items.append(_set[index])

    for n in xrange(1,length, 1):
        _int //= strLength
        index = _int % strLength
        items.append(_set[index])

    return items

class PermutationIterator:
    """
    A class that can iterate over possible permuations
    of the given 'iterable' and 'length' argument.
    """

    def __init__(self, iterable, length):
        self.length = length
        self.current = 0
        self.max = len(iterable) ** length
        self.iterable = iterable

    def __iter__(self):
        return self

    def __next__(self):
        if self.current >= self.max:
            raise StopIteration

        try:
            return permutation_atindex(self.current, self.iterable, self.length)
        finally:
            self.current   += 1

Give it an iterable object and an integer as the output-length.

from string import ascii_lowercase

for e in PermutationIterator(ascii_lowercase, 3):
    print "".join(e)

This will start from 'aaa' and end with 'zzz'.

agf
  • 171,228
  • 44
  • 289
  • 238
Niklas R
  • 16,299
  • 28
  • 108
  • 203
2
chars = range(ord('a'), ord('z')+1);
print [chr(a) + chr(b) +chr(c) for a in chars for b in chars for c in chars]
davidvino
  • 21
  • 2
1

We could solve this without the itertools by utilizing two function definitions:

def combos(alphas, k):
    l = len(alphas)
    kRecur(alphas, "", l, k)

def KRecur(alphas, prfx, l, k):
    if k==0:
        print(prfx)
    else:
        for i in range(l):
            newPrfx = prfx + alphas[i]
            KRecur(alphas, newPrfx, l, k-1)

It's done using two functions to avoid resetting the length of the alphas, and the second function self-iterates itself until it reaches a k of 0 to return the k-mer for that i loop.

Adopted from a solution by Abhinav Ramana on Geeks4Geeks

alphahmed
  • 97
  • 4
  • Note: This is `print`ing the results, which makes it less programatically useful than something that actually creates them and `yield`s/`return`s them for further processing, and it's using recursion (which means it explodes for large values of `k`; Python's stack frame limit defaults to 1000, and it doesn't do tail recursion optimization). – ShadowRanger Nov 18 '21 at 16:07
0

Well, i came up with that solution while thinking about how to cover that topic:

import random

s = "aei"
b = []
lenght=len(s)
for _ in range(10):
    for _ in range(length):
        password = ("".join(random.sample(s,length)))
        if password not in b:
            b.append("".join(password))
print(b)
print(len(b))

Please let me describe what is going on inside:

  1. Importing Random,
  2. creating a string with letters that we want to use
  3. creating an empty list that we will use to put our combinations in
  4. and now we are using range (I put 10 but for 3 digits it can be less)
  5. next using random.sample with a list and list length we are creating letter combinations and joining it.
  6. in next steps we are checking if in our b list we have that combination - if so, it is not added to the b list. If current combination is not on the list, we are adding it to it. (we are comparing final joined combination).
  7. the last step is to print list b with all combinations and print number of possible combinations. Maybe it is not clear and most efficient code but i think it works...
-1
print([a+b+c for a in alphabets for b in alphabets for c in alphabets if a !=b and b!=c and c!= a])

This removes the repetition of characters in one string

a_r
  • 488
  • 6
  • 12
aks
  • 1
  • 1