# \[SOLUTION\]\[QUIZ\] Pangrams (#86)

**URL:** <https://rubytalk.org/t/solution-quiz-pangrams-86/28900>\
**Category:** ruby-talk\
**Created:** [12 July 2006 16:52 UTC](https://rubytalk.org/t/solution-quiz-pangrams-86/28900 "2006-07-12T16:52:07Z")\
**Posts on this page:** 1\
**Page:** 1

<div class="post-metadata">

**Author:** ![John\_Baylor](https://avatars.discourse-cdn.com/v4/letter/j/919ad9/32.png) [@John\_Baylor](https://rubytalk.org/u/John_Baylor)\
**Post date:** [12 July 2006 16:52 UTC](https://rubytalk.org/t/solution-quiz-pangrams-86/28900/1 "2006-07-12T16:52:07Z")

</div>

I let it run overnight in two irb sessions (with two different starter  
sentences) and they both finally converged - but I don't know how long it  
took.  
Looking at the other solutions, I think I have plenty to learn about Ruby -  
especially when to modify an existing class, such as Integer or FixNum, and  
when to just do it in the domain-specific class. Everything here is in the  
domain-specific class but could be simpler in others.

iteration 2860162:  
My sentence contains three 'a's, one 'b', three 'c's, two 'd's, thirty 'e's,  
five 'f's, four 'g's, eight 'h's, nine 'i's, one 'j', one 'k',  
one 'l', two 'm's, twenty-one 'n's, seventeen 'o's, one 'p', one 'q', eight  
'r's, twenty-four 's's, twenty-two 't's, four 'u's, three 'v's,  
eight 'w's, two 'x's, six 'y's and one 'z'.

iteration 3224814:  
John's cool pangram program created this sentence which contains exactly  
eight 'a's, one 'b', seven 'c's, three 'd's, forty-one 'e's, eight  
'f's, five 'g's, thirteen 'h's, twelve 'i's, two 'j's, one 'k', four 'l's,  
three 'm's, twenty-two 'n's, seventeen 'o's, three 'p's, one 'q'  
, fifteen 'r's, thirty-three 's's, twenty-six 't's, four 'u's, seven 'v's,  
seven 'w's, four 'x's, six 'y's and one 'z'.

I haven't tried a French version yet but it would likely work...

> **···**
>
> #===============================================================  
> require 'Win32API' # for checking the keyboard to stop and show the  
> progress
> 
> EXAMPLE\_SENTENCE = "Darren's ruby panagram program found this sentence which  
> contains exactly  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;nine 'a's, two 'b's, five 'c's, four 'd's, thirty-five 'e's, nine  
> 'f's,  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;three 'g's, nine 'h's, sixteen 'i's, one 'j', one 'k', two 'l's,  
> three 'm's,  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;twenty-seven 'n's, fourteen 'o's, three 'p's, one 'q', fifteen 'r's,  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;thirty-four 's's, twenty-two 't's, six 'u's, six 'v's, seven 'w's,  
> six 'x's,  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;seven 'y's, and one 'z'."
> 
> class Pangram  
> &nbsp;&nbsp;DEFAULT\_SENTENCE = "John's cool pangram program created this sentence  
> which contains exactly"  
> &nbsp;&nbsp;ENGLISH\_NUMBERS = %w[? one two three four five six seven eight nine ten  
> eleven twelve thirteen fourteen fifteen sixteen seventeen eighteen nineteen  
> twenty]  
> &nbsp;&nbsp;ENGLISH\_THIRTY\_PLUS = %w[thirty forty fifty sixty seventy eighty ninety]  
> &nbsp;&nbsp;#this isn't quite right for french: 21 and 31 and 41 are "thirty and  
> one"...  
> &nbsp;&nbsp;FRENCH\_NUMBERS = %w[? un deux trois quatre cinq six sept huit neuf dix  
> onze douze treize quatorze quinze seize dix-sept dix-huit dix-neuf vingt]  
> &nbsp;&nbsp;FRENCH\_THIRTY\_PLUS = %w[trente quarante cinquante soixante soixante-dix  
> quatre-vingt quatre-vingt-dix]  
> &nbsp;&nbsp;MAX\_COUNT = 50
> 
> &nbsp;&nbsp;attr\_accessor :sentence
> 
> &nbsp;&nbsp;# expect args to be [sentence,number\_words,thirty\_plus]  
> &nbsp;&nbsp;def initialize \*args  
> &nbsp;&nbsp;&nbsp;&nbsp;@attempts = Hash.new(nil)  
> &nbsp;&nbsp;&nbsp;&nbsp;@sentence = args[0] || DEFAULT\_SENTENCE  
> &nbsp;&nbsp;&nbsp;&nbsp;@words = args[1] || ENGLISH\_NUMBERS  
> &nbsp;&nbsp;&nbsp;&nbsp;@words = @words.dup  
> &nbsp;&nbsp;&nbsp;&nbsp;@thirty\_plus = args[1] || ENGLISH\_THIRTY\_PLUS  
> &nbsp;&nbsp;&nbsp;&nbsp;count = 30  
> &nbsp;&nbsp;&nbsp;&nbsp;@thirty\_plus.each do |word|  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@words[count] = word  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;count += 10  
> &nbsp;&nbsp;&nbsp;&nbsp;end
> 
> &nbsp;&nbsp;&nbsp;&nbsp;extend\_wordlist MAX\_COUNT  
> &nbsp;&nbsp;&nbsp;&nbsp;#puts "@sentence=#{@sentence}"  
> &nbsp;&nbsp;&nbsp;&nbsp;# only add one the first time, all the others already have the extra one
> 
> &nbsp;&nbsp;&nbsp;&nbsp;@result = letter\_frequency @sentence  
> &nbsp;&nbsp;&nbsp;&nbsp;@result.each { |k,v| @result[k] += 1 }  
> &nbsp;&nbsp;&nbsp;&nbsp;#puts full\_sentence( @result )  
> &nbsp;&nbsp;&nbsp;&nbsp;self  
> &nbsp;&nbsp;end
> 
> &nbsp;&nbsp;def distance hash1, hash2  
> &nbsp;&nbsp;&nbsp;&nbsp;dist = 0  
> &nbsp;&nbsp;&nbsp;&nbsp;hash1.each { |k,v| dist += (v - hash2[k]).abs }  
> &nbsp;&nbsp;&nbsp;&nbsp;return dist  
> &nbsp;&nbsp;end
> 
> &nbsp;&nbsp;def approximate current, start  
> &nbsp;&nbsp;&nbsp;&nbsp;# apply each letter-word to an empty list  
> &nbsp;&nbsp;&nbsp;&nbsp;approximation = {}  
> &nbsp;&nbsp;&nbsp;&nbsp;"a".upto("z") { |c| approximation[c] = start }  
> &nbsp;&nbsp;&nbsp;&nbsp;current.each do |k,v|  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@words[current[k]].split('').each do |c|  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;approximation[c] += 1 if approximation[c] and approximation[c] \>= 0  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;return approximation  
> &nbsp;&nbsp;end
> 
> &nbsp;&nbsp;def unique\_solution sentence,count  
> &nbsp;&nbsp;&nbsp;&nbsp;#puts "#{count}: crypt=#{crypt} for #{sentence}"  
> &nbsp;&nbsp;&nbsp;&nbsp;if @attempts[sentence] != nil  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;puts "Oops, we've already tried this solution! (#{count} ==\>  
> #{@attempts[sentence]})"  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return nil  
> &nbsp;&nbsp;&nbsp;&nbsp;else  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@attempts[sentence] = count  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return 1  
> &nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;end
> 
> &nbsp;&nbsp;def make\_randomly\_similar! dest, src  
> &nbsp;&nbsp;&nbsp;&nbsp;diffs = ""  
> &nbsp;&nbsp;&nbsp;&nbsp;dest.each { |k,v| diffs += k if v != src[k] }  
> &nbsp;&nbsp;&nbsp;&nbsp;c = diffs[(rand \* diffs.length).to\_i].chr  
> &nbsp;&nbsp;&nbsp;&nbsp;dest[c] = src[c]  
> &nbsp;&nbsp;end
> 
> &nbsp;&nbsp;def solve  
> &nbsp;&nbsp;&nbsp;&nbsp;iteration = 0  
> &nbsp;&nbsp;&nbsp;&nbsp;hash = {}  
> &nbsp;&nbsp;&nbsp;&nbsp;s = full\_sentence( @result )  
> &nbsp;&nbsp;&nbsp;&nbsp;hash = letter\_frequency( s )  
> &nbsp;&nbsp;&nbsp;&nbsp;w = Win32API.new( "MSVCRT", "\_kbhit", [], 'i')  
> &nbsp;&nbsp;&nbsp;&nbsp;io = IO.new(0)  
> &nbsp;&nbsp;&nbsp;&nbsp;while hash != @result ## and unique\_solution(s,iteration) != nil  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;iteration += 1  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;dist = distance( hash, @result )  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;puts "#{iteration}: #{dist}"  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;c = make\_randomly\_similar! @result, hash  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#puts "iteration #{iteration}: #{dist},#{c} ==\>  
> #{hash.sort.collect{|i| i[1] }.join(',') }"  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;s = full\_sentence( @result )  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;hash = letter\_frequency( s )  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if w.Call() \> 0  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;io.getc  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;puts "iteration #{iteration}: #{dist} ==\> #{hash.sort.collect {|i|  
> "#{i[0]}\>#{i[1]}" }.join(',') }"  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;puts s  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;puts "\nSOLVED!\n #{s}"  
> &nbsp;&nbsp;&nbsp;&nbsp;puts "iteration #{iteration}: #{dist} ==\> #{hash.sort.collect {|i| i[1]  
> }.join(',') }"  
> &nbsp;&nbsp;&nbsp;&nbsp;show\_freq letter\_frequency(s)  
> &nbsp;&nbsp;end
> 
> &nbsp;&nbsp;def full\_sentence hash  
> &nbsp;&nbsp;&nbsp;&nbsp;s = @sentence.rstrip + " "  
> &nbsp;&nbsp;&nbsp;&nbsp;"a".upto("x") do |letter|  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;s += to\_letter\_count( letter, hash[letter] ) + ", "  
> &nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;s += to\_letter\_count( "y", hash["y"] ) + " and "  
> &nbsp;&nbsp;&nbsp;&nbsp;s += to\_letter\_count( "z", hash["z"] ) + "."  
> &nbsp;&nbsp;&nbsp;&nbsp;return s  
> &nbsp;&nbsp;end
> 
> &nbsp;&nbsp;def to\_letter\_count letter, count  
> &nbsp;&nbsp;&nbsp;&nbsp;ret = "#{@words[count]} '#{letter}'"  
> &nbsp;&nbsp;&nbsp;&nbsp;ret += "s" if count \> 1  
> &nbsp;&nbsp;&nbsp;&nbsp;return ret  
> &nbsp;&nbsp;end
> 
> &nbsp;&nbsp;def extend\_wordlist count  
> &nbsp;&nbsp;&nbsp;&nbsp;0.upto(count) do |n|  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@words[n] = @words[10\*(n/10).floor] + '-' + @words[n % 10] unless  
> @words[n]  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#puts "#{n}: #{@words[n]}"  
> &nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;end
> 
> &nbsp;&nbsp;def show\_freq hash  
> &nbsp;&nbsp;&nbsp;&nbsp;hash.sort.each { |k, v| puts "#{k}: #{v}" }  
> &nbsp;&nbsp;end
> 
> &nbsp;&nbsp;def letter\_frequency \*sentence  
> &nbsp;&nbsp;&nbsp;&nbsp;s = sentence[0] || @sentence  
> &nbsp;&nbsp;&nbsp;&nbsp;s = s.downcase.gsub(/^a-z/,'')  
> &nbsp;&nbsp;&nbsp;&nbsp;s.gsub!(/ |,|'|-|\./,'')  
> &nbsp;&nbsp;&nbsp;&nbsp;freq = Hash.new(0)  
> &nbsp;&nbsp;&nbsp;&nbsp;"a".upto("z") { |c| freq[c] = 0 }  
> &nbsp;&nbsp;&nbsp;&nbsp;s.split('').each { |c| freq[c] += 1 }  
> &nbsp;&nbsp;&nbsp;&nbsp;freq  
> &nbsp;&nbsp;end  
> end
