Squeak Smalltalk でマンデルブロ集合 その3(1トゥート版)

マストドンでは1トゥートが 500文字までで、そのインスタンスのひとつの Qiitadon ではコードブロックを指定できることから、短めのプログラムならシンタックスハイライトを使ってトゥートすることが可能です。id:squeakerさんが提示された配列を積算する方法(特にコメントアウトされた素朴な実装の方)を見ていて、Squeak Smalltalk の持つ配列同士の演算を用いれば短く書くこともできるのではないかと思いついたのでトライしてみました。

http://squab.no-ip.com/collab/uploads/mandelbrot07.png


実はちょっと間違いがあって訂正済みの版がこちら。

|s h r x y c q m f i b|
s:=300.
h:=s//2.
s:=s roundUpTo:32.
r:=(0to:s-1)-h/h.
x:=(Array new:s withAll:r)concatenation.
y:=r gather:[:v|Array new:s withAll:v].
c:={x-0.5. y-0.0}.
q:=[:p||re im|re:=p at:1. im:=p at:2. {re*re-(im*im). re*im*2}].
m:=(1to:100)inject:c*0into:[:z :j|(q value:z)+c].
f:=Form extent:(h*2)asPoint.
i:=[:w|Integer readFrom:(w collect:[:v|(v<4)asBit asHexDigit])readStream base:2].
b:=(m*m)sum groupsOf:32atATimeCollect:i.
f bits:(b as:Bitmap);display


そしてスペース等の省略を戻し、少しだけ速度を意識して FloatArray 等若干の変更や拡張を施したのがこちらです。

| size half range xs ys cs squared time map form asInt32 bits |
size := 200.
half := size//2.
size := size roundUpTo: 32.
range :=  (0 to: size-1) - half / half.
xs := (Array new: size withAll: range) concatenation asFloatArray.
ys := (range gather: [:x | Array new: size withAll: x]) asFloatArray.
cs := {xs - 0.5. ys}.
squared := [:pair | {pair first squared - pair second squared. pair first * pair second * 2}].
time := [map := (1 to: 100) inject: cs * 0 into: [:zs :idx | (squared value: zs) + cs]] timeToRun.
form := Form extent: (half * 2) asPoint.
asInt32 := [:vs | Integer readFrom: (vs collect: [:v | (v < 4) asBit asHexDigit]) readStream base: 2].
bits := (map * map) sum groupsOf: 32 atATimeCollect: asInt32.
form bits: (bits as: Bitmap); display.
time. "=> 3033 "


ビットマップを生成してフォームに食わせているところがこだわりだったのですが、普通にピクセルごとに値を置いた方がずっと短く書けるのでなんだかなーと言う感じですね。^^;

|s h r x y c q m f|
s:=300.
h:=s//2.
r:=(0to:s-1)-h/h.
x:=(Array new:s withAll:r)concatenation.
y:=r gather:[:v|Array new:s withAll:v].
c:={x-0.5. y-0.0}.
q:=[:p||re im|re:=p at:1. im:=p at:2. {re*re-(im*im). re*im*2}].
m:=(1to:100)inject:c*0into:[:z :j|(q value:z)+c].
f:=Form extent:(h*2)asPoint.
(m*m)sum doWithIndex:[:v :i|f pixelValueAt:i\\s@(i//s)put:(v<4)asBit]. 
f display


ついでと言ってはなんですが、グレイスケール版にも挑戦してみました。もし #< や #asBit などが加減乗除などの二項セレクターと同様に Colletion のダブルディスパッチに対応していたら map := map + (zs squared sum asArray < 4) asBit などと速度はともかくもっと簡潔に書けそうなものなのですが、現状ではかなり見栄えの悪いものになってしまっていて残念です。

| size half range xs ys cs zs squared time map form bits |
size := 300.
half := size//2.
range :=  (0 to: size-1) - half / half * 1.3.
xs := (Array new: size withAll: range) concatenation asFloatArray.
ys := (range gather: [:x | Array new: size withAll: x]) asFloatArray.
cs := {xs - 0.75. ys}.
zs := cs * 0.
map := Array new: xs size withAll: 0.
squared := [:pair | {pair first squared - pair second squared. pair first * pair second * 2}].
time := [
   100 timesRepeat: [
      zs := squared value: zs + cs.
      map := map + (zs squared sum asArray collect: [:v | (v < 4) asBit]).
   ]
] timeToRun.
form := Form extent: (half * 2) asPoint depth: 32.
bits := map collect: [:v | (Color gray: 1- (v/100) sqrt) pixelWordForDepth: 32].
form bits: (bits as: Bitmap); display.
time. "=> 11591 "

http://squab.no-ip.com/collab/uploads/mandelbrot06.png


追記
最後に、速度を気にして FloatArray を使うなどしないのであれば、実部と虚部それぞれに配列を用意せずとも複素数の配列ひとつで済ませてもよいのではないかと書いてみたらこんな感じになりました。

| size half range cs form map time zs |
size := 300.
half := size//2.
range := (0 to: size-1) - half / half * 1.3.
cs := ((Array new: size withAll: range) + (range collect: #i) - 0.75) concatenation.
zs := cs * 0.
map := cs abs * 0.
time := [
   100 timesRepeat: [
      zs := zs squared + cs.
      map := map + (zs abs collect: [:v | (v < 2) asBit]).
   ]
] timeToRun.
form := Form extent: (half * 2) asPoint depth: 32.
map doWithIndex: [:v :idx | form colorAt: idx\\size@(idx//size) put: (Color gray: 1- (v/100) sqrt)].
form display.
time. "=> 15789 "