Panagrams (#86)

On Sat, Jul 08, 2006 at 08:06:27AM +0900, Christoffer Lern? wrote:

h’s, sixteen i’s, one j, one k, three l’s, two m’s, twentyfive n’s,
fifteen o’s, one p, two q’s, eleven r’s, twentynine s’s, twentyfive
t’s, nine u’s, four v’s, nine w’s, three x’s, six y’s and two z’s

…but it took my program 8018361 iterations.

Is it the optimization or the algorithm I got wrong?

/Christoffer

I ran mine again and after 10 minutes it hadn’t found anything. I did a
basic “randomized Robisonizing” algorithm, so, I think the speed just
depends on luck.

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

randomized Robisonizing:
let’s say in candidate(N) which includes the phrase
"… eight a's ..." actually contains 13 a’s. then
in candidate(N+1) you pick a random number R between 8
and 13 (inclusive) and put the phrase “… [R] `a’s
…”. you do this for each letter.

Brian

Christoffer Lernö wrote:

sixteen i’s, one j, one k, three l’s, two m’s, twentyfive n’s, fifteen
o’s, one p, two q’s, eleven r’s, twentynine s’s, twentyfive t’s, nine
u’s, four v’s, nine w’s, three x’s, six y’s and two z’s

…but it took my program 8018361 iterations.

Is it the optimization or the algorithm I got wrong?

/Christoffer

Well, what’s an iteration in your code?

I found a solution (the same) to your sentence above after
255062 full circles over all 26 letters. (changing only some
of them each time) It took 36s on my laptop. (Pentium M 2.1 GHz)

I ran it again and it took 134s and did 10105853 changes to
the original guess (counting each changed count separately).

Obviously (at least with my algorithm) it depends on luck.
And for the total Time it takes it depends on your PC and
optimization.

cheers

Simon

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

Hi!

Plese note that this message uses ISO 8859-7 and contains greek
letters. For MUAs not capable of displaying this encodig I added
transliteration as well as TeX notation.

At Sat, 8 Jul 2006 00:21:37 +0900, Brown, Warren wrote:

So is it “pangram” or “panagram”?

The word is “pangram”.

The origin of “pangram” is greek “pan gramma” (“ðáí ãñáììá”, TeX
notation “\pi\alpha\nu \gamma\rho\alpha\mu\mu\alpha”) meaning “every
letter”.

Josef ‘Jupp’ Schugt

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

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 Q. 86, Pangrams: Solution by Mat S. [email protected]

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

“Tim Hollingsworth” [email protected] writes:

=> 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> (1023).class
| => Bignum
| irb(main):003:0> 10
23 % 10**22
| => 0
`----

Tom

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 09/07/2006, at 8:17 PM, Tom R. wrote:

bug in Numeric.divmod on large numbers, as can be seen when using
problems. This has been discussed here not long ago.

Tom

aha right you are! I was using to_i in my code, and there lies my
problem.

Simon Kröger [email protected] 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.

#!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 [email protected] produced a sentence with ",

"Daniel M.'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}

)

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.

Ok, I will post my other version as soon as I’m home again.
Btw: nice idea to use Bignums :slight_smile:

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

yes, some of the stuff is a little bit mind bending (using ‘true’ or
‘false’ for slicing dimensions for example) but you get true C speed
while implementing in ruby - that’s fun.

cheers

Simon

“Kroeger, Simon (ext)” [email protected] writes:

yes, some of the stuff is a little bit mind bending (using ‘true’ or
‘false’ for slicing dimensions for example) but you get true C speed
while implementing in ruby - that’s fun.

Unfortunately, I can’t actually use NArray on my fast machine as I
don’t have VC 6.0 on it. (Windows, using the one-click installer)

“true C speed” on my 500Mhz K6 that’s already heavily loaded with lots
of spamassassin work is significantly less impressive.

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.

Ok, I will post my other version as soon as I’m home again.

Here is the second version (which was my first version):

require ‘narray’
class NArray; include Enumerable; end
srand(1)

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

class Fixnum
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
count = NArray.byte(26)
each_byte do |b|
count[b - ?a] += 1 if b >= ?a and b <= ?z
count[b - ?A] += 1 if b >= ?A and b <= ?Z
end
count
end
end

text = "This is a pangram from simon, it contains "
number = (0…99).map{|i| i.to_english.to_na + (i > 1 ? ‘s’ : ‘’).to_na}
guess = NArray.byte(26).fill!(1)
real = guess + text.to_na + ‘and’.to_na + (number[1] * 26)

i, r, g, changed = nil, nil, nil, true
while changed do
changed = false
26.times do |i|
g = guess[i]
r = real[i]
if g != r
real.sbt! number[g]
guess[i] = g = (g < r ? g : r) + rand((g - r).abs + 1)
real.add! number[g]
changed = true
end
end
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)

It’s quite similar except the loop.

cheers

Simon

well, I’m not sure if it is possible for every start of a sentence,
perhaps try another. (any thoughts from the mathematicians out there?)

Not all sentences can be completed to a pangram. Try a sentence
containing 999998 a’s and a less than, say, 500 of all other letters.
Yes, I agree that such an example cannot be realized by en english
sentence, but a greater number of other letters could be allowed by
appropriately lowering the number of a’s (it could be difficult to
prove, though).

In italian one can construct a counterexample with ‘only’ 1998 l’s
(this is based on the fact that 1999 contains two l’s, while 2000
containes 2, and no number less than 1000 contains l’s).

Paolo C.

Simon Kröger [email protected] writes:

Here is the second version (which was my first version):


It’s quite similar except the loop.

Indeed, this is more what I expected - and this is a distinctly
different algorithm than the first solution you posted. In fact, your
main loop is pretty much identical to mine, except that you were using
NArray whereas I was using a bignum to hold the frequency. This meant
that it was much more cumbersome for me to extract each component and
update than it was for you.

I think that this algorithm itself is significantly faster than the
update-all-26-at-once version, and the only reason you see an
advantage to the update-all-26-at-once approach is because you can do
almost all of it by calling NArray vector operations.

On 7/11/06, Paolo C. [email protected] wrote:

In italian one can construct a counterexample with ‘only’ 1998 l’s
(this is based on the fact that 1999 contains two l’s, while 2000
containes 2, and no number less than 1000 contains l’s).

err… 2000 contains one l in italian.

Paolo C.

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

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©
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
###################

“Nasir K.” [email protected] writes:

You may notice that I have tried both changing current value one at a time
and also accumulating changes in @nchar_cnt_arr and changing in one go. The
convergence has been elusive in either case.
One way for me is to start looking at other solutions (which I will
certainly do), but it would be a great help if someone points me towards a
flaw in this.

I’ll look more at your code later, but have you tried different
initial sentence parts? Specifically, have your tried the initial
bits that others were posting over the weekend?

I found that my code was unable to converge (after four hours) if I
began with:

The program from [email protected] produced a sentence with

But when I switched to:

Daniel M.'s sallowsgram program produced a sentence with

I then got convergence in less than ten seconds. Some initial
sequences are just impossible to complete.