Domanda

As part of my journey into learning python I am implementing Bulls and Cows.
I have a working implementation that uses list comprehension but I figured it might be a nice solution to solve this using a generator and reduce()-ing the final result.

So I have my generator:

def bullsandcows(given, number):
    for i in range(given.__len__()):
        if given[i] == number[i]:
            yield (given[i], None)
        elif given[i] in number:
            yield (None, given[i])

And my reduce implementation:

(bulls, cows) = reduce(\
    lambda (bull, cow), (b, c): \
        (bull + 1, cow + 1), bullsandcows(given, number), (0, 0))

Where given is the user input and number is the randomly generated number for the user to guess.

As you can see, this is not exactly a working implementation, this will just return the counts of the yielded tuples.

What I need is a replacement for (bull + 1, cow + 1), I have no idea how to construct this.

  • number is a randomly generated number, say: 1234
  • given is entered by the user, say: 8241
  • The result of bullsandcows(given, number) would be: [('2', None), (None, '4'), (None, '1']
  • The result of the reduce should be: (1, 2), which is the count of all non-None values of the first element and count of all non-None values of the second element
È stato utile?

Soluzione

If I understood the process correctly, you want to count what bulls are not None, and how many cows are not None:

reduce(lambda (bcount, ccount), (b, c): (bcount + (b is not None), ccount + (c is not None)),
       bullsandcows(given, number), (0, 0))

This increments a counter only if the bull or cow value is not None. The test produces a boolean, which is a subclass of int with False == 0 and True == 1; summing an integer and a boolean results in another integer.

Since you are feeding it non-empty strings, you could simplify it to:

reduce(lambda (bcount, ccount), (b, c): (bcount + bool(b), ccount + bool(c)),
       bullsandcows(given, number), (0, 0))

I'd rewrite bullsandcows() to:

def bullsandcows(given, number):
    given, number = map(str, (given, number))
    for g, n in zip(given, number):
        if g == n:
            yield (g, None)
        elif g in number:
            yield (None, g)

e.g. use zip() to pair up the digits of given and number.

Demo:

>>> def bullsandcows(given, number):
...     given, number = map(str, (given, number))
...     for g, n in zip(given, number):
...         if g == n:
...             yield (g, None)
...         elif g in number:
...             yield (None, g)
... 
>>> given, number = 8241, 1234
>>> list(bullsandcows(given, number))
[('2', None), (None, '4'), (None, '1')]
>>> reduce(lambda (bcount, ccount), (b, c): (bcount + bool(b), ccount + bool(c)),
...        bullsandcows(given, number), (0, 0))
(1, 2)

Note that unpacking in function arguments was removed from Python 3 and the reduce() built-in has been delegated to library function; your code is decidedly Python 2 only.

To make it work in Python 3 you need to import functools.reduce() and adjust the lambda to not use unpacking:

from functools import reduce

reduce(lambda counts, bc: (counts[0] + bool(bc[0]), counts[1] + bool(bc[1])),
       bullsandcows(given, number), (0, 0))
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top