[QUIZ] Panagrams (#86)

The three rules of Ruby Quiz:

1. Please do not post any solutions or spoiler discussion for this quiz until
48 hours have passed from the time on this message.

2. Support Ruby Quiz by submitting ideas as often as you can:

http://www.rubyquiz.com/

3. Enjoy!

Suggestion: A [QUIZ] in the subject of emails about the problem helps everyone
on Ruby Talk follow the discussion. Please reply to the original quiz message,
if you can.

···

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

by Darren Kirby

One thing that interests me are word puzzles and language oddities. One such
example is the self-documenting panagram. If a panagram is a sentence that uses
every letter in the alphabet, then a self-documenting panagram is a sentence
that enumerates its own letter count. Simple enough, but what if we state that
the letter count must be spelled ie: 'twenty-seven' instead of '27'. Now we
have a challenge.

A while back I wrote a script in Python that finds these sentences. Today I
rewrote it in Ruby and it found me this sentence:

  Darren's ruby panagram program found this sentence which contains exactly
  nine 'a's, two 'b's, five 'c's, four 'd's, thirty-five 'e's, nine 'f's,
  three 'g's, nine 'h's, sixteen 'i's, one 'j', one 'k', two 'l's, three 'm's,
  twenty-seven 'n's, fourteen 'o's, three 'p's, one 'q', fifteen 'r's,
  thirty-four 's's, twenty-two 't's, six 'u's, six 'v's, seven 'w's, six 'x's,
  seven 'y's, and one 'z'.

My script does have its problems, and I would love to see what kind of code the
Ruby experts could come up with to find self-documenting panagrams.

There is a lot more info on self-documenting panagrams at this address:

  http://www.cs.indiana.edu/~tanaka/GEB/pangram.txt

So is it "pangram" or "panagram"?

···

There is a lot more info on self-documenting panagrams at this address:

        http://www.cs.indiana.edu/~tanaka/GEB/pangram.txt

Is it a spoiler if we post a resulting pangram (no code)?

I got one in:
real 1m17.089s
user 1m8.444s
sys 0m2.492s

on an athlon 64 300+

Brian

Thank the lord, I can go to sleep now:

This terribly inefficient pangram contains five a's, two b's, three c's, two d's, thirty-one e's, six f's, four g's, ten h's, sixteen i's, one j, one k, three l's, two m's, twenty n's, thirteen o's, two p's, one q, twelve r's, twenty-eight s's, twenty-eight t's, three u's, three v's, nine w's, four x's, six y's and one z.

Sadly I wasn't timing it when it ran. But I'd estimate it took 5 minutes or so. Can't wait to see how the rest of the group is getting the time so low.
-Mat

This isn't much to look at, but it appears to be > 48 hours since the posting. So I thought I'd post it anyway. I still can't generate the example pangrams given at http://www.cs.indiana.edu/~tanaka/GEB/pangram.txt but it does generate this pangram in 1-5 minutes:

This terribly inefficient pangram contains five a's, two b's, three c's, two d's, thirty-one e's, six f's, four g's, ten h's, sixteen i's, one j, one k, three l's, two m's, twenty n's, thirteen o's, two p's, one q, twelve r's, twenty-eight s's, twenty-eight t's, three u's, three v's, nine w's, four x's, six y's and one z.

It's a basic randomized robinsoniziz..ing. With a rather absurd amount of test code.
-Mat

-- pangram.rb
#!/usr/bin/ruby

# Ruby Quiz 86, Pangrams: Solution by Mat Schaffer <schapht@gmail.com>
# uses numeric_spell library available from http://tanjero.com/svn/plugins/numeric_spell/

require 'numeric_spell'

class SelfDocumentingPangram
   LETTERS = (?a..?z).to_a

   def initialize(starter_string = "This test starter contains ")
     @start = starter_string
   end

   def to_s
     current = count(@start)
     actual = count(add_count(current))
     while current != actual
       LETTERS.each do |letter|
         current[letter] = rand_between(current[letter], actual[letter])
       end
       actual = count(add_count(current))
     end
     add_count(current)
   end

   def rand_between a,b
     range = (a - b).abs + 1
     rand(range) + [a,b].min
   end

   def add_count(counts)
     @start + counts_to_s(counts)
   end

   def count_to_s(char, count)
     if count != 1
       count.spell + " " + char.chr + "'s"
     else
       count.spell + " " + char.chr
     end
   end

   def counts_to_s(count)
     string_counts =
     LETTERS.each do |letter|
       string_counts << count_to_s(letter, count[letter])
     end
     last = string_counts.pop
     string_counts.join(", ") + " and " + last + "."
   end

   def count(string)
     count = Hash.new(0)
     string.downcase.each_byte do |letter|
       if LETTERS.include? letter
         count[letter] += 1
       end
     end
     count
   end
end

if ARGV[0] =~ /test/i
   require 'test/unit'

   class TestSelfDocumentingPangram < Test::Unit::TestCase
     # checks that count will yield accurate counts for only letters, ignoring case
     def test_count
       # check basic case containing only a..z
       string = ('a'..'z').to_a.to_s
       count = SelfDocumentingPangram.new.count(string)
       assert_equal(26, count.length)
       count.each do |key, value|
         assert_equal(1, value)
       end

       # check case for a..z, A..Z, and some punctiation that we're likely to use
       string = (('a'..'z').to_a + ('A'..'Z').to_a + ['\'', ',', '.', '-']).to_s
       count = SelfDocumentingPangram.new.count(string)
       assert_equal(26, count.length)
       count.each do |key, value|
         assert_equal(2, value)
       end
     end

    def test_count_to_s
       assert_equal("one a", SelfDocumentingPangram.new.count_to_s(?a, 1))
       assert_equal("fifteen z's", SelfDocumentingPangram.new.count_to_s(?z, 15))
       assert_equal("forty-two d's", SelfDocumentingPangram.new.count_to_s(?d, 42))
     end

     def test_counts_to_s
       start = "The last of these contained "
       expected = "two a's, zero b's, one c, one d, four e's, one f, zero g's, two h's, one i, zero j's, zero k's, one l, zero m's, two n's, two o's, zero p's, zero q's, zero r's, two s's, four t's, zero u's, zero v's, zero w's, zero x's, zero y's and zero z's."
       pangram = SelfDocumentingPangram.new
       result = pangram.counts_to_s(pangram.count(start))
       assert_equal(expected, result)
     end

     def test_rand_between
       100.times do
         a = rand(100)
         b = [a, rand(100)].max
         c = SelfDocumentingPangram.new.rand_between(a,b)
         assert (a..b) === c, "#{c} is not between #{a} and #{b}"
       end
     end

     def test_add_count
       pangram = SelfDocumentingPangram.new("hi ")
       count = Hash.new(0)
       expected = "hi " + pangram.counts_to_s(Hash.new(0))
       assert_equal(expected, pangram.add_count(Hash.new(0)))
     end

     # runs the SelfDocumentingPangram class to verify that it can produce the pangrams found at
     # http://www.cs.indiana.edu/~tanaka/GEB/pangram.txt
     def test_to_s
       pangram1 = "This pangram tallies five a's, one b, one c, two d's, twenty-eight e's, eight f's, six g's, eight h's, thirteen i's, one j, one k, three l's, two m's, eighteen n's, fifteen o's, two p's, one q, seven r's, twenty-five s's, twenty-two t's, four u's, four v's, nine w's, two x's, four y's and one z."
       assert_equal(pangram1, SelfDocumentingPangram.new("This pangram tallies ").to_s)

       #pangram2 = "This computer-generated pangram contains six a's, one b, three c's, three d's, thirty-seven e's, six f's, three g's, nine h's, twelve i's, one j, one k, two l's, three m's, twenty-two n's, thirteen o's, three p's, one q, fourteen r's, twenty-nine s's, twenty-four t's, five u's, six v's, seven w's, four x's, five y's and one z."
       #assert_equal(pantram2, SelfDocumentingPangram.new("This computer-generated pangram contains ").to_s)
     end

     # This is mainly a sanity check to see that a pangram will evaluate to itself when counted and regenerated
     def test_approach
       prefix = "This pangram tallies "
       solution = "This pangram tallies five a's, one b, one c, two d's, twenty-eight e's, eight f's, six g's, eight h's, thirteen i's, one j, one k, three l's, two m's, eighteen n's, fifteen o's, two p's, one q, seven r's, twenty-five s's, twenty-two t's, four u's, four v's, nine w's, two x's, four y's and one z."
       pangram = SelfDocumentingPangram.new(prefix)
       assert_equal(solution, pangram.add_count(pangram.count(solution)))

       prefix = "This terribly inefficient pangram contains "
       solution = "This terribly inefficient pangram contains five a's, two b's, three c's, two d's, thirty-one e's, six f's, four g's, ten h's, sixteen i's, one j, one k, three l's, two m's, twenty n's, thirteen o's, two p's, one q, twelve r's, twenty-eight s's, twenty-eight t's, three u's, three v's, nine w's, four x's, six y's and one z."
       pangram = SelfDocumentingPangram.new(prefix)
       assert_equal(solution, pangram.add_count(pangram.count(solution)))
     end
   end
else
   puts SelfDocumentingPangram.new("This terribly inefficient pangram contains ").to_s
end

···

On Jul 7, 2006, at 11:01 AM, Ruby Quiz wrote:

The three rules of Ruby Quiz:

1. Please do not post any solutions or spoiler discussion for this quiz until
48 hours have passed from the time on this message.

2. Support Ruby Quiz by submitting ideas as often as you can:

http://www.rubyquiz.com/

3. Enjoy!

Suggestion: A [QUIZ] in the subject of emails about the problem helps everyone
on Ruby Talk follow the discussion. Please reply to the original quiz message,
if you can.

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

by Darren Kirby

One thing that interests me are word puzzles and language oddities. One such
example is the self-documenting panagram. If a panagram is a sentence that uses
every letter in the alphabet, then a self-documenting panagram is a sentence
that enumerates its own letter count. Simple enough, but what if we state that
the letter count must be spelled ie: 'twenty-seven' instead of '27'. Now we
have a challenge.

A while back I wrote a script in Python that finds these sentences. Today I
rewrote it in Ruby and it found me this sentence:

  Darren's ruby panagram program found this sentence which contains exactly
  nine 'a's, two 'b's, five 'c's, four 'd's, thirty-five 'e's, nine 'f's,
  three 'g's, nine 'h's, sixteen 'i's, one 'j', one 'k', two 'l's, three 'm's,
  twenty-seven 'n's, fourteen 'o's, three 'p's, one 'q', fifteen 'r's,
  thirty-four 's's, twenty-two 't's, six 'u's, six 'v's, seven 'w's, six 'x's,
  seven 'y's, and one 'z'.

My script does have its problems, and I would love to see what kind of code the
Ruby experts could come up with to find self-documenting panagrams.

There is a lot more info on self-documenting panagrams at this address:

  http://www.cs.indiana.edu/~tanaka/GEB/pangram.txt

Hi,

here is my solution: (some remarks are below)

···

----------------------------------------------------------------------
require 'narray'

class NArray;
  include Enumerable;
  srand(1)
end

class Fixnum
  NUMS = %w(zero one two three four five six seven eight nine ten eleven) +
    %w(twelve thirteen fourteen fifteen sixteen seventeen eighteen nineteen)
  TENS = %w(no teen twenty thirty forty fifty sixty seventy eighty ninety)

  def to_english
    return NUMS[self] if self < 20
    return TENS[self / 10] if (self % 10).zero?
    TENS[self / 10] + '-' + NUMS[self % 10]
  end
end

class String
  def to_na
    freq = NArray.int(26)
    each_byte do |b|
      freq[b - ?a] += 1 if b >= ?a and b <= ?z
      freq[b - ?A] += 1 if b >= ?A and b <= ?Z
    end
    freq
  end
end

text = "This is a pangram from simon, it contains "
nums = NArray[*(0..99).map{|i| i.to_english.to_na + (i>1 ? 's' : '').to_na}]
seed = text.to_na + 'and'.to_na + 1
guess = NArray.int(26).fill!(1)
real = seed + nums[true, guess].sum(1)
rnd = NArray.float(26)

while real != guess
  guess.add! rnd.random!.mul!(real.sbt!(guess)).round
  real.fill!(0).add!(seed).add!(nums[true, guess].sum(1))
end

s = guess.zip([*(' a'..' z')]).map{|g, l| g.to_english + l + (g>1 ? "'s":"")}
result = text + s[0..-2].join(', ') + ' and ' + s.last + '.'

puts result
puts(result.to_na == guess)
----------------------------------------------------------------------

I always wanted to look at NArray because a well known rubyist keeps
promoting it - if it's good enough for the Nasa than it may be good
enough for word puzzles :slight_smile:

This algorithm is stupid, but why use a scissor if u can have a chain saw?
NArray is such a chain saw, so it doesn't really matter how well you aim.

Ok, the details. First a two dimensional NArray is created containing the
frequencies of all 26 letters in the words 'zero' upto 'ninety-nine'. (nums)

seed contains the frequencies of the static part of the sentence - the
preamble the 'and' and 1 of each letter.

guess is the actual guess and will change over time (very often) I start
with a 1 for every letter (which is obviously wrong)

real contains the frequencies for all letters if one would spell out the
sentence described in guess (one 'a', one 'b', one 'c'... for the initial
guess). To get this we select the lines from nums that correspond to the
numbers (26 times 'one' for the first guess) and sums every column up.
(you start to see the chain saw?)

Ok, now we can start the massacre.

If real == guess we are ready, if not we calculate the error for each
letter (in one step of course: real.sbt!(guess)).
We multiply each error with a random value 0 <= rand < 1
(in one step: rnd.ramdom!.mul!) and add this randomized error back onto
the guess. (again, one step)

All that is left is to calculate the real frequencies again and check
if we hit the spot.

(this is my second version, the other one is longer and uses a slightly
other algorithm (and is sometimes even faster) but i like this one)

cheers

Simon

Hello

First post here, by the way. Greetings to all:) New to ruby, love it, slaps
on backs all round.

