views:

159

answers:

4

I'm working on learning python, here is a simple program, I wrote:

def guesser(var, num1,possible):
    if var == 'n':
        cutoff = len(possible)/2
        possible = possible[0:cutoff]
        cutoff = possible[len(possible)/2]
        #print possible

        if (len(possible) == 1):
             print "Your Number is:", possible
        else:
            var = raw_input("Is Your Number Bigger Than %s? (y/n): " %cutoff)
            guesser(var, cutoff,possible)


    elif var == 'y':
        cutoff = len(possible)/2
        possible = possible[cutoff:len(possible)]
        cutoff = possible[len(possible)/2]
        #print possible
        #print cutoff

       if (len(possible) == 1):
           print "Your Number is:", possible
       else:
            var = raw_input("Is Your Number Bigger Than %s? (y/n): " %cutoff)
            guesser(var, cutoff,possible)


    else:
        var = raw_input("Is Your Number Bigger Than 50? (y/n): ")
        guesser(var, 50, possible)

possible = []
possible = range(1,101)

guesser('a', 50, possible)
+2  A: 

Before doing it more pythonic I will probably make it more simple... the algorithm is much more complex than necessary. No need to use a list when two ints are enough.

def guesser(low = 0, up = 100):
    print("Choose a number between %d and %d" % (low, up-1))
    while low < up - 1:
        mid = (low+up)//2
        yn = raw_input("Is Your Number Smaller Than %s? (y/n): " % mid)
        if yn not in ['y', 'n']: continue
        low, up =  (low, mid) if yn == 'y' else (mid, up)
    print "Your Number is:", low


guesser()
kriss
print "Your Number is:", lo is missing a 'w'.
NoahClark
@NoahClark: thanks
kriss
I just played around with this solution, and it seems to work fine for all the numbers I've tested and I understand it!Thanks.
NoahClark
+4  A: 

Normally I would try to help with your code, but you have made it so way much too complicated that I think it would be easier for you to look at some code.

def guesser( bounds ):
    a, b = bounds
    mid = ( a + b ) // 2

    if a == b: return a

    if input( "over {0}? ".format( mid ) ) == "y":
        new_bounds = ( mid, b )
    else:
        new_bounds = ( a, mid )

    return guesser( new_bounds )

You should think about how your algorithm will work in abstract terms before diving in.

EDIT: Simplified the code at the expense of brevity.

katrielalex
+1 if I remember when my votes reup. this absolutely the right answer as long as `b - a < 2 ** n` where `n` is the recursion limit of the local python. I think the default is 1000 so that covers quite a wide range. one thing that I would suggest for readability is assigning a `new_range` explicitly and passing that to `guesser` in the recursion step.
aaronasterling
I'm passing guesser range(1,101) and I get an too many values to unpack issue.
NoahClark
@katrielalex: it does not work, try with 17 for range 0,100 (simple matter of stop case, easy glitch for this example).
kriss
@NoahClark: you need to pass a tuple with bounds, not a range (parameter name is misleading)
kriss
I still get the same error. A tuple is just an immutable list wrapped in parenthesis right?
NoahClark
@katrielalex and @aaronasterling: as far as I know, even even recursion works using python it should be considered as unpythonic what makes me think so is some blogs from Guido on TCO like this one: http://neopythonic.blogspot.com/2009/04/final-words-on-tail-calls.html
kriss
@NoahClark: the correct call should look like `guesser((0, 100))` one parenthesis level for function call, another one for the tuple. You also have to change input by raw_input to make it (nearly) work. I write nearly because of the stop case bug as explained in my other comment.
kriss
Thanks, I got it working changing those two things except for that bug.
NoahClark
@NohaClark It's a trivial bug to fix - doing so would be a good exercise
aaronasterling
@kriss: nonsense. Recursion is just a tool, same as conditionals and loops. We require `log(b - a)` stack frames for this, for which the overhead is nearly zero anyway, not to mention that we wait for input each call. You simply have to remember that Python doesn't optimise tail calls for you.
katrielalex
@NoahClark: true, there is a stop case bug. I'll leave it for you to think about =) (Hint: work out how the range is re-evaluated each time around the loop.)
katrielalex
@katrielalex: I have no problem with recursion (I even have some experience with Lisp and Haskell). But what I understood of Guido's bloging was merely that he didn't liked the recursive style, even if it's obviously not a problem with small stack depth like here. The only cost is using log(a-b) stackframes (more or less log(a-b)*3 instead of 3). It makes less understandable the recommandation of not using a full list of integers when two are enough. Also the derecursivation is so simple in our case that using a loop instead is a no thinker.
kriss
@kriss: I still disagree with you, but I think it's more a style point than anything else -- I personally find recursive functions just as intuitive as their iterative counterparts, if not more so. Note also that Guido isn't complaining about the recursive style at all; he's just saying that Python will not optimise for tail-recursion.
katrielalex
A: 

This is not as elegant as katrielalex's recursion, but it illustrates a basic class.

class guesser:
    def __init__(self, l_bound, u_bound):
        self.u_bound = u_bound
        self.l_bound = l_bound
        self.nextguess()

    def nextguess(self):
        self.guess = int((self.u_bound + self.l_bound)/2)
        print 'Higher or lower than %i?' % self.guess

    def mynumberishigher(self):
        self.l_bound = self.guess
        self.nextguess()

    def mynumberislower(self):
        self.u_bound = self.guess
        self.nextguess()
Nick T
+2  A: 

More pythonic to use the bisect module - and a class of course :)

import bisect    
hival= 50

class Guesser(list):
    def __getitem__(self, idx):
        return 0 if raw_input("Is your number bigger than %s? (y/n)"%idx)=='y' else hival

g=Guesser()
print "Think of a number between 0 and %s"%hival
print "Your number is: %s"%bisect.bisect(g,0,hi=hival)

Here is the definition of bisect.bisect from the python library. As you can see, most of the algorithm is implemented here for you

def bisect_right(a, x, lo=0, hi=None):
    """Return the index where to insert item x in list a, assuming a is sorted.

    The return value i is such that all e in a[:i] have e <= x, and all e in
    a[i:] have e > x.  So if x already appears in the list, a.insert(x) will
    insert just after the rightmost x already there.

    Optional args lo (default 0) and hi (default len(a)) bound the
    slice of a to be searched.
    """

    if lo < 0:
        raise ValueError('lo must be non-negative')
    if hi is None:
        hi = len(a)
    while lo < hi:
        mid = (lo+hi)//2
        if x < a[mid]: hi = mid
        else: lo = mid+1
    return lo

bisect = bisect_right   # backward compatibility
gnibbler
+1: It's probably a bit of cheating in this case, but ok, no need to reinvent the wheel when we have a good one available
kriss
it seems to me that this requires knowledge of the internal implementation of `bisect`. Specifically, g stated to be a list in the docs and your guesser doesn't provide a (e.g.) `count` method. How do you know that `bisect` doesn't use that without looking at the source? It is mighty slick though. I just wonder about pythonic.
aaronasterling
I'm trying to figure out how this all works. Where does idx get it's number from?
NoahClark
@arronasterling, I changed it to subclass `list` instead :) I hardly ever use bisect with plain lists, I think there is an answer on SO somewhere, where I use bisect to quickly locate an item in a file for example
gnibbler
@NoahClark, `__getitem__` is called when you access `g[idx]`. bisect.bisect does that for you
gnibbler