[QUIZ] Panagrams (#86)

M

Mat Schaffer

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
 
M

Mat Schaffer

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 <[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



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
 
P

Paolo Capriotti

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 Capriotti
 
P

Paolo Capriotti

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 Capriotti
 
S

Sam Kong

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}.map{|i| i.to_i}
when 2: [0..1,2..-1].map{|i| self.to_s}.map{|i| i.to_i}
when 1: [0..0,1..-1].map{|i| self.to_s}.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 += 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
 
D

darren kirby

--nextPart1896260.BgATiusI6Z
Content-Type: text/plain;
charset="iso-8859-6"
Content-Transfer-Encoding: quoted-printable
Content-Disposition: inline

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

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

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

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

and:

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

And the code:

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

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

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

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

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

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

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

--nextPart1896260.BgATiusI6Z
Content-Type: application/pgp-signature

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.4 (GNU/Linux)

iD8DBQBEtXMMwPD5Cr/3CJgRAmefAKDikB6co/gqVVEAup14QJcsQiaByQCgyviB
Czkk2hO3MzIyq3LHjyEmMf8=
=lWcs
-----END PGP SIGNATURE-----

--nextPart1896260.BgATiusI6Z--
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
474,208
Messages
2,571,082
Members
47,683
Latest member
AustinFairchild

Latest Threads

Top