chrislo-sourceclassifier 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (326) hide show
  1. data/Manifest +325 -0
  2. data/README.textile +51 -0
  3. data/Rakefile +64 -0
  4. data/examples/example.rb +22 -0
  5. data/lib/sourceclassifier.rb +22 -0
  6. data/lib/trainer.rb +51 -0
  7. data/sourceclassifier.gemspec +35 -0
  8. data/sources/gcc/ackermann.gcc-2.gcc +93 -0
  9. data/sources/gcc/ackermann.gcc-3.gcc +20 -0
  10. data/sources/gcc/ary.gcc +40 -0
  11. data/sources/gcc/binarytrees.gcc +136 -0
  12. data/sources/gcc/binarytrees.gcc-2.gcc +140 -0
  13. data/sources/gcc/binarytrees.gcc-3.gcc +134 -0
  14. data/sources/gcc/chameneos.gcc +117 -0
  15. data/sources/gcc/chameneos.gcc-2.gcc +134 -0
  16. data/sources/gcc/chameneos.gcc-3.gcc +120 -0
  17. data/sources/gcc/chameneosredux.gcc +306 -0
  18. data/sources/gcc/echo.gcc +144 -0
  19. data/sources/gcc/except.gcc +52 -0
  20. data/sources/gcc/fannkuch.gcc +105 -0
  21. data/sources/gcc/fannkuch.gcc-2.gcc +94 -0
  22. data/sources/gcc/fasta.gcc +158 -0
  23. data/sources/gcc/fasta.gcc-2.gcc +132 -0
  24. data/sources/gcc/fasta.gcc-3.gcc +145 -0
  25. data/sources/gcc/fasta.gcc-4.gcc +141 -0
  26. data/sources/gcc/fibo.gcc +20 -0
  27. data/sources/gcc/harmonic.gcc +22 -0
  28. data/sources/gcc/hash.gcc +31 -0
  29. data/sources/gcc/hash2.gcc +38 -0
  30. data/sources/gcc/heapsort.gcc +72 -0
  31. data/sources/gcc/hello.gcc-2.gcc +12 -0
  32. data/sources/gcc/hello.gcc-3.gcc +20 -0
  33. data/sources/gcc/knucleotide.gcc +173 -0
  34. data/sources/gcc/knucleotide.gcc-2.gcc +168 -0
  35. data/sources/gcc/lists.gcc +226 -0
  36. data/sources/gcc/magicsquares.gcc +378 -0
  37. data/sources/gcc/mandelbrot.gcc +84 -0
  38. data/sources/gcc/mandelbrot.gcc-2.gcc +63 -0
  39. data/sources/gcc/mandelbrot.gcc-3.gcc +91 -0
  40. data/sources/gcc/mandelbrot.gcc-4.gcc +78 -0
  41. data/sources/gcc/matrix.gcc +66 -0
  42. data/sources/gcc/message.gcc-2.gcc +72 -0
  43. data/sources/gcc/message.gcc-3.gcc +64 -0
  44. data/sources/gcc/methcall.gcc +89 -0
  45. data/sources/gcc/moments.gcc +120 -0
  46. data/sources/gcc/nbody.gcc +142 -0
  47. data/sources/gcc/nbody.gcc-2.gcc +143 -0
  48. data/sources/gcc/nbody.gcc-3.gcc +142 -0
  49. data/sources/gcc/nestedloop.gcc +25 -0
  50. data/sources/gcc/nsieve.gcc +35 -0
  51. data/sources/gcc/nsievebits.gcc-2.gcc +37 -0
  52. data/sources/gcc/nsievebits.gcc-3.gcc +53 -0
  53. data/sources/gcc/objinst.gcc +95 -0
  54. data/sources/gcc/partialsums.gcc +86 -0
  55. data/sources/gcc/partialsums.gcc-3.gcc +81 -0
  56. data/sources/gcc/partialsums.gcc-4.gcc +68 -0
  57. data/sources/gcc/pidigits.gcc +105 -0
  58. data/sources/gcc/primes.gcc +75 -0
  59. data/sources/gcc/prodcons.gcc +86 -0
  60. data/sources/gcc/random.gcc +29 -0
  61. data/sources/gcc/raytracer.gcc +125 -0
  62. data/sources/gcc/raytracer.gcc-2.gcc +181 -0
  63. data/sources/gcc/recursive.gcc +68 -0
  64. data/sources/gcc/recursive.gcc-2.gcc +55 -0
  65. data/sources/gcc/regexdna.gcc-2.gcc +126 -0
  66. data/sources/gcc/regexmatch.gcc +136 -0
  67. data/sources/gcc/revcomp.gcc +85 -0
  68. data/sources/gcc/revcomp.gcc-2.gcc +88 -0
  69. data/sources/gcc/revcomp.gcc-4.gcc +71 -0
  70. data/sources/gcc/reversefile.gcc +103 -0
  71. data/sources/gcc/reversefile.gcc-2.gcc +56 -0
  72. data/sources/gcc/sieve.gcc +34 -0
  73. data/sources/gcc/spectralnorm.gcc +54 -0
  74. data/sources/gcc/spellcheck.gcc +72 -0
  75. data/sources/gcc/spellcheck.gcc-2.gcc +61 -0
  76. data/sources/gcc/strcat.gcc +38 -0
  77. data/sources/gcc/sumcol.gcc-2.gcc +98 -0
  78. data/sources/gcc/sumcol.gcc-3.gcc +22 -0
  79. data/sources/gcc/sumcol.gcc-4.gcc +18 -0
  80. data/sources/gcc/sumcol.gcc-5.gcc +32 -0
  81. data/sources/gcc/takfp.gcc +23 -0
  82. data/sources/gcc/tcp-stream.gcc +122 -0
  83. data/sources/gcc/tcpecho.gcc +122 -0
  84. data/sources/gcc/tcpecho.gcc-2.gcc +136 -0
  85. data/sources/gcc/tcprequest.gcc +122 -0
  86. data/sources/gcc/threadring.gcc +73 -0
  87. data/sources/gcc/wc.gcc +52 -0
  88. data/sources/gcc/wc.gcc-2.gcc +40 -0
  89. data/sources/gcc/wc.gcc-3.gcc +46 -0
  90. data/sources/gcc/wordfreq.gcc-2.gcc +85 -0
  91. data/sources/perl/ackermann.perl +28 -0
  92. data/sources/perl/ackermann.perl-2.perl +25 -0
  93. data/sources/perl/ackermann.perl-3.perl +20 -0
  94. data/sources/perl/ackermann.perl-4.perl +18 -0
  95. data/sources/perl/ary.perl +25 -0
  96. data/sources/perl/ary.perl-2.perl +23 -0
  97. data/sources/perl/binarytrees.perl +66 -0
  98. data/sources/perl/binarytrees.perl-2.perl +71 -0
  99. data/sources/perl/chameneos.perl +67 -0
  100. data/sources/perl/echo.perl +81 -0
  101. data/sources/perl/except.perl +73 -0
  102. data/sources/perl/fannkuch.perl +44 -0
  103. data/sources/perl/fannkuch.perl-2.perl +38 -0
  104. data/sources/perl/fasta.perl +112 -0
  105. data/sources/perl/fasta.perl-2.perl +135 -0
  106. data/sources/perl/fasta.perl-4.perl +122 -0
  107. data/sources/perl/fibo.perl +15 -0
  108. data/sources/perl/fibo.perl-2.perl +0 -0
  109. data/sources/perl/fibo.perl-3.perl +0 -0
  110. data/sources/perl/harmonic.perl +7 -0
  111. data/sources/perl/hash.perl +23 -0
  112. data/sources/perl/hash.perl-2.perl +17 -0
  113. data/sources/perl/hash.perl-3.perl +24 -0
  114. data/sources/perl/hash2.perl +16 -0
  115. data/sources/perl/heapsort.perl +65 -0
  116. data/sources/perl/heapsort.perl-2.perl +0 -0
  117. data/sources/perl/hello.perl +5 -0
  118. data/sources/perl/knucleotide.perl-2.perl +30 -0
  119. data/sources/perl/lists.perl +48 -0
  120. data/sources/perl/mandelbrot.perl-2.perl +32 -0
  121. data/sources/perl/matrix.perl +59 -0
  122. data/sources/perl/matrix.perl-2.perl +0 -0
  123. data/sources/perl/matrix.perl-3.perl +0 -0
  124. data/sources/perl/message.perl +27 -0
  125. data/sources/perl/methcall.perl +66 -0
  126. data/sources/perl/moments.perl +44 -0
  127. data/sources/perl/nbody.perl +108 -0
  128. data/sources/perl/nestedloop.perl +28 -0
  129. data/sources/perl/nsieve.perl-2.perl +41 -0
  130. data/sources/perl/nsieve.perl-4.perl +43 -0
  131. data/sources/perl/nsievebits.perl +37 -0
  132. data/sources/perl/objinst.perl +73 -0
  133. data/sources/perl/partialsums.perl-3.perl +31 -0
  134. data/sources/perl/pidigits.perl +52 -0
  135. data/sources/perl/pidigits.perl-2.perl +47 -0
  136. data/sources/perl/process.perl +50 -0
  137. data/sources/perl/prodcons.perl +47 -0
  138. data/sources/perl/random.perl-4.perl +17 -0
  139. data/sources/perl/recursive.perl-2.perl +57 -0
  140. data/sources/perl/regexdna.perl +48 -0
  141. data/sources/perl/regexdna.perl-2.perl +43 -0
  142. data/sources/perl/regexdna.perl-3.perl +50 -0
  143. data/sources/perl/regexdna.perl-4.perl +49 -0
  144. data/sources/perl/regexdna.perl-5.perl +42 -0
  145. data/sources/perl/regexdna.perl-6.perl +43 -0
  146. data/sources/perl/regexmatch.perl +35 -0
  147. data/sources/perl/revcomp.perl-2.perl +34 -0
  148. data/sources/perl/reversefile.perl +8 -0
  149. data/sources/perl/reversefile.perl-2.perl +0 -0
  150. data/sources/perl/reversefile.perl-3.perl +0 -0
  151. data/sources/perl/sieve.perl +23 -0
  152. data/sources/perl/spectralnorm.perl-2.perl +54 -0
  153. data/sources/perl/spellcheck.perl +24 -0
  154. data/sources/perl/strcat.perl +13 -0
  155. data/sources/perl/strcat.perl-2.perl +0 -0
  156. data/sources/perl/sumcol.perl +8 -0
  157. data/sources/perl/takfp.perl +23 -0
  158. data/sources/perl/takfp.perl-3.perl +20 -0
  159. data/sources/perl/tcpecho.perl +61 -0
  160. data/sources/perl/tcprequest.perl +61 -0
  161. data/sources/perl/tcpstream.perl +61 -0
  162. data/sources/perl/threadring.perl +55 -0
  163. data/sources/perl/threadring.perl-2.perl +43 -0
  164. data/sources/perl/wc.perl +20 -0
  165. data/sources/perl/wc.perl-2.perl +14 -0
  166. data/sources/perl/wordfreq.perl +22 -0
  167. data/sources/perl/wordfreq.perl-3.perl +0 -0
  168. data/sources/perl/wordfreq.perl3.perl +0 -0
  169. data/sources/python/ackermann.python +21 -0
  170. data/sources/python/ary.python +19 -0
  171. data/sources/python/binarytrees.python +39 -0
  172. data/sources/python/binarytrees.python-3.python +44 -0
  173. data/sources/python/chameneos.python-6.python +73 -0
  174. data/sources/python/chameneosredux.python +126 -0
  175. data/sources/python/chameneosredux.python-2.python +122 -0
  176. data/sources/python/dispatch.python +176 -0
  177. data/sources/python/dispatch.python-2.python +136 -0
  178. data/sources/python/echo.python +64 -0
  179. data/sources/python/except.python +62 -0
  180. data/sources/python/fannkuch.python +50 -0
  181. data/sources/python/fannkuch.python-2.python +54 -0
  182. data/sources/python/fasta.python-2.python +79 -0
  183. data/sources/python/fibo.python +17 -0
  184. data/sources/python/fibo.python-2.python +0 -0
  185. data/sources/python/fibo.python-3.python +0 -0
  186. data/sources/python/harmonic.python-2.python +9 -0
  187. data/sources/python/hash.python +21 -0
  188. data/sources/python/hash.python-2.python +0 -0
  189. data/sources/python/hash2.python +30 -0
  190. data/sources/python/heapsort.python-3.python +66 -0
  191. data/sources/python/hello.python +5 -0
  192. data/sources/python/implicitode.python +231 -0
  193. data/sources/python/knucleotide.python +55 -0
  194. data/sources/python/lists.python +44 -0
  195. data/sources/python/magicsquares.python +145 -0
  196. data/sources/python/mandelbrot.python +44 -0
  197. data/sources/python/mandelbrot.python-2.python +35 -0
  198. data/sources/python/mandelbrot.python-3.python +46 -0
  199. data/sources/python/matrix.python +34 -0
  200. data/sources/python/matrix.python-2.python +23 -0
  201. data/sources/python/message.python +24 -0
  202. data/sources/python/message.python-2.python +20 -0
  203. data/sources/python/message.python-3.python +19 -0
  204. data/sources/python/meteor.python +210 -0
  205. data/sources/python/meteor.python-2.python +192 -0
  206. data/sources/python/methcall.python +51 -0
  207. data/sources/python/moments.python +65 -0
  208. data/sources/python/nbody.python +123 -0
  209. data/sources/python/nbody.python-2.python +120 -0
  210. data/sources/python/nestedloop.python +24 -0
  211. data/sources/python/nsieve.python +27 -0
  212. data/sources/python/nsieve.python-2.python +23 -0
  213. data/sources/python/nsieve.python-4.python +25 -0
  214. data/sources/python/nsievebits.python +27 -0
  215. data/sources/python/nsievebits.python-2.python +43 -0
  216. data/sources/python/objinst.python +53 -0
  217. data/sources/python/partialsums.python +37 -0
  218. data/sources/python/partialsums.python-2.python +35 -0
  219. data/sources/python/partialsums.python-3.python +48 -0
  220. data/sources/python/pidigits.python +38 -0
  221. data/sources/python/pidigits.python-3.python +63 -0
  222. data/sources/python/pidigits.python-4.python +24 -0
  223. data/sources/python/process.python +51 -0
  224. data/sources/python/process.python-2.python +133 -0
  225. data/sources/python/prodcons.python +51 -0
  226. data/sources/python/prodcons.python-2.python +0 -0
  227. data/sources/python/random.python +27 -0
  228. data/sources/python/raytracer.python +203 -0
  229. data/sources/python/recursive.python +35 -0
  230. data/sources/python/regexdna.python +39 -0
  231. data/sources/python/regexdna.python-2.python +34 -0
  232. data/sources/python/regexmatch.python +36 -0
  233. data/sources/python/revcomp.python-3.python +31 -0
  234. data/sources/python/reversefile.python +13 -0
  235. data/sources/python/reversefile.python-2.python +0 -0
  236. data/sources/python/reversefile.python-3.python +0 -0
  237. data/sources/python/sieve.python +50 -0
  238. data/sources/python/spectralnorm.python-2.python +36 -0
  239. data/sources/python/spellcheck.python +17 -0
  240. data/sources/python/strcat.python +35 -0
  241. data/sources/python/strcat.python-2.python +0 -0
  242. data/sources/python/sumcol.python-2.python +0 -0
  243. data/sources/python/sumcol.python-3.python +0 -0
  244. data/sources/python/takfp.python +19 -0
  245. data/sources/python/tcpecho.python +67 -0
  246. data/sources/python/tcprequest.python +67 -0
  247. data/sources/python/tcpstream.python +67 -0
  248. data/sources/python/threadring.python +47 -0
  249. data/sources/python/threadring.python-2.python +40 -0
  250. data/sources/python/threadring.python-3.python +34 -0
  251. data/sources/python/wc.python-2.python +19 -0
  252. data/sources/python/wordfreq.python +43 -0
  253. data/sources/python/wordfreq.python-2.python +0 -0
  254. data/sources/python/wordfreq.python-3.python +28 -0
  255. data/sources/python/wordfreq.python-4.python +38 -0
  256. data/sources/python/wordfreq.python-5.python +39 -0
  257. data/sources/ruby/ackermann.ruby +17 -0
  258. data/sources/ruby/ackermann.ruby-5.ruby +153 -0
  259. data/sources/ruby/ary.ruby +22 -0
  260. data/sources/ruby/binarytrees.ruby-2.ruby +55 -0
  261. data/sources/ruby/chameneos.ruby-2.ruby +71 -0
  262. data/sources/ruby/dispatch.ruby +114 -0
  263. data/sources/ruby/echo.ruby +41 -0
  264. data/sources/ruby/except.ruby +61 -0
  265. data/sources/ruby/except.ruby-2.ruby +61 -0
  266. data/sources/ruby/fannkuch.ruby +42 -0
  267. data/sources/ruby/fasta.ruby +81 -0
  268. data/sources/ruby/fibo.ruby +15 -0
  269. data/sources/ruby/harmonic.ruby-2.ruby +15 -0
  270. data/sources/ruby/hash.ruby +19 -0
  271. data/sources/ruby/hash2.ruby +23 -0
  272. data/sources/ruby/heapsort.ruby +55 -0
  273. data/sources/ruby/hello.ruby +6 -0
  274. data/sources/ruby/knucleotide.ruby-2.ruby +44 -0
  275. data/sources/ruby/lists.ruby +46 -0
  276. data/sources/ruby/mandelbrot.ruby-3.ruby +63 -0
  277. data/sources/ruby/matrix.ruby +40 -0
  278. data/sources/ruby/matrix.ruby-2.ruby +30 -0
  279. data/sources/ruby/message.ruby +29 -0
  280. data/sources/ruby/message.ruby-2.ruby +24 -0
  281. data/sources/ruby/meteor.ruby +386 -0
  282. data/sources/ruby/meteor.ruby-2.ruby +561 -0
  283. data/sources/ruby/methcall.ruby +58 -0
  284. data/sources/ruby/methcall.ruby-2.ruby +54 -0
  285. data/sources/ruby/moments.ruby +64 -0
  286. data/sources/ruby/nbody.ruby-2.ruby +145 -0
  287. data/sources/ruby/nestedloop.ruby +22 -0
  288. data/sources/ruby/nsieve.ruby +36 -0
  289. data/sources/ruby/nsieve.ruby-2.ruby +25 -0
  290. data/sources/ruby/nsievebits.ruby-2.ruby +42 -0
  291. data/sources/ruby/objinst.ruby +58 -0
  292. data/sources/ruby/partialsums.ruby +39 -0
  293. data/sources/ruby/pidigits.ruby +92 -0
  294. data/sources/ruby/pidigits.ruby-2.ruby +109 -0
  295. data/sources/ruby/prodcons.ruby +41 -0
  296. data/sources/ruby/random.ruby +17 -0
  297. data/sources/ruby/recursive.ruby-2.ruby +53 -0
  298. data/sources/ruby/regexdna.ruby +32 -0
  299. data/sources/ruby/regexdna.ruby-2.ruby +38 -0
  300. data/sources/ruby/regexmatch.ruby +33 -0
  301. data/sources/ruby/revcomp.ruby +28 -0
  302. data/sources/ruby/reversefile.ruby +7 -0
  303. data/sources/ruby/sieve.ruby +30 -0
  304. data/sources/ruby/spectralnorm.ruby +48 -0
  305. data/sources/ruby/spellcheck.ruby +18 -0
  306. data/sources/ruby/spellcheck.ruby-2.ruby +0 -0
  307. data/sources/ruby/strcat.ruby +12 -0
  308. data/sources/ruby/strcat.ruby-2.ruby +12 -0
  309. data/sources/ruby/sumcol.ruby +12 -0
  310. data/sources/ruby/sumcol.ruby-2.ruby +5 -0
  311. data/sources/ruby/takfp.ruby +15 -0
  312. data/sources/ruby/tcpecho.ruby +45 -0
  313. data/sources/ruby/tcprequest.ruby +45 -0
  314. data/sources/ruby/tcpstream.ruby +45 -0
  315. data/sources/ruby/threadring.ruby +61 -0
  316. data/sources/ruby/threadring.ruby-2.ruby +33 -0
  317. data/sources/ruby/wc.ruby +15 -0
  318. data/sources/ruby/wordfreq.ruby +17 -0
  319. data/sources/ruby/wordfreq.ruby2.ruby +0 -0
  320. data/test/fixtures/sources/gcc/ackermann.gcc-2.gcc +93 -0
  321. data/test/fixtures/sources/python/ackermann.python +21 -0
  322. data/test/fixtures/sources/ruby/ackermann.ruby +17 -0
  323. data/test/test_source_classifier.rb +40 -0
  324. data/test/test_trainer.rb +34 -0
  325. data/trainer.bin +1193 -0
  326. 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
+