Anonymous Monk has asked for the wisdom of the Perl Monks concerning the following question:

Hi!
I have the following code which I wrote for a guessing number script. The purpose of this code was to make a program that guesses a number between 1 and 10 that the user thinks of. It should make a guess, and the user shall answer yes (if correctly guessed), higher (if the number the user thought of was higher than the quess) or lower (if the number the user thought of was lower than the quess). The program ends when the number is guessed correctly, otherwise it tries again.
print "Think of a number between 1 and 10\n"; $user_answer = <STDIN>; $program_guess = 0; while ($user_answer ne 'YES') { $program_guess++; print "Is the number you thought of $program_guess ? Answer yes, hi +gher or lower\n"; $answer = <STDIN>; chomp $user_answer; } print "Program finished. The number you guessed was $program_guess.\n" +;

Now, the goal is to make it more advanced:
I need to count how many guesses it took for the program until it finds the correct number (easy one, just a counter there) but also to make the program guess the number in the fewest guesses possible...How can I do that?

Replies are listed 'Best First'.
Re: Updates to a guessing number script
by davido (Cardinal) on Feb 28, 2014 at 23:04 UTC

    The fewest guesses possible in the general case (excluding a "lucky guess") is going to be ceil(log2(n)), and that is possible by implementing a binary search.

    A stable binary search will require ceil(log2(n))+1, and a simple binary search will require a worst case of ceil(log2(n)) which is 4. That's the lower limit for the worst case.

    Here's how a binary search works. Let's extend your game to any number between 1 and 100. You select a secret number, which in this case is 73:

    1. Is it 50? No, higher. (Mid-point between 1 .. 100 )
    2. Is it 75? No, lower. (Mid-point between 51 .. 100 )
    3. Is it 63? No, higher. (Mid-point between 51 .. 74 )
    4. Is it 69? No, higher. (Mid-point between 64 .. 74 )
    5. Is it 72? No, higher. (Mid-point between 70 .. 74 )
    6. Is it 74? No, lower. (Mid-point between 73 .. 74 )
    7. Is it 73? Yes, it is. (Mid-point between 73 .. 73 )

    At each step, I take the remaining range, and select the mid-point. So at step one, I'm bisecting the range of 1..100. On step two, I'm bisecting 51..100, and so on until, on step seven, I guess it (and the range happens to also have narrowed to only one possibility anyway).

    The Wikipedia article on Binary Search demonstrates the algorithm in code. You could adapt it to work for your needs.

    There is no general algorithm for beating log2(n). There will be the occasional lucky guess though. But consider how effective this is. You are guaranteed of finding the correct number between 1..10 in 4 or fewer guesses. Between 1..100 in 7 or fewer guesses. Between 1 and 1,000,000 is guaranteed to be 20 or fewer guesses. Between 1..1,000,000,000 will be 30 or fewer guesses. Between 1 and a trillion, 40 or fewer guesses. If you had to check each number between 1 and a trillion, one by one, your worse case would be a trillion guesses, and average would be 500 billion, but with the binary search's divide and conquer approach, that problem domain a trillion members big is reduced to 40 guesses.

    The CPAN module List::BinarySearch implements a stable binary search, which is almost (but not quite) what you need, and it's not really designed to be used for a number guessing game. Nevertheless, it can be coerced to do what you are after:

    use List::BinarySearch 'binsearch'; use IO::Prompt::Hooked 'prompt'; print "Please think of a number between 1 and 10...\n"; print "DON'T tell me, just let me guess it!\n\n"; my $tries = 0; binsearch { ++$tries; my $response = prompt( message => "Is the number $b? (higher, lower, or yes):", validate => qr/higher|lower|yes/i, error => sub { "I don't understand $_[0]. Try again.\n" }, ); return 1 if $response =~ /higher/i; return -1 if $response =~ /lower/i; print "Found $b in $tries guesses.\n"; exit(0); } undef, @{[1 .. 10]};

    Here's a sample run:

    Please think of a number between 1 and 10... DON'T tell me, just let me guess it! Is the number 5? (higher, lower, or yes): higher Is the number 8? (higher, lower, or yes): lower Is the number 7? (higher, lower, or yes): yes Found 7 in 3 guesses.

    You won't be able to turn this in to your professor, because certainly he wants you to implement the algorithm yourself. However, with this and a little research you should be able to work out your own solution without too much difficulty. There's also an article about the Binary Search in Mastering Algorithms with Perl (O'Reilly).

    (Disclaimer: I'm the author of List::BinarySearch.)


    Dave

      For a start, if you're dealing with humans, forget the idea of starting in any random or binary manner. For numbers between 1 and 10, make 7 your first guess. Weird as it may seem, at least in some cultures, given the 1 to 10 choice, as many as 60% or more will choose 7! In other words, your most efficient algorithm is not a random one. It probably does you no great harm, if you then follow various binary search techniques from that point onward, although this (http://groups.csail.mit.edu/uid/deneme/?p=628) would suggest you have better strategies to continue with past your hard-coded first choice. This is not a "pure maths" solution -- it's about real life, so you'd better prepare your groundwork if your professor wants you to play with abstract theory rather than the Real World. Cite the above as a reference or google for more examples ?

      If you had to check each number between 1 and a trillion, one by one, your worse case would be a trillion guesses, and average would be 500 billion, but with the binary search's divide and conquer approach, that problem domain a trillion members big is reduced to 40 guesses.

      Although I perfectly understand what you really mean (and agree with everything else that you said), the statement above about the average search is technically not true if you play according to the rules of the game, i.e. if you select a number within the range previously determined by the previous tries: the average would be much much smaller than what you said. As an example, let me take the trillion (1e12) case.

      The following program is just selecting a wild guess within the admitted range and adjusting the range in accordance with the results of the previous try.

      use strict; use warnings; my ($low, $high) = qw /0 1e12/; my $search_number = int rand $high; my $guess; my $count = 0; while (1) { $guess = $low + int rand ($high - $low); last if $guess == $search_number; if ($search_number > $guess) { $low = $guess; } else { $high = $guess; } printf "%12d", $guess; print " - Range is $low - $high \n"; $count++; } print "Searched value is : $guess, found in $count tries \n";
      Now running the program a few times: In brief, 45, 37 and 50 tries. This is far from being bad. Compared with the optimal 40 guesses, the random approach is actually fairly good.

        Without re-reading my post, I believe I was talking about the Binary Search in general -- that it can reduce a trillion-item problem to 40 "guesses", where guess really should have been written as 40 well-chosen queries.

        But you are correct, and your post is well thought-out. If the person trying to guess the number picks a number at random, then another number at random within the newly discovered valid range, and so on, he would be able to narrow the problem area much faster than doing a linear search.

        And it seems we agree that the optimal solution (excluding approaches involving human frailty or psychology), will still be a binary search. The "random guess" method for dividing and conquering is not totally dissimilar from a binary search; it's just not as well disciplined in perfectly bisecting the remaining problem area. Nevertheless, it still manages to divide and conquer, like Binary Search does.

        Your post is good; it adds real-world practicality to the discussion.


        Dave

Re: Updates to a guessing number script
by frozenwithjoy (Priest) on Feb 28, 2014 at 22:47 UTC
    Seems like the fewest # of guesses would use a splitting algorithm where you take the possible range and guess the # in the middle. For example, the user chooses 2. You start out with a possible range of 1-10 and program finds the average (1 + 10) / 2 = 5.5 and chooses 6. User says 'lower' and the program updates the range to be 1-5 and guesses 3. User says 'lower', so new range is 1-2 and program guesses 2. In order to implement this, you can just keep track of the upper and lower bounds of the range and adjust the appropriate boundary for each iteration.
Re: Updates to a guessing number script
by Anonymous Monk on Feb 28, 2014 at 23:02 UTC

    As it's been said, dividing the range in the middle is the optimum strategy here. This way, the high/low question will yield maximum information. Read also the article on Binary search.

Re: Updates to a guessing number script
by ww (Archbishop) on Feb 28, 2014 at 22:55 UTC
    Re counting guesses: Best you should do a little searching... try "increment" and "Perl" as your search terms on the likes of DDG... or use Super Search here.

    And please show some effort at solving your own questions before you come here with a 'gimmé' -- a question demonstrating no effort whatsoever.

    Come, let us reason together: Spirit of the Monastery
Re: Updates to a guessing number script
by Laurent_R (Canon) on Mar 01, 2014 at 11:22 UTC
    Pretty much everything has been said by davido and others, just a couple of other keywords you might want to "google" besides "binary search" are "bisection method" and "dichotomy method".