While I haven't solved the pangram problem, I did find what appears to be a
bug in Numeric.divmod on large numbers, as can be seen when using
Bignum.modulo:

irb(main):017:0> 1e22 % 1e21
=> 0.0
irb(main):018:0> 1e23 % 1e22
=> 9.99999999999999e+021

whoops!

Also, I had a bit of fun hacking away at the plumbing required for the
quiz. My solution to generating spelt numbers is, while neither fast nor
optimally pretty I'm sure, novel and rather flexible.

I wrote a little RangeMap class which will map a continuous set of variable
ranges to values. Then I used it to map ranges to spelling rules (in the
form of proc objects). The rules can recursively call eachother using
further lookups on the RangeMap. This system allows easy implentation of
exceptions (such as "twelve hundred" etc), and translation into different
languages or variations of english. Also it could be used to solve the
pangrams problem directly by configuring the rules to tally the characters
rather than generate the actual strings.

class RangeMap

  def initialize
    @values = Hash.new
    @ranges =
  end

  #Insert your range by specifying the lower bound.
  #RangeMap generates the upper value based on what's
  #already in the map. While not ideal, it makes it
  #a lot easier to keep the set continuous.
  def insert range_first, value
    @values[range_first] = value
    lowers = @values.keys.sort
    uppers = @values.keys.sort
    lowers.pop
    uppers.shift
    @ranges =
    for i in 0...lowers.size do
      @ranges << (lowers[i]...uppers[i])
    end
  end

  def find n
    if n < @ranges.first.first || n > @ranges.last.last
        raise "Number outside ranges: #{@ranges.first.first}-#{@
