--=-F/fjOwTFLoeqy+p+1VGt
Content-Type: text/plain
Content-Transfer-Encoding: 7bit
Late response, with a question about the solvabilty of these
kind of problems in general.
I used a similar approach and get similar answers to Glenns, only
much slower (although mine finishes in finite time :) When running
Glens program, I get many partial solutions in < 1 Sec (including most
that my program finds) - then his progam consumes 99% of my CPU
time indefinately. On my PC, mine runs in ~10 S for crypto1
(partial answer) ~16 S for crypto2, full correct answer,
and ~80 S for crypto3, producing garbage. (can I buy a
vowel? :)
Actually I added the ablity to provide a partial map
to help prime the solver - still cant make sense of crypto3.
The main question I have is how does one handle non-dictionary
words that match a dictionary word signature - and then force
other wrong choices. The specific case is "alva" which can
map to "aqua". Once I have "aqua" as plain text, it prevents
the solver from using the letter 'u' in genius - making that
word match denims.
In fact I can envsion quotes with non-dictionary words that
could map, wrongly, to a set of all dictionary words - i.e.
the solution with 1 or more "unsolved" words would be more
correct than the solution with no unsolved words...
There's also the situation of "bent, cent, vent, went" - where
the choice of any of them doesn't affect any other words in the
cypher text - providing no help selecting between them.
In both these cases, I don't think backtracking can help - other
than to provide a number of possible solutions that a person
could read and pick from?
Addionally, while I agree that brute force w/26! possible maps
is impractial, after a certain point, would it make sense to
use the brute force approach for the "remaining" letters -
somwhere in the 8-10 letters remaining?
Thanks to all involved in providing this - been reading for a
while, but this is my first try at solving one ... took *way*
more time than I intended, but was lots of fun. Looking forward
to more in the future.
Vance
--=-F/fjOwTFLoeqy+p+1VGt
Content-Disposition: attachment; filename=rq13
Content-Type: application/x-ruby; name=rq13
Content-Transfer-Encoding: 7bit
#! /bin/env ruby
def sig(w)
# rtn = ''
rtn = 0
ltrs = w.split(//)
ndx = 0
while l = ltrs.shift
if fnd = ltrs.index(l)
# rtn += ndx.to_s + ',' + (fnd+ndx+1).to_s + ' '
rtn = rtn*100 + ndx*10 + fnd
end
ndx += 1
end
return rtn
end
def get_words (fname, ary )
file = File.open(fname)
while word = file.gets
ary.push(word.chomp)
end
file.close
end
def choices (dict, word, dpairs, pairs, re = nil)
re = '.'* word.length unless re
c = eval("dict.grep(\/^#{re}\$\/)")
c = c.delete_if{ |w| dpairs[w] != pairs[word] }
return c
end
def decode (cyphertext, cypher, plain)
code = {}
('a'..'z').each { |l| code[l] = '.'}
kl = cypher.split(//)
wl = plain.split(//)
kl.each_index { |i|
code[kl[i]] = wl[i] if code[kl[i]] == '.'
}
clear = []
cyphertext.each {|w|
next unless w
word = ""
w.each_byte { |c|
word << code[c.chr] if c
}
clear << word
}
return clear
end
def count_options (puzzle, dictionary, pairs, dpairs, sizes, clear)
min_choices = 10000
savi = -1
savl = -1
savt = []
puzzle.each_index { |i|
t = choices(dictionary, puzzle[i], dpairs, pairs, clear[i])
sizes[i] = t.length
next if (clear[i] && clear[i] !~ /\./)
if sizes[i] > 0 && sizes[i] < min_choices
min_choices = sizes[i]
savt = t
savi = i
end
}
if savi == -1
return nil, nil
else
return puzzle[savi], savt
end
end
### MAIN
dictionary = []
puzzle = []
to_try = {}
get_words(ARGV[0], dictionary)
#dictionary.push('a')
#dictionary.push('i')
get_words(ARGV[1], puzzle)
#puzzle.each {|a| a.uniq! if a }
# find words w/dup letters
pairs = {}
puzzle.each {|w| pairs[w] = sig(w) }
dpairs = {}
dictionary.each {|w| dpairs[w] = sig(w) }
sizes = []
clear = []
eword = []
tries = []
newtries = []
etxt = ""
ctxt = ""
trial = ""
c2p = {}
cta = []
# first time
if ARGV[3]
etxt=ARGV[2].to_s
ctxt=ARGV[3].to_s
clear = decode(puzzle, etxt, ctxt)
end
eword[0], tries = count_options(puzzle, dictionary, pairs, dpairs, sizes, clear)
puts "#{puzzle.join(' ')}\n"
puts "#{sizes.join(' ')}"
begin
tries_s = []
trial_s = []
clear_s = []
sizes_s = []
zeros = []
num_opts = []
etxt += eword[0]
eword_s = eword[0]
i = 0
tries.each{ |trial|
next if ( sig(etxt) != sig(ctxt+trial)) # incompatible signatures
clear = decode(puzzle, etxt, ctxt+trial)
eword[i], tries_s[i] = count_options(puzzle, dictionary, pairs, dpairs, sizes, clear)
puts "#{clear.join(' ')}"
# puts "#{sizes.join(' ')}"
clear_s[i] = clear
sizes_s[i] = sizes
trial_s[i] = trial
zeros[i] = sizes.grep(0).length
i += 1
}
break unless i > 0
# find best "trial"
best = -1
num_choices = 10000
sizes_s.each_index { |i|
sizes_s[i].each_index { |j|
if (sizes_s[i][j] > 0 &&
sizes_s[i][j] <= num_choices &&
clear_s[i][j] =~ /\./)
best = i
num_choices = sizes_s[i][j]
end
}
}
if best == -1 # no unresolved options w/1 choice ...
best = zeros.rindex(zeros.min) || -1
end
if best != -1
ctxt += trial_s[best]
tries = tries_s[best]
clear = clear_s[best]
eword[0] = eword[best]
puts "DBG - selected #{eword_s} -> #{trial_s[best]}"
end
end while eword[0]
exit
--=-F/fjOwTFLoeqy+p+1VGt--