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

后端 未结 1 405
悲&欢浪女
悲&欢浪女 2020-12-22 06:02

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 wa

相关标签:
1条回答
  • 2020-12-22 06:53

    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
    
    0 讨论(0)
提交回复
热议问题