ranges.last.last}"
    end
    range_first = binary_search(n, 0, @ranges.size)
    @values[range_first]
  end

protected
  def binary_search n, a, b
    middle = (a + b) / 2
    range = @ranges[middle]
    if n < range.first
      binary_search(n, a, middle)
    elsif n >= range.last
      binary_search(n, middle, b)
    else
      range.first
    end
  end

end

class Integer

  @@rules = RangeMap.new

  [{ :first => 0, :name => "zero" }, { :first => 1, :name => "one" },
  { :first => 2, :name => "two" }, { :first => 3, :name => "three" },
  { :first => 4, :name => "four" }, { :first => 5, :name => "five" },
  { :first => 6, :name => "six" }, { :first => 7, :name => "seven" },
  { :first => 8, :name => "eight" }, { :first => 9, :name => "nine" },
  { :first => 10, :name => "ten" }, { :first => 11, :name => "eleven" },
  { :first => 12, :name => "twelve" }, { :first => 13, :name => "thirteen"
},
  { :first => 14, :name => "fourteen" }, { :first => 15, :name => "fifteen"
},
  { :first => 16, :name => "sixteen" }, { :first => 17, :name => "seventeen"
},
  { :first => 18, :name => "eighteen" },
  { :first => 19, :name => "nineteen" }].each do |single|
    name = single[:name].freeze
    @@rules.insert(single[:first], lambda {|n| name})
  end

  [{ :first => 20, :name => "twenty" },
  { :first => 30, :name => "thirty" },
  { :first => 40, :name => "forty" },
  { :first => 50, :name => "fifty" },
  { :first => 60, :name => "sixty" },
  { :first => 70, :name => "seventy" },
  { :first => 80, :name => "eighty" },
  { :first => 90, :name => "ninety" }].each do |ten|
    divisor = ten[:first]
    name = ten[:name].freeze
    @@rules.insert(divisor, lambda do |n|
      spelt = name.dup
      remainder = n % divisor
      spelt << "-" + execute_rule(remainder) if remainder != 0
      spelt
    end)
  end

  [{ :first => 1E2.to_i, :name => "hundred" },
  { :first => 1E3.to_i, :name => "thousand" },
  { :first => 1E6.to_i, :name => "million" },
  { :first => 1E9.to_i, :name => "billion" },
  { :first => 1E12.to_i, :name => "trillion" },
  { :first => 1E15.to_i, :name => "quadrillion" },
  { :first => 1E18.to_i, :name => "quintillion" },
  { :first => 1E21.to_i, :name => "sextillion" },
  { :first => 1E24.to_i, :name => "septillion" },
  { :first => 1E27.to_i, :name => "octillion" },
  { :first => 1E30.to_i, :name => "nonillion" },
  { :first => 1E33.to_i, :name => "decillion" },
  { :first => 1E36.to_i, :name => "undecillion" },
  { :first => 1E39.to_i, :name => "duodecillion" },
  { :first => 1E42.to_i, :name => "tredecillion" },
  { :first => 1E45.to_i, :name => "quattuordecillion" },
  { :first => 1E48.to_i, :name => "quindecillion" },
  { :first => 1E51.to_i, :name => "sexdecillion" },
  { :first => 1E54.to_i, :name => "septendecillion" },
  { :first => 1E57.to_i, :name => "octodecillion" },
  { :first => 1E60.to_i, :name => "novemdecillion" },
  { :first => 1E63.to_i, :name => "vigintillion" }].each do |big|
    divisor = big[:first]
    name = " " + big[:name].freeze
    @@rules.insert(divisor, lambda do |n|
      spelt = execute_rule(n/divisor) + name
      remainder = n % divisor
      if (remainder > 0)
        if remainder < 100
          spelt << " and "
        else
          spelt << ", "
        end
        spelt << execute_rule(remainder)
      end
      spelt
    end)
  end

  def self.execute_rule n
    @@rules.find(n).call(n)
  end

  def to_english
    self.class.execute_rule(self)
  end
