Created
          October 14, 2014 17:10 
        
      - 
      
- 
        Save maxrothman/d1268b72301cd32ea841 to your computer and use it in GitHub Desktop. 
    ITA word numbers puzzle solution
  
        
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
  | '''My solution to the ITA puzzle "Word Numbers" | |
| <http://www.itasoftware.com/careers/puzzle_archive.html> | |
| "If the integers from 1 to 999,999,999 are written as words, | |
| sorted alphabetically, and concatenated, what is the 51 billionth letter?" | |
| To be precise: if the integers from 1 to 999,999,999 are expressed in words | |
| (omitting spaces, 'and', and punctuation[1]), and sorted alphabetically | |
| so that the first six integers are | |
| eight | |
| eighteen | |
| eighteenmillion | |
| eighteenmillioneight | |
| eighteenmillioneighteen | |
| eighteenmillioneighteenthousand | |
| ... | |
| twothousandtwohundredtwo | |
| then reading top to bottom, left to right, the 28th letter completes | |
| the spelling of the integer "eighteenmillion". | |
| The 51 billionth letter also completes the spelling of an integer. | |
| Which one, and what is the sum of all the integers to that point? | |
| [1] For example, 911,610,034 is written | |
| "ninehundredelevenmillionsixhundredtenthousandthirtyfour"; | |
| 500,000,000 is written "fivehundredmillion"; | |
| 1,709 is written "onethousandsevenhundrednine". | |
| ''' | |
| import heapq | |
| ones = ['one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine'] | |
| teens = ['ten', 'eleven', 'twelve', 'thirteen', 'fourteen', 'fifteen', 'sixteen', | |
| 'seventeen', 'eighteen', 'nineteen'] | |
| tens = ['twenty', 'thirty', 'fourty', 'fifty', 'sixty', 'seventy', 'eighty', 'ninety'] | |
| hundred = ['hundred'] | |
| mags = ['thousand', 'million'] | |
| # 'start' must be higher than any other mags | |
| mags_val = {'thousand': 3, 'million': 6, 'billion': 9, 'start': 10} | |
| vals = {w:i+1 for i,w in enumerate(ones)} | |
| vals.update({w:i+10 for i,w in enumerate(teens)}) | |
| vals.update({w:(i+2)*10 for i,w in enumerate(tens)}) | |
| #cache of combined lists | |
| hundred_mags = hundred + mags | |
| ones_mags = ones + mags | |
| ones_teens_tens_mags = ones + teens + tens + mags | |
| ones_teens_tens = ones + teens + tens | |
| def generate_nums(cur_set, word=(), prev_mag='start', no_hundreds=False): | |
| '''A generator that yields number words in alphabetical order''' | |
| # How it works: | |
| # 'potentials' represents a search front. First, 'potentials' is populated | |
| # with all valid number words that start with 'word' and corresponding instances | |
| # of generate_nums with 'word'=that word. Then, the alphabetically first one is yielded | |
| # and replaced with a result produced by its generator. | |
| potentials = [] | |
| new_prev_mag = prev_mag | |
| new_no_hundreds = no_hundreds | |
| # Generate a list of valid number words starting with <word> using <cur_set> | |
| for num in cur_set: | |
| # Ensure that 10^3ns appear in decreasing magnitude, and every time we hit a new mag, hundreds are allowed | |
| if num in mags: | |
| if not mags_val[num] < mags_val[prev_mag]: | |
| continue | |
| else: | |
| new_prev_mag = num | |
| new_no_hundreds = False | |
| # Ensure hundreds don't appear in the wrong places | |
| elif num in hundred or num in tens: | |
| if no_hundreds: | |
| continue | |
| else: | |
| new_no_hundreds = True | |
| # Set the next set based on language patterns | |
| #TODO: try inline cache | |
| if num in ones: | |
| new_set = hundred_mags | |
| elif num in teens: | |
| new_set = mags | |
| elif num in tens: | |
| new_set = ones_mags | |
| elif num in hundred: | |
| new_set = ones_teens_tens_mags | |
| elif num in mags: | |
| new_set = ones_teens_tens | |
| else: | |
| raise ValueError("cur_set has a num not in any sets: " + num) | |
| # add the word to the list of potential minimums | |
| heapq.heappush(potentials, (''.join(word+(num,)), word+(num,), generate_nums(new_set, word+(num,), new_prev_mag, new_no_hundreds))) | |
| # As long as there are potentials in the list, get the minimum, then replace it with the result produced by its generator | |
| while len(potentials) > 0: | |
| _, min_word, min_word_gen = heapq.heappop(potentials) | |
| yield min_word | |
| try: | |
| next_word = next(min_word_gen) | |
| heapq.heappush(potentials, (''.join(next_word), next_word, min_word_gen)) | |
| except StopIteration: | |
| pass | |
| def word_to_number(word): | |
| '''Converts a list of words to the number they describe''' | |
| total = running_total = 0 | |
| for w in word: | |
| if w in vals: | |
| running_total += vals[w] | |
| elif w == 'hundred': | |
| running_total *= 100 | |
| elif w in mags_val: | |
| total += running_total * 10**mags_val[w] | |
| running_total = 0 | |
| return total + running_total | |
| def main(): | |
| stop_count = 5e5 | |
| #stop_count = 51e9 | |
| print_mult = 0 | |
| stop_count = int(stop_count) | |
| total = letter_count = word_count = 0 | |
| for word in generate_nums(ones + teens + tens): | |
| letter_count += sum(map(len, word)) | |
| word_count += 1 | |
| value = word_to_number(word) | |
| total += value | |
| if print_mult and word_count % print_mult == 0: | |
| print "wc:{:.3e} lc:{:.3e} v:{:<8d} t:{:.3e} {}".format(word_count, letter_count, value, total, ''.join(word)) | |
| #pass | |
| if letter_count >= stop_count: | |
| word = ''.join(word) | |
| diff = letter_count - stop_count | |
| print('') | |
| print('words: {:.3e} letters: {:.3e} total: {:d}'.format(word_count, letter_count, total)) | |
| #print("words: {:d} letters: {:d} total: {:d}".format(word_count, letter_count, total)) | |
| if diff == 0: | |
| print(word[:-diff-1] + '\033[1;31m' + word[-diff-1] + '\033[m') | |
| else: | |
| print(word[:-diff-1] + '\033[1;31m' + word[-diff-1] + '\033[m' + word[-diff:]) | |
| break | |
| def printem(): | |
| i=0 | |
| for word in generate_nums(ones + teens + tens): | |
| print word | |
| i += 1 | |
| if i>30: break | |
| if __name__ == "__main__": | |
| main() | |
| #print word_to_number(('eighteen', 'million', 'eighteen', 'thousand', 'eight', 'hundred', 'fifty', 'six')) | |
| #printem() | 
  
    Sign up for free
    to join this conversation on GitHub.
    Already have an account?
    Sign in to comment