-- InLvOB6Mci9vowW5PV7
Content-Type: text/plain
Content-Transfer-Encoding: 7bit
Hmm, this is a tricky one :) This solution is pretty ugly (I've not
seriously tried to clean it up, either) and slow.
There are actually two solutions in here, one that's fast and 'good
enough' for the smaller digit counts, and one that's *very* slow but
gives shorter results with less retesting of codes.
Neither of these are all that much better than simply running through
and checking each code against AlarmKeypad#tested? but then that's to be
expected I guess, perhaps this is why real alarms don't tend to
duplicate that functionality :P
I ran benchmarked tests to get an idea of how it did. The tests are
doing simple sequential tests (seq), sequential using tested? (seq/chk),
the simple/quick(er) way (simple) and the slow, better way (best). Check
out the 'best' time for 4 digit codes - there has to be room for
improvement there :/
### 2 digits, [1,2,3] stops ###
user system total real
seq. 0.010000 0.000000 0.010000 ( 0.005530)
Search space exhausted.
Tested 100 of 100 codes in 300 keystrokes.
39 codes were tested more than once.
seq/chk 0.010000 0.000000 0.010000 ( 0.008893)
Search space exhausted.
Tested 100 of 100 codes in 234 keystrokes.
11 codes were tested more than once.
simple 0.000000 0.000000 0.000000 ( 0.006074)
Search space exhausted.
Tested 100 of 100 codes in 243 keystrokes.
15 codes were tested more than once.
best 0.050000 0.000000 0.050000 ( 0.054995)
Search space exhausted.
Tested 100 of 100 codes in 223 keystrokes.
2 codes were tested more than once.
### 3 digits, [1,2,3] stops ###
user system total real
seq. 0.070000 0.000000 0.070000 ( 0.064838)
Search space exhausted.
Tested 1000 of 1000 codes in 4000 keystrokes.
513 codes were tested more than once.
seq/chk 0.060000 0.000000 0.060000 ( 0.062613)
Search space exhausted.
Tested 1000 of 1000 codes in 2944 keystrokes.
224 codes were tested more than once.
simple 0.090000 0.000000 0.090000 ( 0.086938)
Search space exhausted.
Tested 1000 of 1000 codes in 2960 keystrokes.
223 codes were tested more than once.
best 6.430000 0.050000 6.480000 ( 6.741495)
Search space exhausted.
Tested 1000 of 1000 codes in 2623 keystrokes.
50 codes were tested more than once.
### 4 digits, [1,2,3] stops ###
user system total real
seq. 0.820000 0.020000 0.840000 ( 0.871923)
Search space exhausted.
Tested 10000 of 10000 codes in 50000 keystrokes.
6108 codes were tested more than once.
seq/chk 0.690000 0.010000 0.700000 ( 0.704795)
Search space exhausted.
Tested 10000 of 10000 codes in 33395 keystrokes.
2569 codes were tested more than once.
simple 1.830000 0.030000 1.860000 ( 1.869937)
Search space exhausted.
Tested 10000 of 10000 codes in 33555 keystrokes.
2620 codes were tested more than once.
best 781.350000 5.520000 786.870000 (796.666829)
Search space exhausted.
Tested 10000 of 10000 codes in 28614 keystrokes.
435 codes were tested more than once.
### 5 digits, [1,2,3] stops ###
user system total real
seq. 11.010000 0.140000 11.150000 ( 11.248501)
Search space exhausted.
Tested 100000 of 100000 codes in 600000 keystrokes.
69494 codes were tested more than once.
seq/chk 7.450000 0.060000 7.510000 ( 7.563379)
Search space exhausted.
Tested 100000 of 100000 codes in 371028 keystrokes.
30902 codes were tested more than once.
simple 136.960000 6.250000 143.210000 (145.916674)
Search space exhausted.
Tested 100000 of 100000 codes in 371394 keystrokes.
31105 codes were tested more than once.
best (not tested)
-- InLvOB6Mci9vowW5PV7
Content-Disposition: attachment; filename=bne.rb
Content-Type: text/plain; name=bne.rb; charset=utf-8
Content-Transfer-Encoding: 7bit
require 'quiz'
require 'benchmark'
# Make our codes. Using random stop digits gives a more
# compressible string.
def mkpool(digits, stops)
(("0" * digits)..("9" * digits)).map do |e|
"#{e}#{stops[rand(stops.length)]}"
end
end
# A really simple, yet surprisingly effective way to do it.
def simple_code(digits, stops 1,2,3])
mkpool(digits, stops).inject("") do |s,e|
s /#{e[0..-2]}[#{stops}]/ ? s : s << e
end
end
# Just gets suffixes up to a certain length. Used for
# prefixes to (reverse in then reverse each out).
def suffixes(str, maxlen il)
start axlen ? str.length-maxlen : 1
(start..(str.length-1)).map do |i|
suf tr[i..-1]
suf
end
end
# A more involved way, a variation on the greedy heuristic (I think)
# that takes _forever_ but gives (slightly) better results.
def best_code(digits, stops 1,2,3])
out "
pool kpool(digits, stops)
# build a hash with each of the valid prefixes for a given
# code. These are matched with valid suffixes of 'out' on
# each iteration to find the most mergeable code (i.e.
# the one that shares most of it's prefix with the end of
# the output string).
#
# Note it's digits, not digits - 1, since we have to
# account for the stop digit.
prefhash ool.inject({}) do |hsh, code|
hsh[code] uffixes(code.reverse, digits).map { |s| s.reverse! }
hsh
end
# Keep going until it's empty - if ever we can't find a match
# we'll just take one at random.
until pool.empty?
if out.empty?
# first iteration, just grab a start and carry on
bestcode ool.first
pool.delete(bestcode)
prefhash.delete(bestcode)
out << bestcode
next
end
# figure out the suffixes we have (i.e. prefixes we can accept) for
# this iteration.
need uffixes(out,digits)
# Build up an array of arrays, containing at a given index all
# the codes that share that many letters of pre/suffix with
# 'need'. Eh? Well, it's like this:
#
# "1234" [ nil, ["4321", "4048"], ["3412"], ["2348"]]
#
# Then, (one of) the highest scoring code(s) is taken from
# the top of the last nested array, best[best.length-1].
best ool.inject([]) do |best, code|
prefs refhash[code].dup
# arrays are always same length
if r eed.detect { |s| prefs.shift s }
(best[r.length] || ]) << code
end
best
end
# Find (one of) our best scoring code(s)
bestscore est.length-1
bestcode best[bestscore] || []).first
# No best code? Nothing with matching pre/suff, so let's just grab
# a code at random instead to keep things moving.
unless bestcode
bestscore
bestcode ool[rand(pool.length)]
end
# Chop off matched prefix from this code
merged estcode[bestscore..-1]
# Remove from our pool and prefix hash to ensure it isn't reused
pool.delete(bestcode)
prefhash.delete(bestcode)
out << merged
end
out
end
[2,3,4,5].each do |n|
puts "\n "
puts " ### #{n} digits, [1,2,3] stops ### "
Benchmark.bm do |x|
a larmKeypad.new(n)
x.report('seq. ') do
mkpool(n,[1,2,3]).join.split(//).each { |c| a.press c.to_i }
end
a.summarize
puts
a larmKeypad.new(n)
x.report('seq/chk') do
mkpool(n,[1,2,3]).each do |c|
next if a.tested?(c[0..-2].to_i)
c.split(//).each { |d| a.press d.to_i }
end
end
a.summarize
puts
a larmKeypad.new(n)
x.report('simple ') do
simple_code(n).split(//).each { |c| a.press c.to_i }
end
a.summarize
puts
if n < 5
a larmKeypad.new(n)
x.report('best ') do
best_code(n).split(//).each { |c| a.press c.to_i }
end
a.summarize
else
puts 'best (not tested)'
end
end
end
-- InLvOB6Mci9vowW5PV7--