end

puts 123456789.to_english

···

On 7/7/06, Ruby Quiz <james@grayproductions.net> wrote:

The three rules of Ruby Quiz:

1. Please do not post any solutions or spoiler discussion for this quiz
until
48 hours have passed from the time on this message.

2. Support Ruby Quiz by submitting ideas as often as you can:

http://www.rubyquiz.com/

3. Enjoy!

Suggestion: A [QUIZ] in the subject of emails about the problem helps
everyone
on Ruby Talk follow the discussion. Please reply to the original quiz
message,
if you can.

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

by Darren Kirby

One thing that interests me are word puzzles and language oddities. One
such
example is the self-documenting panagram. If a panagram is a sentence that
uses
every letter in the alphabet, then a self-documenting panagram is a
sentence
that enumerates its own letter count. Simple enough, but what if we state
that
the letter count must be spelled ie: 'twenty-seven' instead of '27'. Now
we
have a challenge.

A while back I wrote a script in Python that finds these sentences. Today
I
rewrote it in Ruby and it found me this sentence:

        Darren's ruby panagram program found this sentence which contains
exactly
        nine 'a's, two 'b's, five 'c's, four 'd's, thirty-five 'e's, nine
'f's,
        three 'g's, nine 'h's, sixteen 'i's, one 'j', one 'k', two 'l's,
