Some questions no program can answer
The halting problem asks whether a given program will ever stop on a given input. Alan Turing proved in 1936 that no program can answer it correctly for every program.
A second limit is time. Some problems can be solved, but only in more time than you have. This lesson separates the impossible from the merely too slow.
By the end of this lesson
Note
I can explain why no program can decide every question, and what too slow means.
Key words
| Word | What it means |
|---|---|
| Decidable | a yes or no question that some program always answers correctly |
| Undecidable | a question no program can answer correctly every time |
| Halting problem | does this program stop on this input? |
| Brute force | trying every possible answer |
| Subset sum | can some of these numbers add up to the target? |
| Intractable | solvable, but too slow once the input is large |
The cells students fill in are left blank in this preview.
No program can check every program for stopping
Suppose someone writes halts(program, data), a checker that always answers True if the program stops and False if it runs for ever.
We can use it to build a troublemaker that does the opposite of what the checker predicts about it. Ask the checker about the troublemaker running on itself, and it cannot be right.
The troublemaker program
# Suppose halts(program, data) always answered True or False correctly.
def troublemaker(program):
if halts(program, program):
while True:
pass
return "stopped"
# Now ask: does troublemaker stop when given itself?
Every answer the checker gives is wrong
| Checker says about the troublemaker | The troublemaker then | So the checker was |
|---|---|---|
| It stops | Loops for ever | Wrong |
| It loops for ever | Stops at once | Wrong |
The cells students fill in are left blank in this preview.
Waiting never proves a program loops
Running a program tells you when it stops. It never tells you that it never will, because there is always one more second to wait.
The Run below gives sleep five seconds, but the timeout command gives up after one. Giving up is a decision, not an answer.
Give up on a program after a second
timeout 1 sleep 5 || echo gave up after one secondShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Which questions are decidable
| Question | Decidable? | Why |
|---|---|---|
| Is this list sorted? | Yes | Look at each pair once |
| Does this program stop within a million steps? | Yes | Run it for a million steps |
| Does this program stop on this input? | No | It is the halting problem |
| Will this program ever print hello? | No | Answering it would solve the halting problem |
| Do these two programs always give the same output? | No | The same reason |
The cells students fill in are left blank in this preview.
A common mistake
Warning
Many people think undecidable means very hard, or that computers are not fast enough yet. Undecidable is proven impossible for any program, however fast. Intractable is different: a program exists, but it takes too long once the input is large.
Brute force doubles with every extra number
In subset sum you are given some numbers and a target. Brute force tries every subset and adds it up.
With n numbers there are 2 to the power n subsets, because each number is either in or out. One more number doubles the work. Run the demo, then add a number and watch the count.
Brute force on a small subset sum
# Brute force: try every subset of the numbers, counting how many we try.
numbers = [3, 5, 8]
target = 8
n = len(numbers)
tried = 0
found = []
for pattern in range(2 ** n):
total = 0
chosen = []
for i in range(n):
# Bit i of the pattern says whether number i is in this subset.
if (pattern // (2 ** i)) % 2 == 1:
total = total + numbers[i]
chosen.append(numbers[i])
tried = tried + 1
if total == target:
found.append(chosen)
print("subsets tried:", tried)
print("subsets that add up to", target, ":", found)
# Add 13 to numbers and run again: how many subsets are tried now?
Doubling beats squaring
| Numbers (n) | n squared steps | 2 to the power n steps |
|---|---|---|
| 10 | 100 | 1,024 |
| 20 | 400 | 1,048,576 |
| 30 | 900 | 1,073,741,824 |
| 40 | 1,600 | 1,099,511,627,776 |
The cells students fill in are left blank in this preview.
Two to the power 20 and 40
python3 -c "print(2 ** 20, 2 ** 40)"Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Years of brute force on 60 numbers
python3 -c "print(2 ** 60 // 10 ** 9 // 31536000)"Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Going further
Checking an answer to subset sum is quick, but finding one seems to need a search. Whether a quick way to find one exists is a famous open question, called P versus NP.
Write down two other puzzles where checking an answer is easy and finding one looks hard, such as a sudoku.