問題:http://nabetani.sakura.ne.jp/hena/ordf05rotblo/
実装リンク集:http://qiita.com/Nabetani/items/a6ed674166a151fd0a0a
Smalltalk 処理系は Pharo を使いました。Squeak でもコメントの通り一部修正すれば動きます。入力と出力をスペースで区切ったファイル(text.txt)を用意し、それへのパスも 'path\to\test.txt' 部分で指定する必要があります。
Pharo なら Playground、Squeak なら Workspace にコピペして前述の準備を終えたら、右クリックメニューから do it すると実行できます。text.txt の入力と望ましい出力の組を #assert: しているので出力はありません(不一致がなければそのまま終了します)が、solve value: 'a:00000/00110/00100/00100/00000' などと書いた式を右クリックメニューから print it することで個別の結果も得られます。
コード
solve := [:data |
| map rotate result |
map := (data allButFirst: 2) splitOn: '/'. "use #splitBy: on Squeak"
rotate := data first caseOf: {
[$a] -> [[:pt | pt rotateBy: #right centerAt: 2@3]].
[$b] -> [[:pt | (pt rotateBy: #right centerAt: 2.5@2.5) asIntegerPoint]]}.
result := (Array new: 5 withAll: '00000') deepCopy.
[:exit | map doWithIndex: [:str :y | str doWithIndex: [:chr :x |
((map at: y) at: x) == $1 ifTrue: [
| pos |
pos := rotate value: x@y.
((1@1 extent: 5@5) containsPoint: pos) ifFalse: [result := nil. exit value].
(result at: pos y) at: pos x put: $1
]
]]] valueWithExit.
result ifNil: ['-'] ifNotNil: [result joinUsing: '/'] "use #joinSeparatedBy: on Squeak"
].
FileStream oldFileNamed: 'path\to\test.txt' do: [:file |
[file atEnd] whileFalse: [
| pair |
pair := file nextLine splitOn: ' '. "use #splitBy: on Squeak"
self assert: [(solve value: pair first) = pair second]
]
]
a:00000/00110/00100/00100/00000 00000/00000/00000/11100/00100
b:00000/00000/00000/00011/00011 -
a:00000/00000/00000/00011/00011 -
b:00000/00000/00100/00000/00000 00000/00000/01000/00000/00000
a:00000/00000/00100/00000/00000 00000/00000/00000/01000/00000
...
解説
与えられたデータの最初の文字が a か b かで rotate の処理を切り替えています。なお、$x は Smalltalk の文字オブジェクトのリテラルです。
rotate := data first caseOf: {
[$a] -> [[:pt | pt rotateBy: #right centerAt: 2@3]].
[$b] -> [[:pt | (pt rotateBy: #right centerAt: 2.5@2.5) asIntegerPoint]]}.
回転後のブロックの位置は、そのブロックの位置を左上原点の x-y 座標に見立てつつ、Smalltalk 組み込みの Point クラスの #rotateBy:centerAt: メソッドを使用することで手抜きをしています。ブロックが中心となる a の場合は整数座標、境界が中心になる b では 0.5 を減ずることで適切な結果が得られます。
結果を書き込む配列は '00000' を要素に持つ、サイズ 5 の配列を deepCopy して同一オブジェクトが 5個ある状態を解消しつつ作りました。
result := (Array new: 5 withAll: '00000') deepCopy.
回転の先の座標が 5×5 の範囲をこえなければ result の対応する場所に $1 を書き込みます。はみ出した場合は result := nil し、exit value によって直ちにループを抜けます。
| pos |
pos := rotate value: x@y.
((1@1 extent: 5@5) containsPoint: pos) ifFalse: [result := nil. exit value].
(result at: pos y) at: pos x put: $1
参考まで BlockClosure>>#valueWithExit の定義はこのようになっていて、ある種の継続渡しスタイル (CPS, Continuation-passing style) でループを抜けられるしくみになっています。
BlockClosure >> valueWithExit
self value: [ ^nil ]