three 'm's,
        twenty-seven 'n's, fourteen 'o's, three 'p's, one 'q', fifteen
'r's,
        thirty-four 's's, twenty-two 't's, six 'u's, six 'v's, seven 'w's,
six 'x's,
        seven 'y's, and one 'z'.

My script does have its problems, and I would love to see what kind of
code the
Ruby experts could come up with to find self-documenting panagrams.

There is a lot more info on self-documenting panagrams at this address:

        http://www.cs.indiana.edu/~tanaka/GEB/pangram.txt

#!ruby

# First, we need a way to convert integers into English.
# Fortunately, that was quiz #25, so I just stole one of
# the many solutions offered there.

···

#
# More commentary after the swiped code

# stolen from Glenn Parker's solution to ruby
# quiz #25. [ruby-talk:135449]

# Begin swiped code
class Integer

   Ones = %w[ zero one two three four five six seven eight nine ]
   Teen = %w[ ten eleven twelve thirteen fourteen fifteen
              sixteen seventeen eighteen nineteen ]
   Tens = %w[ zero ten twenty thirty forty fifty
              sixty seventy eighty ninety ]
   Mega = %w[ none thousand million billion ]

   def to_en
     places = to_s.split(//).collect {|s| s.to_i}.reverse
     name = []
     ((places.length + 2) / 3).times do |p|
       strings = Integer.trio(places[p * 3, 3])
       name.push(Mega[p]) if strings.length > 0 and p > 0
       name += strings
     end
     name.push(Ones[0]) unless name.length > 0
     name.reverse.join(" ")
   end

   private

   def Integer.trio(places)
     strings = []
     if places[1] == 1
       strings.push(Teen[places[0]])
     elsif places[1] and places[1] > 0
       strings.push(places[0] == 0 ? Tens[places[1]] :
                    "#{Tens[places[1]]}-#{Ones[places[0]]}")
     elsif places[0] > 0
       strings.push(Ones[places[0]])
     end
     if places[2] and places[2] > 0
       strings.push("hundred", Ones[places[2]])
     end
     strings
   end

end
# End swiped code

# Okay, now on with my solution. I assume that almost every
# solution will use some form of the "Robbinsoning" described
# in the pangram page linked from the quiz. This somewhat limits
# the number of different variations we can have.
#
# In my solution, I was trying something a bit tricky with how I
# represented letter frequency to speed things up - though I still
# think that there's some slight twist to the search scheme that
# makes things even faster. Hopefully I'll find out by examining
# others' solutions.
#
# I represented the letter frequencies of letters in a sentence
# as one huge bignum, such that if "freq" was a variable containing
# the number, then "freq & 0xFF" would be the number of "a"s in the
# sentence, "(freq>>8) & 0xFF" would be the number of "b"s, etc.
#
# This means that when I adjust a guess, changing the actual frequency
# is as simple as adding and subtracting from a single variable.
#
# I probably could have split this up some, but the option of writing
# Hash.new to get the equivalent of a memoized lambda made it really
# easy to simply write it as one big routine.
#
# One last thing - note when I take a random step towards the actual
# frequency, I actually call rand twice, and take the larger value.
# I found this to be about twice as fast as calling rand just once.
# (This biases things towards larger steps; i.e. towards the actual
# measured frequency)

# This routine is for debugging - it turns a bignum containing
# letter frequencies into a human-readable string.
def explode(big)
  s = ""
  ('a'..'z').each{|x| big, r = big.divmod(256); s += "%2d " % r}
  s
end

def find_sentence(prefix, suffix, initial = {})
  letterre = Regexp.new('(?i:[a-z])');
  letters = ('a'..'z').to_a
  letterpower = Hash.new {|h,k| h[k] = 1 << ((k[0]-?a)*8)}
  lettershifts = letters.map {|x| ((x[0]-?a)*8)}
  basesentence = prefix + letters.map {|x|
    (x == 'z'? 'and ' : '') + "_ '#{x}'"}.join(', ') + suffix
  basefreq = 0
  basesentence.scan(letterre) {|x| basefreq +=
letterpower[x.downcase]}
  # enfreq holds the letter counts that spelling out that number adds
to
  # the sentence.
  # E.g. enfreq[1] == letterpower['o'] + letterpower['n'] +
letterpower['e']
  enfreq = Hash.new {|h,k|
    if k > 255 then
      h[k] = h[k >> 8]
    else
      h[k] = 0
      k.to_en.scan(letterre) {|x| h[k] += letterpower[x.downcase]}
      h[k] += letterpower['s'] if k != 1
    end
    h[k]
  }
  guessfreq = 0
  letters.each{|x|
    guessfreq += (initial[x]||0) * letterpower[x]
  }
  guessfreq = basefreq if guessfreq == 0
  actualfreq = 0
  sentence = ""
  begin
    cyclecount, adjusts = 0, 0
    until guessfreq == actualfreq do
      if actualfreq > 0 then
        lettershifts.each{ |y|
          g = 0xFF & (guessfreq >> y)
          a = 0xFF & (actualfreq >> y)
          if (g != a)
            d = (g-a).abs
            r1 = rand(d+1)
            r2 = rand(d+1)
            r1=r2 if r1 < r2
            r1=-r1 if a<g
            if (r1 != 0) then
              adjusts += 1
              guessfreq += r1 << y
              actualfreq += enfreq[g+r1] - enfreq[g]
            end
          end
        }
      else
        actualfreq = basefreq
        lettershifts.each {|y|
          g = 0xFF & (guessfreq >> y)
          actualfreq += enfreq[g]
        }
      end
#DEBUG
# puts explode(actualfreq)
# return "___" if cyclecount > 10_000_000
      cyclecount += 1
    end
  ensure
    puts [cyclecount, adjusts].inspect
  end
  sentence = prefix + ('a'..'z').map {|x|
    g = (guessfreq >> ((x[0]-?a)*8))%256
    (x == 'z'? 'and ' : '') + "#{g.to_en} '#{x}'" +
    (g==1 ? '' : 's')}.join(', ') + suffix
  sentence
end

# And here's where I actually call it. Note that I *cannot* get an
# answer if I use the prefix that contains my email address.

puts find_sentence(
# "a ruby quiz solution found this sentence enumerating ",
# "The program from martin@snowplow.org produced a sentence with ",
  "Daniel Martin's sallowsgram program produced a sentence with ",
# "This terribly inefficient pangram contains ",
# "Darren's ruby panagram program found this sentence which contains
exactly ",
  "."
  # optional seed value - this is the same as the sentence given
  # in the quiz description.
  # {'a'=>9,'b'=>2,'c'=>5,'d'=>4,'e'=>35,
  # 'f'=>9,'g'=>3,'h'=>9,'i'=>16,'j'=>1,
  # 'k'=>1,'l'=>2,'m'=>3,'n'=>27,'o'=>14,
  # 'p'=>3,'q'=>1,'r'=>15,'s'=>34,'t'=>22,
  # 'u'=>6,'v'=>6,'w'=>7,'x'=>6,'y'=>7,
  # 'z'=>1}
  )

Here's my late submission.

I borrowed the English Numerals solution from Quiz #25.
(http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/135446)
Also, I stole the ideas from other posts.
The reason I made this solution was not that I wanted to challenge it,
but that I was very impressed by the Robinsonizing algorithm.
My first solution (brute force) took forever and couldn't find an
answer.
Now this solution finds an answer mostly within 10 seconds.

It was very interesting.

Sam

···

--------

class Integer

  def teen
    case self
    when 0: "ten"
    when 1: "eleven"
    when 2: "twelve"
    else in_compound + "teen"
    end
  end

  def ten
    case self
    when 1: "ten"
    when 2: "twenty"
    else in_compound + "ty"
    end
  end

  def in_compound
    case self
    when 3: "thir"
    when 5: "fif"
    when 8: "eigh"
    else to_en
    end
  end

  def to_en(ands=true)
    small_nums = [""] + %w[one two three four five six seven eight nine]
    if self < 10: small_nums[self]
    elsif self < 20: (self % 10).teen
    elsif self < 100:
      result = (self/10).ten
      result += "-" if (self % 10) != 0
      result += (self % 10).to_en
      return result
    elsif self < 1000
      if self%100 != 0 and ands
        (self/100).to_en(ands)+" hundred and "+(self%100).to_en(ands)
      else ((self/100).to_en(ands)+
        " hundred "+(self%100).to_en(ands)).chomp(" ")
      end
    else
      front,back = case (self.to_s.length) % 3
        when 0: [0..2,3..-1].map{|i| self.to_s[i]}.map{|i| i.to_i}
        when 2: [0..1,2..-1].map{|i| self.to_s[i]}.map{|i| i.to_i}
        when 1: [0..0,1..-1].map{|i| self.to_s[i]}.map{|i| i.to_i}
        end
      degree = [""] + %w[thousand million billion trillion quadrillion
      quintillion sextillion septillion octillion nonillion decillion
      undecillion duodecillion tredecillion quattuordecillion
      quindecillion sexdecillion septdecillion novemdecillion
      vigintillion unvigintillion duovigintillion trevigintillion
      quattuorvigintillion quinvigintillion sexvigintillion
      septvigintillion octovigintillion novemvigintillion trigintillion
      untregintillion duotrigintillion googol]
      result = front.to_en(false) + " " + degree[(self.to_s.length-1)/3]
      result += if back > 99: ", "
                elsif back > 0: ands ? " and " : " "
                else ""
                end
      result += back.to_en(ands)
      return result.chomp(" ")
    end
  end

end

class Pangram

  LETTERS = ('a'..'z').to_a
  RE = /[a-z]/

  def initialize sentence
    @original_sentence = sentence
    @count = Hash.new(1)
  end

  def self_documenting_pangram
    while true
      make_sentence_with_current_count
      LETTERS.each do |letter|
        return @sentence if is_correct?
        update_count letter
      end
    end
  end

private

  def update_count letter
    current = @count[letter]
    real = get_count letter
    @count[letter] = rand_between current, real
  end

  def rand_between n1, n2
    case (n1 - n2).abs
    when 0, 1
      n2
    when 2
      [n1, n2].min + 1
    else
      [n1, n2].min + rand((n1 - n2).abs - 2) + 1
    end
  end

  def get_count letter
    @sentence.downcase.scan(/#{letter}/).size
  end

  def is_correct?
    @count ==
    @sentence.downcase.scan(RE).inject(Hash.new(0)) do |m, i|
      m[i] += 1
      m
    end
  end

  def make_sentence_with_current_count
    @sentence = @original_sentence + " " +
    LETTERS.inject("") do |memo, letter|
      memo << "and " if letter == 'z'
      memo << @count[letter].to_en + " " + letter
      if @count[letter] > 1
        memo << "'s"
      end
      memo << (letter == 'z' ? "." : ", ")
    end
  end

end

sentence = <<END
Darren's ruby panagram program found this sentence which contains
exactly
END

pangram = Pangram.new sentence
puts pangram.self_documenting_pangram

--
Posted via http://www.ruby-forum.com/.

Thanks for catching the "pangram" versus "panagram" issue, I had never noticed
it. Thanks to everyone for your great solutions, they are very interesting
and I have learned a lot from reading them.

In the interest of completeness, here is my original script that prompted me
to suggest this quiz. Those that read the Perl solution will recognize it is
very similar. This code is limited because (like the perl script) it will
barf if the count of any one letter is > 99, however, I have not found this
to be a practical problem with any seeds I have tried (I think the greatest
letter count was ~35).

This code produced two solutions using the same seed, something I thought not
possible (at least when using a reasonable short seed):

Only a fool would check that this sentence contains exactly six 'a's, one 'b',
six 'c's, three 'd's, thirty-four 'e's, five 'f's, two 'g's, ten 'h's,
thirteen 'i's, one 'j', two 'k's, five 'l's, one 'm', twenty-two 'n's,
seventeen 'o's, one 'p', one 'q', seven 'r's, thirty-two 's's,
twenty-six 't's, three 'u's, seven 'v's, eight 'w's, six 'x's, seven 'y's,
and one 'z'.

and:

Only a fool would check that this sentence contains exactly six 'a's, one 'b',
six 'c's, three 'd's, thirty-three 'e's, four 'f's, two 'g's, ten 'h's,
twelve 'i's, one 'j', two 'k's, six 'l's, one 'm', twenty 'n's, sixteen 'o's,
one 'p', one 'q', seven 'r's, thirty-two 's's, twenty-five 't's, three 'u's,
six 'v's, eight 'w's, seven 'x's, seven 'y's, and one 'z'

And the code:

···

#################
$snum = {
    1 => 'one', 2 => 'two', 3 => 'three', 4 => 'four', 5 => 'five', 6
=> 'six',
    7 => 'seven', 8 => 'eight', 9 => 'nine', 10 => 'ten', 11 => 'eleven',
    12 => 'twelve', 13 => 'thirteen', 14 => 'fourteen', 15 => 'fifteen',
    16 => 'sixteen', 17 => 'seventeen', 18 => 'eighteen', 19 => 'nineteen',
    20 => 'twenty', 30 => 'thirty', 40 => 'forty', 50 => 'fifty', 60
=> 'sixty',
    70 => 'seventy', 80 => 'eighty', 90 => 'ninety'
    }

def spelledNumber(x)
  if x >= 100
    print "must be 99 or less"
    exit
  elsif x <= 20
    $snum[x]
  else
    tens = (x / 10).to_i * 10
    if x - tens == 0
      $snum[x]
    else
      $snum[tens] + "-" + $snum[x - tens]
    end
  end
end

def checkIfTrue(s)
  realCount = {}
  LETTERS.each do |c|
    realCount[c] = s.count(c)
  end
  if $fixedCount == realCount
    puts "Found it:"
    puts s
    exit
  end
  $fixedCount.each do |key, value|
    x = s.count(key)
    y = value
    $fixedCount[key] = randomizer(x, y)
  end
  $fixedCount
end

def randomizer(x, y)
  if x == y then return x end
  if x > y then x, y = y, x end
  rand(y-x+1)+x
end

LETTERS = ('a'..'z').to_a
seed = %q/darrens ruby panagram program found this sentence which contains
exactly and /
$fixedCount = {}
LETTERS.each { |c| $fixedCount[c] = rand(50) }

while 1
  (1..10000).each do
    s = seed
    LETTERS.each do |c|
      s += spelledNumber($fixedCount[c])
      s += " '#{c}'"
      s += $fixedCount[c] >= 2 ? "s, " : ", "
    end
    $fixedCount = checkIfTrue(s)
  end
  print "\t10K blip...\n"
end
###################

--
darren kirby :: Part of the problem since 1976 :: http://badcomputer.org
"...the number of UNIX installations has grown to 10, with more expected..."
- Dennis Ritchie and Ken Thompson, June 1972

Nevermind, answered my own question with the dictionary: pangram.

···

On 7/7/06, Matthew Moss <matthew.moss.coder@gmail.com> wrote:

So is it "pangram" or "panagram"?

> There is a lot more info on self-documenting panagrams at this address:
>
> http://www.cs.indiana.edu/~tanaka/GEB/pangram.txt

Good catch, I didn't even notice the name discrepancy. Pangram does appear to be the correct term.

James Edward Gray II

···

On Jul 7, 2006, at 10:13 AM, Matthew Moss wrote:

So is it "pangram" or "panagram"?

There is a lot more info on self-documenting panagrams at this address:

        http://www.cs.indiana.edu/~tanaka/GEB/pangram.txt

So is it "pangram" or "panagram"?

    The word is "pangram". Some people misspell it as "panagram",
probably due to an association with the word "anagram", another word
puzzle favorite.

    - Warren Brown

Actually, I lied. Almost correct, but at least one number is off...

···

On Sat, Jul 08, 2006 at 02:34:23AM +0900, brian.mattern@gmail.com wrote:

Is it a spoiler if we post a resulting pangram (no code)?

I got one in:
real 1m17.089s
user 1m8.444s
sys 0m2.492s

on an athlon 64 300+

Brian

Not at all. Please do.

James Edward Gray II

···

On Jul 7, 2006, at 12:34 PM, brian.mattern@gmail.com wrote:

Is it a spoiler if we post a resulting pangram (no code)?

"Tim Hollingsworth" <timhollingsworth@gmail.com> writes:

Hello

First post here, by the way. Greetings to all:) New to ruby, love it, slaps
on backs all round.

While I haven't solved the pangram problem, I did find what appears to be a
bug in Numeric.divmod on large numbers, as can be seen when using
Bignum.modulo:

irb(main):017:0> 1e22 % 1e21
=> 0.0
irb(main):018:0> 1e23 % 1e22
=> 9.99999999999999e+021

whoops!

Not quite. 1e23 is a float, that's why you run into precision
problems. This has been discussed here not long ago.
If you use Bignums it works as expected:

···

,----

irb(main):001:0> 1e23.class
=> Float
irb(main):002:0> (10**23).class
=> Bignum
irb(main):003:0> 10**23 % 10**22
=> 0

`----

Tom

Simon Kröger <SimonKroeger@gmx.de> writes:

(this is my second version, the other one is longer and uses a slightly
other algorithm (and is sometimes even faster) but i like this one)

Huh. I must say that when I saw your program, it wasn't at all what I
expected - namely, you are changing all 26 letters at once, and then
recalculating. I had assumed you were doing one letter at a time,
which I found to be much, much faster than doing all 26 at once.

I'm going to have to look into NArray, clearly. In fact, I think I'll
try converting my solution to use NArray.

How about:

rephorm@boru ~/code/ruby/quiz/pangram $ time ruby pangram.rb > p
real 0m28.613s
user 0m27.474s
sys 0m1.000s

rephorm@boru ~/code/ruby/quiz/pangram $ cat p
a ruby quiz solution found this sentence enumerating four a's, two b's,
two c's, three d's, thirty-four e's, nine f's, three g's, eight h's,
sixteen i's, one j, one k, three l's, two m's, twenty-five n's, fifteen
o's, one p, two q's, eleven r's, twenty-nine s's, twenty-five t's, nine
u's, four v's, nine w's, three x's, six y's, and two z's

rephorm@boru ~/code/ruby/quiz/pangram $ ruby verify.rb p
Sentence passes!

It looks like the exact wording of the prelude has a large effect on how
quickly a solution can be found.

Brian

···

On Sat, Jul 08, 2006 at 03:18:26AM +0900, James Edward Gray II wrote:

On Jul 7, 2006, at 12:34 PM, brian.mattern@gmail.com wrote:

>Is it a spoiler if we post a resulting pangram (no code)?

Not at all. Please do.

I was wondering where you can get an athlon64 300+ :slight_smile:

···

On Jul 7, 2006, at 12:39 PM, brian.mattern@gmail.com wrote:

On Sat, Jul 08, 2006 at 02:34:23AM +0900, brian.mattern@gmail.com > wrote:

Is it a spoiler if we post a resulting pangram (no code)?

I got one in:
real 1m17.089s
user 1m8.444s
sys 0m2.492s

on an athlon 64 300+

Brian

Actually, I lied. Almost correct, but at least one number is off...

James Edward Gray II wrote:

Is it a spoiler if we post a resulting pangram (no code)?

Not at all. Please do.

James Edward Gray II

This is the pangram from simon, there are four a's, one b, one c, one d,
thirty-two e's, seven f's, four g's, nine h's, eleven i's, one j, one k,
two l's, four m's, twenty n's, eighteen o's, two p's, one q, thirteen
r's, twenty-seven s's, eighteen t's, six u's, four v's, six w's, three
x's, four y's, one z.
Time: 8.39s

cheers

Simon

···

On Jul 7, 2006, at 12:34 PM, brian.mattern@gmail.com wrote: