We can write permutations(t) of any iterable t using inductive reasoning -
- if tis empty, yield the empty permutation,()
- (inductive) thas at least one element. For allpof the recursive sub-problempermutations(t[1:]), for alliofinserts(p, t[0]), yieldi
def permutations(t):
  if not t:
    yield ()                       # 1. empty t
  else:
    for p in permutations(t[1:]):  # 2. at least one element
      for i in inserts(p, t[0]):
        yield i
Where inserts(t, x) can be written using inductive reasoning as well -
- If the input tis empty, yield the final insert,(x)
- (inductive) thas at least one element. yieldxprepended totand for alliof the recursive sub-probleminserts(t[1:], x)yieldt[0]prepended toi
def inserts(t, x):
  if not t:
    yield (x,)                       # 1. empty t
  else:
    yield (x, *t)                    # 2. at least one element
    for i in inserts(t[1:], x):
      yield (t[0], *i)
t = ("","","","")
for p in permutations(t):
  print("".join(p))
Python has strong support for generators and offers delegation using yield from ... We can simplify the above definitions while maintaining the exact same behaviour -
def permutations(t):
  if not t:
    yield ()
  else:
    for p in permutations(t[1:]):
      yield from inserts(p, t[0])  # <-
def inserts(t, x):
  if not t:
    yield (x,)
  else:
    yield (x, *t)
    yield from map(lambda r: (t[0], *r), inserts(t[1:], x)) # <-
Generators are a good fit for combinatorics because working with them is natural and straightforward -
# find all permutations where red is left of green
for p in permutations(t):
  if p.index("") < p.index(""):
    print("".join(p))
# find all permutations where blue and yellow are adjacent
for p in permutations(t):
  if abs(p.index("") - p.index("")) == 1:
    print("".join(p))
Additionally generators can be paused/stopped at any time, allowing us to skip potentially millions of computations for significantly large problems -
# which permutation is in rainbow order?
for (n, p) in enumerate(permutations(t)):
  if p == ("", "", "", ""):
    print(f"permutation {n} is in rainbow order, {p}")
    break  # <- stops generating additional permutations
permutation 16 is in rainbow order, ('', '', '', '')