Design and Analysis of Algorithms

Today is a quiz day, so we don't want to wear ourselves out. Let's just play a little Bulls and Cows. Find a partner.

Each player writes down a 4-digit "secret number", hidden from the opponent. The four digits must be all different. The players then alternate, trying to guess their opponent's number. After each guess, the code maker gives feedback in turns of, you guessed, bulls and cows. If the guess contains a digit in its correct position, it is a "bull". If a digit is in the code but in a different position, it is a "cow".

For example, if the secret number is `3540` and the
guess is `1234`, the code maker responds "0 bull and
2 cows". "3" and "4" are cows. If the next guess is
`3042`, the feedback is "2 bulls and 1 cow".

The first player to reveal the other's secret number wins the game. Alternate going first, because the first guesser has an obvious advantage!

**Play a few games of Bulls and Cows, but with three-digit
numbers**.

How easy is it to break the code in this version of the game?

**Now, play a few games using a new feedback rule**.

The feedback consists of a single number, computed by first multiplying the corresponding digits in the code and the guess, and then adding these products. For example, if the code is "4 7 2" and the guess is "1 2 3", then the code maker will give the number (4*1 + 7*2 + 2*3) = "24" as feedback.

How easy is it to break the code in this version of the game?

This may not seem easier, but the feedback actually tells us a lot... if we remember some algebra.

Now that you've played a bit, let's sketch some algorithms:

- an algorithm that is guaranteed to find the correct answer, with no regard for how long it takes
- an algorithm that takes advantage of the fact that
we only need
`n`equations to solve a system of`n`variables - an algorithm that takes advantage of the place value of numbers

Huh? That last one must be require us to "zoom in" on some property.

Writing an algorithm that guarantees an answer, if slowly, is pretty easy:

for i := 1 to 3 do for j := 1 to 3 do for k := 1 to 3 do if code = [i, j, k] return [i, j, k] fail

This is a prototypical example of **brute force**: just
whack away at the problem until we find a solution. Because
we whack systematically, we know that we will find the
answer -- eventually.

Could we do better by applying a little knowledge about the problem? When we play the version of the game with the dot product feedback, we know that each bit of feedback tells us this about the code:

g_{i1}* c_{1}+ g_{i2}* c_{2}+ g_{i3}* c_{3}= feedback_{i}

So, we only need `n` equations before we will be able
to solve the system algebraically. Such an algorithm, with
some high-level steps, might look like:

for i := 1 to 3 do guess g_{i,1}g_{i,2}g_{i,3}remember feedback_{i}solve the system of 3 equations

This algorithm depends on the human reader to fill in some details, but if we take "solve the system" as an operator, it is reasonably clear. It's also reasonably efficient and as fast as we can expect on the average case.

Can we do better by choosing guesses systematically or based on the feedback given? For example, what does a guess of "1 1 1" tell us?

How about three guesses of "1 0 0", "0 1 0", and "0 0 1"? Three 'equations' and no real computation to be done...

Somewhat amazingly, we can do even better -- *if* we
zoom in on the nature of the problem *and* are willing
to make a first guess that is guaranteed to be wrong -- and
if we are willing to play a little looser with the rules.
Recall the nature of our feedback:

g_{i1}* c_{1}+ g_{i2}* c_{2}+ g_{i3}* c_{3}= feedback_{i}

Because we know that the code consists of 1-digit numbers, we can make our first guess with:

100 10 1

100 and 10 aren't single digits, so we know they are wrong. But the feedback we receive will be:

100c_{1}+ 10c_{2}+ 1c_{3}

So the feedback will tell our the code in place-value order! Our second 'guess' is guaranteed to succeed.

Solving the code on the second guess isn't much of an
improvement in the case of a three-number code. With the
systems-of-equations approach, we can solve it on our fourth
guess in the worst case. But the system-of-equations approach
requires *n*+1 guesses for a code of length *n*,
whereas the zoom-in approach **requires only two guesses
regardless of the value of n**.

What if we play with *two*-digit numbers? No problem;
the place-value approach generalizes! For two-digit numbers,
guess "10000 100 1". And for three-digit numbers, guess
"1,000,000 1,000 1"; for *n*-digit numbers, guess
"10^{2n} 10^{n}
10^{0}".

But what if we play with longer strings, say, 100 5-digit numbers? Again, not a problem. Grow your guess string following the same pattern. It still works.

Understanding the nature of a problem -- bringing
*knowledge* to bear in designing an algorithm -- can
confer great power on the problem solver. We should seek to
do so whenever the benefit exceeds the costs: the cost of
acquiring the needed data, storing the needed data, computing
the intermediate values, etc.

"*But what if the problem we're solving doesn't involve
integers*?" Representation. Problem transformation.

Bulls and Cows is one variation of a common code-breaking game played around the world. In the 1970s, it became a commercial success as Mastermind. In place of numbers, Mastermind uses pegs of six colors, and allows repeats in the code. This is equivalent to limiting the codes to the numbers [1111..6666].

Donald Knuth developed a clever algorithm that combines set coverage and minimax to solve win every Mastermind game in five moves or fewer. (See Knuth's original paper for even more.) The best known algorithm for playing Mastermind requires 4.34 turns on average.

There is a lot of code available on-line for playing both Mastermind and Bulls and Cows. Mastermind is a popular assignment in our Intermediate Computing course.

- Reading -- ... coming.
- Homework -- Homework 2 is available and due next session.
- Exam -- Exam 1 was this session.