chrislo-sourceclassifier 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Manifest +325 -0
- data/README.textile +51 -0
- data/Rakefile +64 -0
- data/examples/example.rb +22 -0
- data/lib/sourceclassifier.rb +22 -0
- data/lib/trainer.rb +51 -0
- data/sourceclassifier.gemspec +35 -0
- data/sources/gcc/ackermann.gcc-2.gcc +93 -0
- data/sources/gcc/ackermann.gcc-3.gcc +20 -0
- data/sources/gcc/ary.gcc +40 -0
- data/sources/gcc/binarytrees.gcc +136 -0
- data/sources/gcc/binarytrees.gcc-2.gcc +140 -0
- data/sources/gcc/binarytrees.gcc-3.gcc +134 -0
- data/sources/gcc/chameneos.gcc +117 -0
- data/sources/gcc/chameneos.gcc-2.gcc +134 -0
- data/sources/gcc/chameneos.gcc-3.gcc +120 -0
- data/sources/gcc/chameneosredux.gcc +306 -0
- data/sources/gcc/echo.gcc +144 -0
- data/sources/gcc/except.gcc +52 -0
- data/sources/gcc/fannkuch.gcc +105 -0
- data/sources/gcc/fannkuch.gcc-2.gcc +94 -0
- data/sources/gcc/fasta.gcc +158 -0
- data/sources/gcc/fasta.gcc-2.gcc +132 -0
- data/sources/gcc/fasta.gcc-3.gcc +145 -0
- data/sources/gcc/fasta.gcc-4.gcc +141 -0
- data/sources/gcc/fibo.gcc +20 -0
- data/sources/gcc/harmonic.gcc +22 -0
- data/sources/gcc/hash.gcc +31 -0
- data/sources/gcc/hash2.gcc +38 -0
- data/sources/gcc/heapsort.gcc +72 -0
- data/sources/gcc/hello.gcc-2.gcc +12 -0
- data/sources/gcc/hello.gcc-3.gcc +20 -0
- data/sources/gcc/knucleotide.gcc +173 -0
- data/sources/gcc/knucleotide.gcc-2.gcc +168 -0
- data/sources/gcc/lists.gcc +226 -0
- data/sources/gcc/magicsquares.gcc +378 -0
- data/sources/gcc/mandelbrot.gcc +84 -0
- data/sources/gcc/mandelbrot.gcc-2.gcc +63 -0
- data/sources/gcc/mandelbrot.gcc-3.gcc +91 -0
- data/sources/gcc/mandelbrot.gcc-4.gcc +78 -0
- data/sources/gcc/matrix.gcc +66 -0
- data/sources/gcc/message.gcc-2.gcc +72 -0
- data/sources/gcc/message.gcc-3.gcc +64 -0
- data/sources/gcc/methcall.gcc +89 -0
- data/sources/gcc/moments.gcc +120 -0
- data/sources/gcc/nbody.gcc +142 -0
- data/sources/gcc/nbody.gcc-2.gcc +143 -0
- data/sources/gcc/nbody.gcc-3.gcc +142 -0
- data/sources/gcc/nestedloop.gcc +25 -0
- data/sources/gcc/nsieve.gcc +35 -0
- data/sources/gcc/nsievebits.gcc-2.gcc +37 -0
- data/sources/gcc/nsievebits.gcc-3.gcc +53 -0
- data/sources/gcc/objinst.gcc +95 -0
- data/sources/gcc/partialsums.gcc +86 -0
- data/sources/gcc/partialsums.gcc-3.gcc +81 -0
- data/sources/gcc/partialsums.gcc-4.gcc +68 -0
- data/sources/gcc/pidigits.gcc +105 -0
- data/sources/gcc/primes.gcc +75 -0
- data/sources/gcc/prodcons.gcc +86 -0
- data/sources/gcc/random.gcc +29 -0
- data/sources/gcc/raytracer.gcc +125 -0
- data/sources/gcc/raytracer.gcc-2.gcc +181 -0
- data/sources/gcc/recursive.gcc +68 -0
- data/sources/gcc/recursive.gcc-2.gcc +55 -0
- data/sources/gcc/regexdna.gcc-2.gcc +126 -0
- data/sources/gcc/regexmatch.gcc +136 -0
- data/sources/gcc/revcomp.gcc +85 -0
- data/sources/gcc/revcomp.gcc-2.gcc +88 -0
- data/sources/gcc/revcomp.gcc-4.gcc +71 -0
- data/sources/gcc/reversefile.gcc +103 -0
- data/sources/gcc/reversefile.gcc-2.gcc +56 -0
- data/sources/gcc/sieve.gcc +34 -0
- data/sources/gcc/spectralnorm.gcc +54 -0
- data/sources/gcc/spellcheck.gcc +72 -0
- data/sources/gcc/spellcheck.gcc-2.gcc +61 -0
- data/sources/gcc/strcat.gcc +38 -0
- data/sources/gcc/sumcol.gcc-2.gcc +98 -0
- data/sources/gcc/sumcol.gcc-3.gcc +22 -0
- data/sources/gcc/sumcol.gcc-4.gcc +18 -0
- data/sources/gcc/sumcol.gcc-5.gcc +32 -0
- data/sources/gcc/takfp.gcc +23 -0
- data/sources/gcc/tcp-stream.gcc +122 -0
- data/sources/gcc/tcpecho.gcc +122 -0
- data/sources/gcc/tcpecho.gcc-2.gcc +136 -0
- data/sources/gcc/tcprequest.gcc +122 -0
- data/sources/gcc/threadring.gcc +73 -0
- data/sources/gcc/wc.gcc +52 -0
- data/sources/gcc/wc.gcc-2.gcc +40 -0
- data/sources/gcc/wc.gcc-3.gcc +46 -0
- data/sources/gcc/wordfreq.gcc-2.gcc +85 -0
- data/sources/perl/ackermann.perl +28 -0
- data/sources/perl/ackermann.perl-2.perl +25 -0
- data/sources/perl/ackermann.perl-3.perl +20 -0
- data/sources/perl/ackermann.perl-4.perl +18 -0
- data/sources/perl/ary.perl +25 -0
- data/sources/perl/ary.perl-2.perl +23 -0
- data/sources/perl/binarytrees.perl +66 -0
- data/sources/perl/binarytrees.perl-2.perl +71 -0
- data/sources/perl/chameneos.perl +67 -0
- data/sources/perl/echo.perl +81 -0
- data/sources/perl/except.perl +73 -0
- data/sources/perl/fannkuch.perl +44 -0
- data/sources/perl/fannkuch.perl-2.perl +38 -0
- data/sources/perl/fasta.perl +112 -0
- data/sources/perl/fasta.perl-2.perl +135 -0
- data/sources/perl/fasta.perl-4.perl +122 -0
- data/sources/perl/fibo.perl +15 -0
- data/sources/perl/fibo.perl-2.perl +0 -0
- data/sources/perl/fibo.perl-3.perl +0 -0
- data/sources/perl/harmonic.perl +7 -0
- data/sources/perl/hash.perl +23 -0
- data/sources/perl/hash.perl-2.perl +17 -0
- data/sources/perl/hash.perl-3.perl +24 -0
- data/sources/perl/hash2.perl +16 -0
- data/sources/perl/heapsort.perl +65 -0
- data/sources/perl/heapsort.perl-2.perl +0 -0
- data/sources/perl/hello.perl +5 -0
- data/sources/perl/knucleotide.perl-2.perl +30 -0
- data/sources/perl/lists.perl +48 -0
- data/sources/perl/mandelbrot.perl-2.perl +32 -0
- data/sources/perl/matrix.perl +59 -0
- data/sources/perl/matrix.perl-2.perl +0 -0
- data/sources/perl/matrix.perl-3.perl +0 -0
- data/sources/perl/message.perl +27 -0
- data/sources/perl/methcall.perl +66 -0
- data/sources/perl/moments.perl +44 -0
- data/sources/perl/nbody.perl +108 -0
- data/sources/perl/nestedloop.perl +28 -0
- data/sources/perl/nsieve.perl-2.perl +41 -0
- data/sources/perl/nsieve.perl-4.perl +43 -0
- data/sources/perl/nsievebits.perl +37 -0
- data/sources/perl/objinst.perl +73 -0
- data/sources/perl/partialsums.perl-3.perl +31 -0
- data/sources/perl/pidigits.perl +52 -0
- data/sources/perl/pidigits.perl-2.perl +47 -0
- data/sources/perl/process.perl +50 -0
- data/sources/perl/prodcons.perl +47 -0
- data/sources/perl/random.perl-4.perl +17 -0
- data/sources/perl/recursive.perl-2.perl +57 -0
- data/sources/perl/regexdna.perl +48 -0
- data/sources/perl/regexdna.perl-2.perl +43 -0
- data/sources/perl/regexdna.perl-3.perl +50 -0
- data/sources/perl/regexdna.perl-4.perl +49 -0
- data/sources/perl/regexdna.perl-5.perl +42 -0
- data/sources/perl/regexdna.perl-6.perl +43 -0
- data/sources/perl/regexmatch.perl +35 -0
- data/sources/perl/revcomp.perl-2.perl +34 -0
- data/sources/perl/reversefile.perl +8 -0
- data/sources/perl/reversefile.perl-2.perl +0 -0
- data/sources/perl/reversefile.perl-3.perl +0 -0
- data/sources/perl/sieve.perl +23 -0
- data/sources/perl/spectralnorm.perl-2.perl +54 -0
- data/sources/perl/spellcheck.perl +24 -0
- data/sources/perl/strcat.perl +13 -0
- data/sources/perl/strcat.perl-2.perl +0 -0
- data/sources/perl/sumcol.perl +8 -0
- data/sources/perl/takfp.perl +23 -0
- data/sources/perl/takfp.perl-3.perl +20 -0
- data/sources/perl/tcpecho.perl +61 -0
- data/sources/perl/tcprequest.perl +61 -0
- data/sources/perl/tcpstream.perl +61 -0
- data/sources/perl/threadring.perl +55 -0
- data/sources/perl/threadring.perl-2.perl +43 -0
- data/sources/perl/wc.perl +20 -0
- data/sources/perl/wc.perl-2.perl +14 -0
- data/sources/perl/wordfreq.perl +22 -0
- data/sources/perl/wordfreq.perl-3.perl +0 -0
- data/sources/perl/wordfreq.perl3.perl +0 -0
- data/sources/python/ackermann.python +21 -0
- data/sources/python/ary.python +19 -0
- data/sources/python/binarytrees.python +39 -0
- data/sources/python/binarytrees.python-3.python +44 -0
- data/sources/python/chameneos.python-6.python +73 -0
- data/sources/python/chameneosredux.python +126 -0
- data/sources/python/chameneosredux.python-2.python +122 -0
- data/sources/python/dispatch.python +176 -0
- data/sources/python/dispatch.python-2.python +136 -0
- data/sources/python/echo.python +64 -0
- data/sources/python/except.python +62 -0
- data/sources/python/fannkuch.python +50 -0
- data/sources/python/fannkuch.python-2.python +54 -0
- data/sources/python/fasta.python-2.python +79 -0
- data/sources/python/fibo.python +17 -0
- data/sources/python/fibo.python-2.python +0 -0
- data/sources/python/fibo.python-3.python +0 -0
- data/sources/python/harmonic.python-2.python +9 -0
- data/sources/python/hash.python +21 -0
- data/sources/python/hash.python-2.python +0 -0
- data/sources/python/hash2.python +30 -0
- data/sources/python/heapsort.python-3.python +66 -0
- data/sources/python/hello.python +5 -0
- data/sources/python/implicitode.python +231 -0
- data/sources/python/knucleotide.python +55 -0
- data/sources/python/lists.python +44 -0
- data/sources/python/magicsquares.python +145 -0
- data/sources/python/mandelbrot.python +44 -0
- data/sources/python/mandelbrot.python-2.python +35 -0
- data/sources/python/mandelbrot.python-3.python +46 -0
- data/sources/python/matrix.python +34 -0
- data/sources/python/matrix.python-2.python +23 -0
- data/sources/python/message.python +24 -0
- data/sources/python/message.python-2.python +20 -0
- data/sources/python/message.python-3.python +19 -0
- data/sources/python/meteor.python +210 -0
- data/sources/python/meteor.python-2.python +192 -0
- data/sources/python/methcall.python +51 -0
- data/sources/python/moments.python +65 -0
- data/sources/python/nbody.python +123 -0
- data/sources/python/nbody.python-2.python +120 -0
- data/sources/python/nestedloop.python +24 -0
- data/sources/python/nsieve.python +27 -0
- data/sources/python/nsieve.python-2.python +23 -0
- data/sources/python/nsieve.python-4.python +25 -0
- data/sources/python/nsievebits.python +27 -0
- data/sources/python/nsievebits.python-2.python +43 -0
- data/sources/python/objinst.python +53 -0
- data/sources/python/partialsums.python +37 -0
- data/sources/python/partialsums.python-2.python +35 -0
- data/sources/python/partialsums.python-3.python +48 -0
- data/sources/python/pidigits.python +38 -0
- data/sources/python/pidigits.python-3.python +63 -0
- data/sources/python/pidigits.python-4.python +24 -0
- data/sources/python/process.python +51 -0
- data/sources/python/process.python-2.python +133 -0
- data/sources/python/prodcons.python +51 -0
- data/sources/python/prodcons.python-2.python +0 -0
- data/sources/python/random.python +27 -0
- data/sources/python/raytracer.python +203 -0
- data/sources/python/recursive.python +35 -0
- data/sources/python/regexdna.python +39 -0
- data/sources/python/regexdna.python-2.python +34 -0
- data/sources/python/regexmatch.python +36 -0
- data/sources/python/revcomp.python-3.python +31 -0
- data/sources/python/reversefile.python +13 -0
- data/sources/python/reversefile.python-2.python +0 -0
- data/sources/python/reversefile.python-3.python +0 -0
- data/sources/python/sieve.python +50 -0
- data/sources/python/spectralnorm.python-2.python +36 -0
- data/sources/python/spellcheck.python +17 -0
- data/sources/python/strcat.python +35 -0
- data/sources/python/strcat.python-2.python +0 -0
- data/sources/python/sumcol.python-2.python +0 -0
- data/sources/python/sumcol.python-3.python +0 -0
- data/sources/python/takfp.python +19 -0
- data/sources/python/tcpecho.python +67 -0
- data/sources/python/tcprequest.python +67 -0
- data/sources/python/tcpstream.python +67 -0
- data/sources/python/threadring.python +47 -0
- data/sources/python/threadring.python-2.python +40 -0
- data/sources/python/threadring.python-3.python +34 -0
- data/sources/python/wc.python-2.python +19 -0
- data/sources/python/wordfreq.python +43 -0
- data/sources/python/wordfreq.python-2.python +0 -0
- data/sources/python/wordfreq.python-3.python +28 -0
- data/sources/python/wordfreq.python-4.python +38 -0
- data/sources/python/wordfreq.python-5.python +39 -0
- data/sources/ruby/ackermann.ruby +17 -0
- data/sources/ruby/ackermann.ruby-5.ruby +153 -0
- data/sources/ruby/ary.ruby +22 -0
- data/sources/ruby/binarytrees.ruby-2.ruby +55 -0
- data/sources/ruby/chameneos.ruby-2.ruby +71 -0
- data/sources/ruby/dispatch.ruby +114 -0
- data/sources/ruby/echo.ruby +41 -0
- data/sources/ruby/except.ruby +61 -0
- data/sources/ruby/except.ruby-2.ruby +61 -0
- data/sources/ruby/fannkuch.ruby +42 -0
- data/sources/ruby/fasta.ruby +81 -0
- data/sources/ruby/fibo.ruby +15 -0
- data/sources/ruby/harmonic.ruby-2.ruby +15 -0
- data/sources/ruby/hash.ruby +19 -0
- data/sources/ruby/hash2.ruby +23 -0
- data/sources/ruby/heapsort.ruby +55 -0
- data/sources/ruby/hello.ruby +6 -0
- data/sources/ruby/knucleotide.ruby-2.ruby +44 -0
- data/sources/ruby/lists.ruby +46 -0
- data/sources/ruby/mandelbrot.ruby-3.ruby +63 -0
- data/sources/ruby/matrix.ruby +40 -0
- data/sources/ruby/matrix.ruby-2.ruby +30 -0
- data/sources/ruby/message.ruby +29 -0
- data/sources/ruby/message.ruby-2.ruby +24 -0
- data/sources/ruby/meteor.ruby +386 -0
- data/sources/ruby/meteor.ruby-2.ruby +561 -0
- data/sources/ruby/methcall.ruby +58 -0
- data/sources/ruby/methcall.ruby-2.ruby +54 -0
- data/sources/ruby/moments.ruby +64 -0
- data/sources/ruby/nbody.ruby-2.ruby +145 -0
- data/sources/ruby/nestedloop.ruby +22 -0
- data/sources/ruby/nsieve.ruby +36 -0
- data/sources/ruby/nsieve.ruby-2.ruby +25 -0
- data/sources/ruby/nsievebits.ruby-2.ruby +42 -0
- data/sources/ruby/objinst.ruby +58 -0
- data/sources/ruby/partialsums.ruby +39 -0
- data/sources/ruby/pidigits.ruby +92 -0
- data/sources/ruby/pidigits.ruby-2.ruby +109 -0
- data/sources/ruby/prodcons.ruby +41 -0
- data/sources/ruby/random.ruby +17 -0
- data/sources/ruby/recursive.ruby-2.ruby +53 -0
- data/sources/ruby/regexdna.ruby +32 -0
- data/sources/ruby/regexdna.ruby-2.ruby +38 -0
- data/sources/ruby/regexmatch.ruby +33 -0
- data/sources/ruby/revcomp.ruby +28 -0
- data/sources/ruby/reversefile.ruby +7 -0
- data/sources/ruby/sieve.ruby +30 -0
- data/sources/ruby/spectralnorm.ruby +48 -0
- data/sources/ruby/spellcheck.ruby +18 -0
- data/sources/ruby/spellcheck.ruby-2.ruby +0 -0
- data/sources/ruby/strcat.ruby +12 -0
- data/sources/ruby/strcat.ruby-2.ruby +12 -0
- data/sources/ruby/sumcol.ruby +12 -0
- data/sources/ruby/sumcol.ruby-2.ruby +5 -0
- data/sources/ruby/takfp.ruby +15 -0
- data/sources/ruby/tcpecho.ruby +45 -0
- data/sources/ruby/tcprequest.ruby +45 -0
- data/sources/ruby/tcpstream.ruby +45 -0
- data/sources/ruby/threadring.ruby +61 -0
- data/sources/ruby/threadring.ruby-2.ruby +33 -0
- data/sources/ruby/wc.ruby +15 -0
- data/sources/ruby/wordfreq.ruby +17 -0
- data/sources/ruby/wordfreq.ruby2.ruby +0 -0
- data/test/fixtures/sources/gcc/ackermann.gcc-2.gcc +93 -0
- data/test/fixtures/sources/python/ackermann.python +21 -0
- data/test/fixtures/sources/ruby/ackermann.ruby +17 -0
- data/test/test_source_classifier.rb +40 -0
- data/test/test_trainer.rb +34 -0
- data/trainer.bin +1193 -0
- metadata +393 -0
@@ -0,0 +1,29 @@
|
|
1
|
+
# The Computer Language Shootout
|
2
|
+
# http://shootout.alioth.debian.org/
|
3
|
+
#
|
4
|
+
# Contributed by Jesse Millikan
|
5
|
+
|
6
|
+
require 'thread'
|
7
|
+
|
8
|
+
N = ARGV[0].to_i
|
9
|
+
next_q = last_q = SizedQueue.new(1)
|
10
|
+
|
11
|
+
500.times {
|
12
|
+
q = SizedQueue.new(1)
|
13
|
+
q2 = next_q
|
14
|
+
Thread.new{
|
15
|
+
i = N
|
16
|
+
while i > 0
|
17
|
+
q2.push(q.pop+1)
|
18
|
+
i -= 1
|
19
|
+
end
|
20
|
+
}
|
21
|
+
next_q = q
|
22
|
+
}
|
23
|
+
|
24
|
+
Thread.new{N.times{next_q.push(0)}}
|
25
|
+
|
26
|
+
t = 0
|
27
|
+
N.times{t+=last_q.pop}
|
28
|
+
puts t
|
29
|
+
|
@@ -0,0 +1,24 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# Gonzalo Garramuno -- Dec.31 2006
|
4
|
+
#
|
5
|
+
|
6
|
+
def coroutine(n)
|
7
|
+
if n > 1
|
8
|
+
coroutine(n-1) { |x| yield x + 1 }
|
9
|
+
else
|
10
|
+
yield 1 while true
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
iter = 0
|
15
|
+
last = ARGV[0].to_i
|
16
|
+
count = 0
|
17
|
+
|
18
|
+
coroutine( 500 ) { |x|
|
19
|
+
break if iter >= last
|
20
|
+
count += x
|
21
|
+
iter += 1
|
22
|
+
}
|
23
|
+
|
24
|
+
puts count
|
@@ -0,0 +1,386 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# The Computer Language Shootout
|
4
|
+
# http://shootout.alioth.debian.org
|
5
|
+
# contributed by Kevin Barnes (Ruby novice)
|
6
|
+
|
7
|
+
def blank_board
|
8
|
+
0b111111100000100000100000100000100000100000100000100000100000100000
|
9
|
+
end
|
10
|
+
|
11
|
+
def is_even( location)
|
12
|
+
(location % 12) < 6
|
13
|
+
end
|
14
|
+
|
15
|
+
def create_collector_support
|
16
|
+
odd_map = [0b11, 0b110, 0b1100, 0b11000, 0b10000]
|
17
|
+
even_map = [0b1, 0b11, 0b110, 0b1100, 0b11000]
|
18
|
+
|
19
|
+
all_odds = Array.new(0b100000)
|
20
|
+
all_evens = Array.new(0b100000)
|
21
|
+
bit_counts = Array.new(0b100000)
|
22
|
+
new_regions = Array.new(0b100000)
|
23
|
+
0.upto(0b11111) do | i |
|
24
|
+
bit_count = odd = even = 0
|
25
|
+
0.upto(4) do | bit |
|
26
|
+
if (i[bit] == 1) then
|
27
|
+
bit_count += 1
|
28
|
+
odd |= odd_map[bit]
|
29
|
+
even |= even_map[bit]
|
30
|
+
end
|
31
|
+
end
|
32
|
+
all_odds[i] = odd
|
33
|
+
all_evens[i] = even
|
34
|
+
bit_counts[i] = bit_count
|
35
|
+
new_regions[i] = create_regions( i)
|
36
|
+
end
|
37
|
+
|
38
|
+
@@converter = []
|
39
|
+
10.times { | row | @@converter.push((row % 2 == 0) ? all_evens : all_odds) }
|
40
|
+
@@bit_counts = bit_counts
|
41
|
+
@@regions = new_regions.collect { | set | set.collect { | value | [ value, bit_counts[value], value] } }
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
def prunable( board, location, slotting = false)
|
46
|
+
collectors = []
|
47
|
+
(location / 6).to_i.upto(9) do | row_on |
|
48
|
+
regions = @@regions[(board >> (row_on * 6)) & 0b11111 ^ 0b11111]
|
49
|
+
converter = @@converter[row_on]
|
50
|
+
initial_collector_count = collectors.length
|
51
|
+
regions.each do | region |
|
52
|
+
collector_found = nil
|
53
|
+
region_mask = region[0]
|
54
|
+
initial_collector_count.times do | collector_num |
|
55
|
+
collector = collectors[collector_num]
|
56
|
+
if (collector) then
|
57
|
+
collector_mask = collector[0]
|
58
|
+
if (collector_mask & region_mask != 0) then
|
59
|
+
if (collector_found) then
|
60
|
+
collector_found[0] |= collector_mask
|
61
|
+
collector_found[1] += collector[1]
|
62
|
+
collector_found[2] |= collector[2]
|
63
|
+
collectors[collector_num] = nil
|
64
|
+
else
|
65
|
+
collector_found = collector
|
66
|
+
collector[1] += region[1]
|
67
|
+
collector[2] |= region_mask
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
if (collector_found == nil) then
|
73
|
+
collectors.push(Array.new(region))
|
74
|
+
end
|
75
|
+
end
|
76
|
+
collectors.length.times do | collector_num |
|
77
|
+
collector = collectors[collector_num]
|
78
|
+
if (collector) then
|
79
|
+
if (collector[2] == 0) then
|
80
|
+
return true if (collector[1] % 5 != 0)
|
81
|
+
collectors[collector_num] = nil
|
82
|
+
else
|
83
|
+
return false if (collector[2] == 0b11111 && !slotting)
|
84
|
+
collector[0] = converter[collector[2]]
|
85
|
+
collector[2] = 0
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
collectors.compact!
|
90
|
+
end
|
91
|
+
return false if (collectors.length <= 1)
|
92
|
+
collectors.any? { | collector | (collector[1] % 5) != 0 }
|
93
|
+
end
|
94
|
+
|
95
|
+
def as_binary( value)
|
96
|
+
rtn = ""
|
97
|
+
5.times do | i |
|
98
|
+
rtn += "#{value[i]}"
|
99
|
+
end
|
100
|
+
rtn
|
101
|
+
end
|
102
|
+
|
103
|
+
def create_regions( value )
|
104
|
+
regions = []
|
105
|
+
cur_region = 0
|
106
|
+
5.times do | bit |
|
107
|
+
if (value[bit] == 1) then
|
108
|
+
cur_region |= 1 << bit
|
109
|
+
else
|
110
|
+
if (cur_region !=0 ) then
|
111
|
+
regions.push( cur_region)
|
112
|
+
cur_region = 0;
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
regions.push(cur_region) if (cur_region != 0)
|
117
|
+
regions
|
118
|
+
end
|
119
|
+
|
120
|
+
def print_board( board, padding = "", rows = 10, row_offset = 0)
|
121
|
+
rows.times do | row |
|
122
|
+
rtn = padding
|
123
|
+
rtn = "#{rtn} " if ((row + row_offset) % 2) == 1
|
124
|
+
6.times do | col |
|
125
|
+
rtn = "#{rtn}#{board[row*6+col]} "
|
126
|
+
end
|
127
|
+
print "#{rtn}\n"
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
class Rotation
|
132
|
+
attr_reader :start_masks
|
133
|
+
|
134
|
+
@@rotation_even_adder = { :west => -1, :east => 1, :nw => -7, :ne => -6, :sw => 5, :se => 6 }
|
135
|
+
@@rotation_odd_adder = { :west => -1, :east => 1, :nw => -6, :ne => -5, :sw => 6, :se => 7 }
|
136
|
+
|
137
|
+
def initialize( directions )
|
138
|
+
values, min = get_values( directions )
|
139
|
+
@even_offsets, @odd_offsets = normalize_offsets( values, min)
|
140
|
+
|
141
|
+
@even_mask = mask_for_offsets( @even_offsets)
|
142
|
+
@odd_mask = mask_for_offsets( @odd_offsets)
|
143
|
+
|
144
|
+
@start_masks = Array.new(60)
|
145
|
+
|
146
|
+
0.upto(59) do | offset |
|
147
|
+
mask = is_even(offset) ? (@even_mask << offset) : (@odd_mask << offset)
|
148
|
+
if (blank_board & mask == 0 && !prunable(blank_board | mask, 0, true)) then
|
149
|
+
@start_masks[offset] = mask
|
150
|
+
else
|
151
|
+
@start_masks[offset] = false
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def offsets( location)
|
157
|
+
if is_even( location) then
|
158
|
+
@even_offsets.collect { | value | value + location }
|
159
|
+
else
|
160
|
+
@odd_offsets.collect { | value | value + location }
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
def normalize_offsets( values, min)
|
165
|
+
even_min = is_even(min)
|
166
|
+
other_min = even_min ? min + 6 : min + 7
|
167
|
+
other_values = values.collect do | value |
|
168
|
+
if is_even(value) then
|
169
|
+
value + 6 - other_min
|
170
|
+
else
|
171
|
+
value + 7 - other_min
|
172
|
+
end
|
173
|
+
end
|
174
|
+
values.collect! { | value | value - min }
|
175
|
+
|
176
|
+
if even_min then
|
177
|
+
[values, other_values]
|
178
|
+
else
|
179
|
+
[other_values, values]
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
def mask_for_offsets( offsets )
|
184
|
+
mask = 0
|
185
|
+
offsets.each { | value | mask = mask + ( 1 << value ) }
|
186
|
+
mask
|
187
|
+
end
|
188
|
+
|
189
|
+
def start_adjust( directions )
|
190
|
+
south = east = 0;
|
191
|
+
directions.each do | direction |
|
192
|
+
east += 1 if ( direction == :sw || direction == :nw || direction == :west )
|
193
|
+
south += 1 if ( direction == :nw || direction == :ne )
|
194
|
+
end
|
195
|
+
[south, east]
|
196
|
+
end
|
197
|
+
|
198
|
+
def get_values ( directions )
|
199
|
+
south, east = start_adjust(directions)
|
200
|
+
min = start = south * 6 + east
|
201
|
+
values = [ start ]
|
202
|
+
directions.each do | direction |
|
203
|
+
if (start % 12 >= 6) then
|
204
|
+
start += @@rotation_odd_adder[direction]
|
205
|
+
else
|
206
|
+
start += @@rotation_even_adder[direction]
|
207
|
+
end
|
208
|
+
min = start if (start < min)
|
209
|
+
values += [ start ]
|
210
|
+
end
|
211
|
+
|
212
|
+
if (values.length != 5)
|
213
|
+
values.uniq!
|
214
|
+
end
|
215
|
+
|
216
|
+
[ values, min ]
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
class Piece
|
221
|
+
attr_reader :rotations, :type, :masks
|
222
|
+
attr_accessor :placed
|
223
|
+
|
224
|
+
@@flip_converter = { :west => :west, :east => :east, :nw => :sw, :ne => :se, :sw => :nw, :se => :ne }
|
225
|
+
@@rotate_converter = { :west => :nw, :east => :se, :nw => :ne, :ne => :east, :sw => :west, :se => :sw }
|
226
|
+
|
227
|
+
def initialize( directions, type )
|
228
|
+
@type = type
|
229
|
+
@rotations = Array.new();
|
230
|
+
@map = {}
|
231
|
+
generate_rotations( directions )
|
232
|
+
directions.collect! { | value | @@flip_converter[value] }
|
233
|
+
generate_rotations( directions )
|
234
|
+
|
235
|
+
@masks = Array.new();
|
236
|
+
0.upto(59) do | i |
|
237
|
+
@masks[i] = @rotations.collect do | rotation |
|
238
|
+
mask = rotation.start_masks[i]
|
239
|
+
@map[mask] = [ i, rotation ] if (mask)
|
240
|
+
mask || nil
|
241
|
+
end
|
242
|
+
@masks[i].compact!
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
def generate_rotations( directions )
|
247
|
+
6.times do
|
248
|
+
rotations.push( Rotation.new(directions))
|
249
|
+
directions.collect! { | value | @@rotate_converter[value] }
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
def fill_array( board_array)
|
254
|
+
location, rotation = @map[@placed]
|
255
|
+
rotation.offsets(location).each do | offset |
|
256
|
+
row, col = offset.divmod(6)
|
257
|
+
board_array[ row*5 + col ] = @type.to_s
|
258
|
+
end
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
class Processor
|
263
|
+
attr :pieces, :board
|
264
|
+
|
265
|
+
def initialize()
|
266
|
+
create_collector_support
|
267
|
+
@pieces = [
|
268
|
+
Piece.new( [ :east, :east, :east, :se ], 0),
|
269
|
+
Piece.new( [ :ne, :east, :ne, :nw ], 1),
|
270
|
+
Piece.new( [ :nw, :ne, :east, :east ], 2),
|
271
|
+
Piece.new( [ :east, :east, :sw, :se ], 3),
|
272
|
+
Piece.new( [ :ne, :nw, :se, :east, :se ], 4),
|
273
|
+
Piece.new( [ :east, :ne, :se, :ne ], 5),
|
274
|
+
Piece.new( [ :east, :sw, :sw, :se ], 6),
|
275
|
+
Piece.new( [ :ne, :se, :east, :ne ], 7),
|
276
|
+
Piece.new( [ :se, :se, :east, :se ], 8),
|
277
|
+
Piece.new( [ :se, :se, :se, :west ], 9) ];
|
278
|
+
|
279
|
+
@all_pieces = Array.new( @pieces)
|
280
|
+
|
281
|
+
@min_board = "99999999999999999999999999999999999999999999999999"
|
282
|
+
@max_board = "00000000000000000000000000000000000000000000000000"
|
283
|
+
@stop_count = ARGV[0].to_i || 2089
|
284
|
+
@all_boards = {}
|
285
|
+
@boards_found = 0
|
286
|
+
end
|
287
|
+
|
288
|
+
def find_all
|
289
|
+
find_top( 0)
|
290
|
+
find_top( 1)
|
291
|
+
print_results
|
292
|
+
end
|
293
|
+
|
294
|
+
def print_results
|
295
|
+
print "#{@boards_found} solutions found\n\n"
|
296
|
+
print_full_board( @min_board)
|
297
|
+
print "\n"
|
298
|
+
print_full_board( @max_board)
|
299
|
+
print "\n"
|
300
|
+
end
|
301
|
+
|
302
|
+
def find_top( rotation_skip)
|
303
|
+
board = blank_board
|
304
|
+
@pieces.length.times do
|
305
|
+
piece = @pieces.shift
|
306
|
+
piece.masks[0].each do | mask |
|
307
|
+
if ((rotation_skip += 1) % 2 == 0) then
|
308
|
+
piece.placed = mask
|
309
|
+
find( 1, 1, board | mask)
|
310
|
+
end
|
311
|
+
end
|
312
|
+
@pieces.push(piece)
|
313
|
+
end
|
314
|
+
end
|
315
|
+
|
316
|
+
def find( start_location, placed, board)
|
317
|
+
while board[start_location] == 1
|
318
|
+
start_location += 1
|
319
|
+
end
|
320
|
+
|
321
|
+
return if (start_location < 28 && prunable( board, start_location))
|
322
|
+
|
323
|
+
@pieces.length.times do
|
324
|
+
piece = @pieces.shift
|
325
|
+
piece.masks[start_location].each do | mask |
|
326
|
+
if (mask & board == 0) then
|
327
|
+
piece.placed = mask
|
328
|
+
if (placed == 9) then
|
329
|
+
add_board
|
330
|
+
else
|
331
|
+
find( start_location + 1, placed + 1, board | mask)
|
332
|
+
end
|
333
|
+
end
|
334
|
+
end
|
335
|
+
@pieces.push(piece)
|
336
|
+
end
|
337
|
+
end
|
338
|
+
|
339
|
+
def print_full_board( board_string)
|
340
|
+
10.times do | row |
|
341
|
+
print " " if (row % 2 == 1)
|
342
|
+
5.times do | col |
|
343
|
+
print "#{board_string[row*5 + col,1]} "
|
344
|
+
end
|
345
|
+
print "\n"
|
346
|
+
end
|
347
|
+
end
|
348
|
+
|
349
|
+
def add_board
|
350
|
+
board_array = Array.new(50)
|
351
|
+
@all_pieces.each do | piece |
|
352
|
+
piece.fill_array( board_array )
|
353
|
+
end
|
354
|
+
start_board = board_string = board_array.join("")
|
355
|
+
save( board_string)
|
356
|
+
board_string = flip( board_string)
|
357
|
+
save( board_string)
|
358
|
+
end
|
359
|
+
|
360
|
+
def flip( board_string)
|
361
|
+
new_string = ""
|
362
|
+
50.times do | i |
|
363
|
+
row, col = i.divmod(5)
|
364
|
+
new_string += board_string[((9 - row) * 5) + (4 - col), 1]
|
365
|
+
end
|
366
|
+
new_string
|
367
|
+
end
|
368
|
+
|
369
|
+
def save( board_string)
|
370
|
+
if (@all_boards[board_string] == nil) then
|
371
|
+
@min_board = board_string if (board_string < @min_board)
|
372
|
+
@max_board = board_string if (board_string > @max_board)
|
373
|
+
@all_boards.store(board_string,true)
|
374
|
+
@boards_found += 1
|
375
|
+
|
376
|
+
if (@boards_found == @stop_count) then
|
377
|
+
print_results
|
378
|
+
exit(0)
|
379
|
+
end
|
380
|
+
end
|
381
|
+
end
|
382
|
+
|
383
|
+
end
|
384
|
+
|
385
|
+
proc = Processor.new.find_all
|
386
|
+
|
@@ -0,0 +1,561 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# The Computer Language Shootout
|
4
|
+
# http://shootout.alioth.debian.org
|
5
|
+
# contributed by Kevin Barnes (Ruby novice)
|
6
|
+
|
7
|
+
# PROGRAM: the main body is at the bottom.
|
8
|
+
# 1) read about the problem here: http://www-128.ibm.com/developerworks/java/library/j-javaopt/
|
9
|
+
# 2) see how I represent a board as a bitmask by reading the blank_board comments
|
10
|
+
# 3) read as your mental paths take you
|
11
|
+
|
12
|
+
# class to represent all information about a particular rotation of a particular piece
|
13
|
+
class Rotation
|
14
|
+
# an array (by location) containing a bit mask for how the piece maps at the given location.
|
15
|
+
# if the rotation is illegal at that location the mask will contain false
|
16
|
+
attr_reader :start_masks
|
17
|
+
|
18
|
+
# maps a direction to a relative location. these differ depending on whether it is an even or
|
19
|
+
# odd row being mapped from
|
20
|
+
@@rotation_even_adder = { :west => -1, :east => 1, :nw => -7, :ne => -6, :sw => 5, :se => 6 }
|
21
|
+
@@rotation_odd_adder = { :west => -1, :east => 1, :nw => -6, :ne => -5, :sw => 6, :se => 7 }
|
22
|
+
|
23
|
+
def initialize( directions )
|
24
|
+
@even_offsets, @odd_offsets = normalize_offsets( get_values( directions ))
|
25
|
+
|
26
|
+
@even_mask = mask_for_offsets( @even_offsets)
|
27
|
+
@odd_mask = mask_for_offsets( @odd_offsets)
|
28
|
+
|
29
|
+
@start_masks = Array.new(60)
|
30
|
+
|
31
|
+
# create the rotational masks by placing the base mask at the location and seeing if
|
32
|
+
# 1) it overlaps the boundries and 2) it produces a prunable board. if either of these
|
33
|
+
# is true the piece cannot be placed
|
34
|
+
0.upto(59) do | offset |
|
35
|
+
mask = is_even(offset) ? (@even_mask << offset) : (@odd_mask << offset)
|
36
|
+
if (blank_board & mask == 0 && !prunable(blank_board | mask, 0, true)) then
|
37
|
+
imask = compute_required( mask, offset)
|
38
|
+
@start_masks[offset] = [ mask, imask, imask | mask ]
|
39
|
+
else
|
40
|
+
@start_masks[offset] = false
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def compute_required( mask, offset )
|
46
|
+
board = blank_board
|
47
|
+
0.upto(offset) { | i | board |= 1 << i }
|
48
|
+
board |= mask
|
49
|
+
return 0 if (!prunable(board | mask, offset))
|
50
|
+
board = flood_fill(board,58)
|
51
|
+
count = 0
|
52
|
+
imask = 0
|
53
|
+
0.upto(59) do | i |
|
54
|
+
if (board[i] == 0) then
|
55
|
+
imask |= (1 << i)
|
56
|
+
count += 1
|
57
|
+
end
|
58
|
+
end
|
59
|
+
(count > 0 && count < 5) ? imask : 0
|
60
|
+
end
|
61
|
+
|
62
|
+
def flood_fill( board, location)
|
63
|
+
return board if (board[location] == 1)
|
64
|
+
board |= 1 << location
|
65
|
+
row, col = location.divmod(6)
|
66
|
+
board = flood_fill( board, location - 1) if (col > 0)
|
67
|
+
board = flood_fill( board, location + 1) if (col < 4)
|
68
|
+
if (row % 2 == 0) then
|
69
|
+
board = flood_fill( board, location - 7) if (col > 0 && row > 0)
|
70
|
+
board = flood_fill( board, location - 6) if (row > 0)
|
71
|
+
board = flood_fill( board, location + 6) if (row < 9)
|
72
|
+
board = flood_fill( board, location + 5) if (col > 0 && row < 9)
|
73
|
+
else
|
74
|
+
board = flood_fill( board, location - 5) if (col < 4 && row > 0)
|
75
|
+
board = flood_fill( board, location - 6) if (row > 0)
|
76
|
+
board = flood_fill( board, location + 6) if (row < 9)
|
77
|
+
board = flood_fill( board, location + 7) if (col < 4 && row < 9)
|
78
|
+
end
|
79
|
+
board
|
80
|
+
end
|
81
|
+
|
82
|
+
# given a location, produces a list of relative locations covered by the piece at this rotation
|
83
|
+
def offsets( location)
|
84
|
+
if is_even( location) then
|
85
|
+
@even_offsets.collect { | value | value + location }
|
86
|
+
else
|
87
|
+
@odd_offsets.collect { | value | value + location }
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
# returns a set of offsets relative to the top-left most piece of the rotation (by even or odd rows)
|
92
|
+
# this is hard to explain. imagine we have this partial board:
|
93
|
+
# 0 0 0 0 0 x [positions 0-5]
|
94
|
+
# 0 0 1 1 0 x [positions 6-11]
|
95
|
+
# 0 0 1 0 0 x [positions 12-17]
|
96
|
+
# 0 1 0 0 0 x [positions 18-23]
|
97
|
+
# 0 1 0 0 0 x [positions 24-29]
|
98
|
+
# 0 0 0 0 0 x [positions 30-35]
|
99
|
+
# ...
|
100
|
+
# The top-left of the piece is at position 8, the
|
101
|
+
# board would be passed as a set of positions (values array) containing [8,9,14,19,25] not necessarily in that
|
102
|
+
# sorted order. Since that array starts on an odd row, the offsets for an odd row are: [0,1,6,11,17] obtained
|
103
|
+
# by subtracting 8 from everything. Now imagine the piece shifted up and to the right so it's on an even row:
|
104
|
+
# 0 0 0 1 1 x [positions 0-5]
|
105
|
+
# 0 0 1 0 0 x [positions 6-11]
|
106
|
+
# 0 0 1 0 0 x [positions 12-17]
|
107
|
+
# 0 1 0 0 0 x [positions 18-23]
|
108
|
+
# 0 0 0 0 0 x [positions 24-29]
|
109
|
+
# 0 0 0 0 0 x [positions 30-35]
|
110
|
+
# ...
|
111
|
+
# Now the positions are [3,4,8,14,19] which after subtracting the lowest value (3) gives [0,1,5,11,16] thus, the
|
112
|
+
# offsets for this particular piece are (in even, odd order) [0,1,5,11,16],[0,1,6,11,17] which is what
|
113
|
+
# this function would return
|
114
|
+
def normalize_offsets( values)
|
115
|
+
min = values.min
|
116
|
+
even_min = is_even(min)
|
117
|
+
other_min = even_min ? min + 6 : min + 7
|
118
|
+
other_values = values.collect do | value |
|
119
|
+
if is_even(value) then
|
120
|
+
value + 6 - other_min
|
121
|
+
else
|
122
|
+
value + 7 - other_min
|
123
|
+
end
|
124
|
+
end
|
125
|
+
values.collect! { | value | value - min }
|
126
|
+
|
127
|
+
if even_min then
|
128
|
+
[values, other_values]
|
129
|
+
else
|
130
|
+
[other_values, values]
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
# produce a bitmask representation of an array of offset locations
|
135
|
+
def mask_for_offsets( offsets )
|
136
|
+
mask = 0
|
137
|
+
offsets.each { | value | mask = mask + ( 1 << value ) }
|
138
|
+
mask
|
139
|
+
end
|
140
|
+
|
141
|
+
# finds a "safe" position that a position as described by a list of directions can be placed
|
142
|
+
# without falling off any edge of the board. the values returned a location to place the first piece
|
143
|
+
# at so it will fit after making the described moves
|
144
|
+
def start_adjust( directions )
|
145
|
+
south = east = 0;
|
146
|
+
directions.each do | direction |
|
147
|
+
east += 1 if ( direction == :sw || direction == :nw || direction == :west )
|
148
|
+
south += 1 if ( direction == :nw || direction == :ne )
|
149
|
+
end
|
150
|
+
south * 6 + east
|
151
|
+
end
|
152
|
+
|
153
|
+
# given a set of directions places the piece (as defined by a set of directions) on the board at
|
154
|
+
# a location that will not take it off the edge
|
155
|
+
def get_values ( directions )
|
156
|
+
start = start_adjust(directions)
|
157
|
+
values = [ start ]
|
158
|
+
directions.each do | direction |
|
159
|
+
if (start % 12 >= 6) then
|
160
|
+
start += @@rotation_odd_adder[direction]
|
161
|
+
else
|
162
|
+
start += @@rotation_even_adder[direction]
|
163
|
+
end
|
164
|
+
values += [ start ]
|
165
|
+
end
|
166
|
+
|
167
|
+
# some moves take you back to an existing location, we'll strip duplicates
|
168
|
+
values.uniq
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
# describes a piece and caches information about its rotations to as to be efficient for iteration
|
173
|
+
# ATTRIBUTES:
|
174
|
+
# rotations -- all the rotations of the piece
|
175
|
+
# type -- a numeic "name" of the piece
|
176
|
+
# masks -- an array by location of all legal rotational masks (a n inner array) for that location
|
177
|
+
# placed -- the mask that this piece was last placed at (not a location, but the actual mask used)
|
178
|
+
class Piece
|
179
|
+
attr_reader :rotations, :type, :masks
|
180
|
+
attr_accessor :placed
|
181
|
+
|
182
|
+
# transform hashes that change one direction into another when you either flip or rotate a set of directions
|
183
|
+
@@flip_converter = { :west => :west, :east => :east, :nw => :sw, :ne => :se, :sw => :nw, :se => :ne }
|
184
|
+
@@rotate_converter = { :west => :nw, :east => :se, :nw => :ne, :ne => :east, :sw => :west, :se => :sw }
|
185
|
+
|
186
|
+
def initialize( directions, type )
|
187
|
+
@type = type
|
188
|
+
@rotations = Array.new();
|
189
|
+
@map = {}
|
190
|
+
|
191
|
+
generate_rotations( directions )
|
192
|
+
directions.collect! { | value | @@flip_converter[value] }
|
193
|
+
generate_rotations( directions )
|
194
|
+
|
195
|
+
# creates the masks AND a map that returns [location, rotation] for any given mask
|
196
|
+
# this is used when a board is found and we want to draw it, otherwise the map is unused
|
197
|
+
@masks = Array.new();
|
198
|
+
0.upto(59) do | i |
|
199
|
+
even = true
|
200
|
+
@masks[i] = @rotations.collect do | rotation |
|
201
|
+
mask = rotation.start_masks[i]
|
202
|
+
@map[mask[0]] = [ i, rotation ] if (mask)
|
203
|
+
mask || nil
|
204
|
+
end
|
205
|
+
@masks[i].compact!
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
# rotates a set of directions through all six angles and adds a Rotation to the list for each one
|
210
|
+
def generate_rotations( directions )
|
211
|
+
6.times do
|
212
|
+
rotations.push( Rotation.new(directions))
|
213
|
+
directions.collect! { | value | @@rotate_converter[value] }
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
# given a board string, adds this piece to the board at whatever location/rotation
|
218
|
+
# important: the outbound board string is 5 wide, the normal location notation is six wide (padded)
|
219
|
+
def fill_string( board_string)
|
220
|
+
location, rotation = @map[@placed]
|
221
|
+
rotation.offsets(location).each do | offset |
|
222
|
+
row, col = offset.divmod(6)
|
223
|
+
board_string[ row*5 + col, 1 ] = @type.to_s
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
# a blank bit board having this form:
|
229
|
+
#
|
230
|
+
# 0 0 0 0 0 1
|
231
|
+
# 0 0 0 0 0 1
|
232
|
+
# 0 0 0 0 0 1
|
233
|
+
# 0 0 0 0 0 1
|
234
|
+
# 0 0 0 0 0 1
|
235
|
+
# 0 0 0 0 0 1
|
236
|
+
# 0 0 0 0 0 1
|
237
|
+
# 0 0 0 0 0 1
|
238
|
+
# 0 0 0 0 0 1
|
239
|
+
# 0 0 0 0 0 1
|
240
|
+
# 1 1 1 1 1 1
|
241
|
+
#
|
242
|
+
# where left lest significant bit is the top left and the most significant is the lower right
|
243
|
+
# the actual board only consists of the 0 places, the 1 places are blockers to keep things from running
|
244
|
+
# off the edges or bottom
|
245
|
+
def blank_board
|
246
|
+
0b111111100000100000100000100000100000100000100000100000100000100000
|
247
|
+
end
|
248
|
+
|
249
|
+
def full_board
|
250
|
+
0b111111111111111111111111111111111111111111111111111111111111111111
|
251
|
+
end
|
252
|
+
|
253
|
+
# determines if a location (bit position) is in an even row
|
254
|
+
def is_even( location)
|
255
|
+
(location % 12) < 6
|
256
|
+
end
|
257
|
+
|
258
|
+
# support function that create three utility maps:
|
259
|
+
# @@converter -- for each row an array that maps a five bit row (via array mapping)
|
260
|
+
# to the a a five bit representation of the bits below it
|
261
|
+
# @@bit_count -- maps a five bit row (via array mapping) to the number of 1s in the row
|
262
|
+
# @@new_regions -- maps a five bit row (via array mapping) to an array of "region" arrays
|
263
|
+
# a region array has three values the first is a mask of bits in the region,
|
264
|
+
# the second is the count of those bits and the third is identical to the first
|
265
|
+
# examples:
|
266
|
+
# 0b10010 => [ 0b01100, 2, 0b01100 ], [ 0b00001, 1, 0b00001]
|
267
|
+
# 0b01010 => [ 0b10000, 1, 0b10000 ], [ 0b00100, 1, 0b00100 ], [ 0b00001, 1, 0b00001]
|
268
|
+
# 0b10001 => [ 0b01110, 3, 0b01110 ]
|
269
|
+
def create_collector_support
|
270
|
+
odd_map = [0b11, 0b110, 0b1100, 0b11000, 0b10000]
|
271
|
+
even_map = [0b1, 0b11, 0b110, 0b1100, 0b11000]
|
272
|
+
|
273
|
+
all_odds = Array.new(0b100000)
|
274
|
+
all_evens = Array.new(0b100000)
|
275
|
+
bit_counts = Array.new(0b100000)
|
276
|
+
new_regions = Array.new(0b100000)
|
277
|
+
0.upto(0b11111) do | i |
|
278
|
+
bit_count = odd = even = 0
|
279
|
+
0.upto(4) do | bit |
|
280
|
+
if (i[bit] == 1) then
|
281
|
+
bit_count += 1
|
282
|
+
odd |= odd_map[bit]
|
283
|
+
even |= even_map[bit]
|
284
|
+
end
|
285
|
+
end
|
286
|
+
all_odds[i] = odd
|
287
|
+
all_evens[i] = even
|
288
|
+
bit_counts[i] = bit_count
|
289
|
+
new_regions[i] = create_regions( i)
|
290
|
+
end
|
291
|
+
|
292
|
+
@@converter = []
|
293
|
+
10.times { | row | @@converter.push((row % 2 == 0) ? all_evens : all_odds) }
|
294
|
+
@@bit_counts = bit_counts
|
295
|
+
@@regions = new_regions.collect { | set | set.collect { | value | [ value, bit_counts[value], value] } }
|
296
|
+
end
|
297
|
+
|
298
|
+
# determines if a board is punable, meaning that there is no possibility that it
|
299
|
+
# can be filled up with pieces. A board is prunable if there is a grouping of unfilled spaces
|
300
|
+
# that are not a multiple of five. The following board is an example of a prunable board:
|
301
|
+
# 0 0 1 0 0
|
302
|
+
# 0 1 0 0 0
|
303
|
+
# 1 1 0 0 0
|
304
|
+
# 0 1 0 0 0
|
305
|
+
# 0 0 0 0 0
|
306
|
+
# ...
|
307
|
+
#
|
308
|
+
# This board is prunable because the top left corner is only 3 bits in area, no piece will ever fit it
|
309
|
+
# parameters:
|
310
|
+
# board -- an initial bit board (6 bit padded rows, see blank_board for format)
|
311
|
+
# location -- starting location, everything above and to the left is already full
|
312
|
+
# slotting -- set to true only when testing initial pieces, when filling normally
|
313
|
+
# additional assumptions are possible
|
314
|
+
#
|
315
|
+
# Algorithm:
|
316
|
+
# The algorithm starts at the top row (as determined by location) and iterates a row at a time
|
317
|
+
# maintainng counts of active open areas (kept in the collector array) each collector contains
|
318
|
+
# three values at the start of an iteration:
|
319
|
+
# 0: mask of bits that would be adjacent to the collector in this row
|
320
|
+
# 1: the number of bits collected so far
|
321
|
+
# 2: a scratch space starting as zero, but used during the computation to represent
|
322
|
+
# the empty bits in the new row that are adjacent (position 0)
|
323
|
+
# The exact procedure is described in-code
|
324
|
+
def prunable( board, location, slotting = false)
|
325
|
+
collectors = []
|
326
|
+
# loop accross the rows
|
327
|
+
(location / 6).to_i.upto(9) do | row_on |
|
328
|
+
# obtain a set of regions representing the bits of the curent row.
|
329
|
+
regions = @@regions[(board >> (row_on * 6)) & 0b11111]
|
330
|
+
converter = @@converter[row_on]
|
331
|
+
|
332
|
+
# track the number of collectors at the start of the cycle so that
|
333
|
+
# we don't compute against newly created collectors, only existing collectors
|
334
|
+
initial_collector_count = collectors.length
|
335
|
+
|
336
|
+
# loop against the regions. For each region of the row
|
337
|
+
# we will see if it connects to one or more existing collectors.
|
338
|
+
# if it connects to 1 collector, the bits from the region are added to the
|
339
|
+
# bits of the collector and the mask is placed in collector[2]
|
340
|
+
# If the region overlaps more than one collector then all the collectors
|
341
|
+
# it overlaps with are merged into the first one (the others are set to nil in the array)
|
342
|
+
# if NO collectors are found then the region is copied as a new collector
|
343
|
+
regions.each do | region |
|
344
|
+
collector_found = nil
|
345
|
+
region_mask = region[2]
|
346
|
+
initial_collector_count.times do | collector_num |
|
347
|
+
collector = collectors[collector_num]
|
348
|
+
if (collector) then
|
349
|
+
collector_mask = collector[0]
|
350
|
+
if (collector_mask & region_mask != 0) then
|
351
|
+
if (collector_found) then
|
352
|
+
collector_found[0] |= collector_mask
|
353
|
+
collector_found[1] += collector[1]
|
354
|
+
collector_found[2] |= collector[2]
|
355
|
+
collectors[collector_num] = nil
|
356
|
+
else
|
357
|
+
collector_found = collector
|
358
|
+
collector[1] += region[1]
|
359
|
+
collector[2] |= region_mask
|
360
|
+
end
|
361
|
+
end
|
362
|
+
end
|
363
|
+
end
|
364
|
+
if (collector_found == nil) then
|
365
|
+
collectors.push(Array.new(region))
|
366
|
+
end
|
367
|
+
end
|
368
|
+
|
369
|
+
# check the existing collectors, if any collector overlapped no bits in the region its [2] value will
|
370
|
+
# be zero. The size of any such reaason is tested if it is not a muliple of five true is returned since
|
371
|
+
# the board is prunable. if it is a multiple of five it is removed.
|
372
|
+
# Collector that are still active have a new adjacent value [0] set based n the matched bits
|
373
|
+
# and have [2] cleared out for the next cycle.
|
374
|
+
collectors.length.times do | collector_num |
|
375
|
+
collector = collectors[collector_num]
|
376
|
+
if (collector) then
|
377
|
+
if (collector[2] == 0) then
|
378
|
+
return true if (collector[1] % 5 != 0)
|
379
|
+
collectors[collector_num] = nil
|
380
|
+
else
|
381
|
+
# if a collector matches all bits in the row then we can return unprunable early for the
|
382
|
+
# follwing reasons:
|
383
|
+
# 1) there can be no more unavailable bits bince we fill from the top left downward
|
384
|
+
# 2) all previous regions have been closed or joined so only this region can fail
|
385
|
+
# 3) this region must be good since there can never be only 1 region that is nuot
|
386
|
+
# a multiple of five
|
387
|
+
# this rule only applies when filling normally, so we ignore the rule if we are "slotting"
|
388
|
+
# in pieces to see what configurations work for them (the only other time this algorithm is used).
|
389
|
+
return false if (collector[2] == 0b11111 && !slotting)
|
390
|
+
collector[0] = converter[collector[2]]
|
391
|
+
collector[2] = 0
|
392
|
+
end
|
393
|
+
end
|
394
|
+
end
|
395
|
+
|
396
|
+
# get rid of all the empty converters for the next round
|
397
|
+
collectors.compact!
|
398
|
+
end
|
399
|
+
return false if (collectors.length <= 1) # 1 collector or less and the region is fine
|
400
|
+
collectors.any? { | collector | (collector[1] % 5) != 0 } # more than 1 and we test them all for bad size
|
401
|
+
end
|
402
|
+
|
403
|
+
# creates a region given a row mask. see prunable for what a "region" is
|
404
|
+
def create_regions( value )
|
405
|
+
regions = []
|
406
|
+
cur_region = 0
|
407
|
+
5.times do | bit |
|
408
|
+
if (value[bit] == 0) then
|
409
|
+
cur_region |= 1 << bit
|
410
|
+
else
|
411
|
+
if (cur_region != 0 ) then
|
412
|
+
regions.push( cur_region)
|
413
|
+
cur_region = 0;
|
414
|
+
end
|
415
|
+
end
|
416
|
+
end
|
417
|
+
regions.push(cur_region) if (cur_region != 0)
|
418
|
+
regions
|
419
|
+
end
|
420
|
+
|
421
|
+
# find up to the counted number of solutions (or all solutions) and prints the final result
|
422
|
+
def find_all
|
423
|
+
find_top( 1)
|
424
|
+
find_top( 0)
|
425
|
+
print_results
|
426
|
+
end
|
427
|
+
|
428
|
+
# show the board
|
429
|
+
def print_results
|
430
|
+
print "#{@boards_found} solutions found\n\n"
|
431
|
+
print_full_board( @min_board)
|
432
|
+
print "\n"
|
433
|
+
print_full_board( @max_board)
|
434
|
+
print "\n"
|
435
|
+
end
|
436
|
+
|
437
|
+
# finds solutions. This special version of the main function is only used for the top level
|
438
|
+
# the reason for it is basically to force a particular ordering on how the rotations are tested for
|
439
|
+
# the first piece. It is called twice, first looking for placements of the odd rotations and then
|
440
|
+
# looking for placements of the even locations.
|
441
|
+
#
|
442
|
+
# WHY?
|
443
|
+
# Since any found solution has an inverse we want to maximize finding solutions that are not already found
|
444
|
+
# as an inverse. The inverse will ALWAYS be 3 one of the piece configurations that is exactly 3 rotations away
|
445
|
+
# (an odd number). Checking even vs odd then produces a higher probability of finding more pieces earlier
|
446
|
+
# in the cycle. We still need to keep checking all the permutations, but our probability of finding one will
|
447
|
+
# diminsh over time. Since we are TOLD how many to search for this lets us exit before checking all pieces
|
448
|
+
# this bennifit is very great when seeking small numbers of solutions and is 0 when looking for more than the
|
449
|
+
# maximum number
|
450
|
+
def find_top( rotation_skip)
|
451
|
+
board = blank_board
|
452
|
+
(@pieces.length-1).times do
|
453
|
+
piece = @pieces.shift
|
454
|
+
piece.masks[0].each do | mask, imask, cmask |
|
455
|
+
if ((rotation_skip += 1) % 2 == 0) then
|
456
|
+
piece.placed = mask
|
457
|
+
find( 1, 1, board | mask)
|
458
|
+
end
|
459
|
+
end
|
460
|
+
@pieces.push(piece)
|
461
|
+
end
|
462
|
+
piece = @pieces.shift
|
463
|
+
@pieces.push(piece)
|
464
|
+
end
|
465
|
+
|
466
|
+
# the normail find routine, iterates through the available pieces, checks all rotations at the current location
|
467
|
+
# and adds any boards found. depth is acheived via recursion. the overall approach is described
|
468
|
+
# here: http://www-128.ibm.com/developerworks/java/library/j-javaopt/
|
469
|
+
# parameters:
|
470
|
+
# start_location -- where to start looking for place for the next piece at
|
471
|
+
# placed -- number of pieces placed
|
472
|
+
# board -- current state of the board
|
473
|
+
#
|
474
|
+
# see in-code comments
|
475
|
+
def find( start_location, placed, board)
|
476
|
+
# find the next location to place a piece by looking for an empty bit
|
477
|
+
while board[start_location] == 1
|
478
|
+
start_location += 1
|
479
|
+
end
|
480
|
+
|
481
|
+
@pieces.length.times do
|
482
|
+
piece = @pieces.shift
|
483
|
+
piece.masks[start_location].each do | mask, imask, cmask |
|
484
|
+
if ( board & cmask == imask) then
|
485
|
+
piece.placed = mask
|
486
|
+
if (placed == 9) then
|
487
|
+
add_board
|
488
|
+
else
|
489
|
+
find( start_location + 1, placed + 1, board | mask)
|
490
|
+
end
|
491
|
+
end
|
492
|
+
end
|
493
|
+
@pieces.push(piece)
|
494
|
+
end
|
495
|
+
end
|
496
|
+
|
497
|
+
# print the board
|
498
|
+
def print_full_board( board_string)
|
499
|
+
10.times do | row |
|
500
|
+
print " " if (row % 2 == 1)
|
501
|
+
5.times do | col |
|
502
|
+
print "#{board_string[row*5 + col,1]} "
|
503
|
+
end
|
504
|
+
print "\n"
|
505
|
+
end
|
506
|
+
end
|
507
|
+
|
508
|
+
# when a board is found we "draw it" into a string and then flip that string, adding both to
|
509
|
+
# the list (hash) of solutions if they are unique.
|
510
|
+
def add_board
|
511
|
+
board_string = "99999999999999999999999999999999999999999999999999"
|
512
|
+
@all_pieces.each { | piece | piece.fill_string( board_string ) }
|
513
|
+
save( board_string)
|
514
|
+
save( board_string.reverse)
|
515
|
+
end
|
516
|
+
|
517
|
+
# adds a board string to the list (if new) and updates the current best/worst board
|
518
|
+
def save( board_string)
|
519
|
+
if (@all_boards[board_string] == nil) then
|
520
|
+
@min_board = board_string if (board_string < @min_board)
|
521
|
+
@max_board = board_string if (board_string > @max_board)
|
522
|
+
@all_boards.store(board_string,true)
|
523
|
+
@boards_found += 1
|
524
|
+
|
525
|
+
# the exit motif is a time saver. Ideally the function should return, but those tests
|
526
|
+
# take noticable time (performance).
|
527
|
+
if (@boards_found == @stop_count) then
|
528
|
+
print_results
|
529
|
+
exit(0)
|
530
|
+
end
|
531
|
+
end
|
532
|
+
end
|
533
|
+
|
534
|
+
|
535
|
+
##
|
536
|
+
## MAIN BODY :)
|
537
|
+
##
|
538
|
+
create_collector_support
|
539
|
+
@pieces = [
|
540
|
+
Piece.new( [ :nw, :ne, :east, :east ], 2),
|
541
|
+
Piece.new( [ :ne, :se, :east, :ne ], 7),
|
542
|
+
Piece.new( [ :ne, :east, :ne, :nw ], 1),
|
543
|
+
Piece.new( [ :east, :sw, :sw, :se ], 6),
|
544
|
+
Piece.new( [ :east, :ne, :se, :ne ], 5),
|
545
|
+
Piece.new( [ :east, :east, :east, :se ], 0),
|
546
|
+
Piece.new( [ :ne, :nw, :se, :east, :se ], 4),
|
547
|
+
Piece.new( [ :se, :se, :se, :west ], 9),
|
548
|
+
Piece.new( [ :se, :se, :east, :se ], 8),
|
549
|
+
Piece.new( [ :east, :east, :sw, :se ], 3)
|
550
|
+
];
|
551
|
+
|
552
|
+
@all_pieces = Array.new( @pieces)
|
553
|
+
|
554
|
+
@min_board = "99999999999999999999999999999999999999999999999999"
|
555
|
+
@max_board = "00000000000000000000000000000000000000000000000000"
|
556
|
+
@stop_count = ARGV[0].to_i || 2089
|
557
|
+
@all_boards = {}
|
558
|
+
@boards_found = 0
|
559
|
+
|
560
|
+
find_all ######## DO IT!!!
|
561
|
+
|