Binary Search
Calvin (Deutschbein)
W13Fri: 22 Nov
Announcements
- Special out-of-sequence lecture Monday.
- Adventure Ongoing
- You should be thinking about how you're going to do QUIT/HELP/LOOK
- Advising ongoing
- If you encounter any problem, email me immediately
- I'll be doing triage:
- If you don't get an email back quickly, either
- the problem is not urgent and I'll solve it latter, or
- I am fleeing a zamboni IRL and require thoughts & prayers stat.
Today
- Do things fast
- Not adventure relevant
- Is write-good-code relevant.
- Searchin'
Throwback Wordle
- Read in some text (hey just like adventure)
- See if it's in a list of words (just like adventure)
- Hella words (this is where it's different)
- Do thing accordingly.
- Wordle's ENGLISH_WORDS had 127145 words
- It's here.
- That would take a long time to read.
Checking Words
- The obvious way to see if a word is in a list is with a loop:
def is_my_word_english(my_word:str)->bool:
for word in ENGLISH_WORDS:
if word == my_word:
return True
return False
- This is technically correct.
- It is a special new type of wrong: algorithmically incorrect.
Technical vs Algorithmic Correctness
- Say I want to make a peanut butter sandwich
- This is an easy assumption as it is true at all points in time
- I could construct a peanut butter sandwich by:
- Driving to Winco Foods or equivalent
- Purchasing one peanut
- Driving to Lowe's Home Improvement or equivalent
- Buying a grinder
- Driving home
- Grinding one peanut
- Storing the peanut butter
- Returning the grinder
- Looping.
- This actually how I lived my life before discovering list comprehensions, now I buy 100 grinders at a time. (This is a joke it is not serious).
Technical vs Algorithmic Correctness
- Say I want to make a peanut butter sandwich
- I have 20 metric tons of peanuts and 40 grinders.
- I divide the tons of peanuts into equal parts until I have 40 parts
- I grind them all at once.
- This technique of "divide-and-conquer" is just as technically correct - it still make peanut butter.
- It is more algorithmically correct - it wastes less effort.
Checking Words
- One peanut at a time:
def is_my_word_english(my_word:str)->bool:
for peanut in ENGLISH_WORDS:
if peanut == my_word:
return True
return False
- This is technically correct.
- Is there a way to divide it in half?
Divide and Conquer
- We can think of words alphabetically, and check to see if, e.g.:
- The first letter of a word is in the first half of the alphabet
>>> abcs = 'abcdefghijklmnopqrstuvwxyz'
>>> abcs[len(abcs)//2]
'n'
>>> "vesuvius" < 'n'
False
>>> "glissando" < 'n'
True
Divide and Conquer
- We can think of words alphabetically, and check to see if, e.g.:
- If words are in alphabetical order, it is easy enough to see if a word is in the first half of the words:
>>> words = ['abraxis', 'canopy', 'palladio', 'snafu', 'turbidity', 'unceasingly', 'zymox']
>>> words[len(words)//2]
'snafu'
>>> "vesuvius" < 'snafu'
False
>>> "glissando" < 'snafu'
True
Divide and Conquer
- How did this work algorithimically?
- We took one word and a sorted list of words.
- We checked if our word was more or less than the middle of the sorted list
- Let's write that out:
def is_first_half(my_word:str, word_list:list[str]) -> bool:
length = len(word_list)
half_length = length // 2 # we need an integer!
middle = word_list[middle]
return my_word < middle
- Note I do not use any if statements!
Divide and Conquer
- How did this work algorithimically?
- This only tells us which half a word would be in, not if it is in that half.
def is_first_half(my_word:str, word_list:list[str]) -> bool:
return my_word < word_list[len(word_list) // 2]
- We could check the whole half but...
- Checking everything is just what we want to avoid!
- We can use the same trick again - keep taking halves...
if is_first_half(my_word, word_list):
# keep only the first half
word_list = word_list[:half_length]
else:
# keep only the second half
word_list = word_list[half_length:]
Divide and Conquer
- How did this work algorithimically?
- We can keep talking halves until there is only one word left.
while len(word_list) > 1:
half_length = len(word_list) // 2
if is_first_half(my_word, word_list):
# keep only the first half
word_list = word_list[:half_length]
else:
# keep only the second half
word_list = word_list[half_length:]
- Then we can see if that's the word we want.
while len(word_list) > 2:
...
return word_list == word_list[0] # only one word left!
Divide and Conquer
- Altogether
def is_word(my_word:str, word_list:list[str]) -> bool:
while len(word_list) > 1:
half_length = len(word_list) // 2
if my_word < word_list[half_length]:
# keep only the first half
word_list = word_list[:half_length]
else:
# keep only the second half
word_list = word_list[half_length:]
return my_word == word_list[0]
- More in two weeks.
Today
- Do things fast
- Not adventure relevant
- Is write-good-code relevant.
- Searchin'
Announcements
- Special out-of-sequence lecture Monday.
- Alonzo Church's Lambda Calculus 🙏
- Adventure Ongoing
- You should be thinking about how you're going to do QUIT/HELP/LOOK
- Advising ongoing
- If you encounter any problem, email me immediately
- I'll be doing triage:
- If you don't get an email back quickly, either
- the problem is not urgent and I'll solve it latter, or
- I am fleeing a zamboni IRL and require thoughts & prayers stat.