Determine all combinations of flipping a coin without using “itertools.product”

ぐ巨炮叔叔 提交于 2019-12-18 09:53:46

问题


I went through similar posts on the forum but all of them suggest using itertools.product but I was wondering if it can be solved without using it.

I want to print all the combinations of outcomes for N flips of a coin. This can be done if N is known in advance. So the number of nested loops will be just N. But if N has to be determined dynamically (input() function) then I am stuck in implementing it in code. In plain English it is easy to imagine that the number of for loops is proportional to N, but how do I implement it? Do I have to use lambdas or recursion? Below is as example code for N = 4.

results = ["H", "T"]
outcomes = []
for l1 in results:
    for l2 in results:
        for l3 in results:
            for l4 in results:
                outcomes.append(l1+l2+l3+l4)

for o in outcomes:
    print(o)  

Thanks in advance.


回答1:


DIY with generators

Here's one way to calculate a product of lists without using the built-in

def product (*iters):
  def loop (prod, first = [], *rest):
    if not rest:
      for x in first:
        yield prod + (x,)
    else:
      for x in first:
        yield from loop (prod + (x,), *rest)
  yield from loop ((), *iters)

for prod in product ("ab", "xyz"):
  print (prod)

# ('a', 'x')
# ('a', 'y')
# ('a', 'z')
# ('b', 'x')
# ('b', 'y')
# ('b', 'z')

In python, we can collect the outputs of a generator in a list by using the list constructor. Note we can also calculate the product of more than two inputs as seen below

print (list (product ("+-", "ab", "xyz")))
# [ ('+', 'a', 'x')
# , ('+', 'a', 'y')
# , ('+', 'a', 'z')
# , ('+', 'b', 'x')
# , ('+', 'b', 'y')
# , ('+', 'b', 'z')
# , ('-', 'a', 'x')
# , ('-', 'a', 'y')
# , ('-', 'a', 'z')
# , ('-', 'b', 'x')
# , ('-', 'b', 'y')
# , ('-', 'b', 'z')
# ]

Because product accepts a a list of iterables, any iterable input can be used in the product. They can even be mixed as demonstrated below

print (list (product (['@', '%'], range (2), "xy")))
# [ ('@', 0, 'x')
# , ('@', 0, 'y')
# , ('@', 1, 'x')
# , ('@', 1, 'y')
# , ('%', 0, 'x')
# , ('%', 0, 'y')
# , ('%', 1, 'x')
# , ('%', 1, 'y')
# ]

Because product is defined as a generator, we are afforded much flexibility even when writing more complex programs. Consider this program that finds right triangles made up whole numbers, a Pythagorean triple. Also note that product allows you to repeat an iterable as input as see in product (r, r, r) below

def is_triple (a, b, c):
  return a * a + b * b == c * c

def solver (n):
  r = range (1, n)
  for p in product (r, r, r):
    if is_triple (*p):
      yield p

print (list (solver (20)))
# (3, 4, 5)
# (4, 3, 5)
# (5, 12, 13)
# (6, 8, 10)
# (8, 6, 10)
# (8, 15, 17)
# (9, 12, 15)
# (12, 5, 13)
# (12, 9, 15)
# (15, 8, 17)

Implementing your coin tossing program is easy now.

def toss_coins (n):
  sides = [ 'H', 'T' ]
  coins = [ sides ] * n
  yield from product (*coins)

print (list (toss_coins (2)))
# [ ('H', 'H'), ('H', 'T'), ('T', 'H'), ('T', 'T') ]

print (list (toss_coins (3)))
# [ ('H', 'H', 'H'), ('H', 'H', 'T'), ('H', 'T', 'H'), ('H', 'T', 'T'), ('T', 'H', 'H'), ('T', 'H', 'T'), ('T', 'T', 'H'), ('T', 'T', 'T') ]

Without generators

But generators are a very high-level language feature and we wonder how we could represent product using pure recursion. Below product is reimplemented without the use of generators and now returns a filled array with all calculated sub-products

def map (f, lst):
  if not lst:
    return []
  else:
    first, *rest = lst
    return [ f (first ) ] + map (f, rest)

def flat_map (f, lst):
  if not lst:
    return []
  else:
    first, *rest = lst
    return f (first) + flat_map (f, rest)

def product (*iters):
  def loop (acc, iters):
    if not iters:
      return acc
    else:
      first, *rest = iters
      return flat_map (lambda c: map (lambda x: [x] + c, first), loop (acc, rest))
  return loop ([[]], iters)

We can now skip the yield and list calls in your program

def toss_coins (n):
  sides = [ 'H', 'T' ]
  coins = [ sides ] * n
  return product (*coins)

print (toss_coins (2))
# [('H', 'H'), ('H', 'T'), ('T', 'H'), ('T', 'T')]

print (toss_coins (3))
# [('H', 'H', 'H'), ('H', 'H', 'T'), ('H', 'T', 'H'), ('H', 'T', 'T'), ('T', 'H', 'H'), ('T', 'H', 'T'), ('T', 'T', 'H'), ('T', 'T', 'T')]

Above, we define map and flat_map with as few dependencies as possible, however there is only one subtle distinction in each implementation. Below, we represent each as a fold (using reduce) allowing us to see the semantic difference more easily. Also note Python includes its own version of map and reduce (in functools) that differ slightly from the versions provided here.

def concat (xs, ys):
  return xs + ys

def append (xs, x):
  return xs + [ x ]

def reduce (f, init, lst):
  if not lst:
    return init
  else:
    first, *rest = lst
    return reduce (f, f (init, first), rest)

def map_reduce (m, r):
  return lambda acc, x: r (acc, m (x))

def map (f, lst):
  return reduce (map_reduce (f, append), [], lst)

def flat_map (f, lst):
  return reduce (map_reduce (f, concat), [], lst)

def product (*iters):
  # this stays the same


来源:https://stackoverflow.com/questions/49718861/determine-all-combinations-of-flipping-a-coin-without-using-itertools-product

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!