LoginSignup
1
0

More than 5 years have passed since last update.

オフラインリアルタイムどう書く F05 を Pharo Smalltalk で

Last updated at Posted at 2017-05-30

問題: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]
   ]
]
test.txt
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 ]
1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0