はじめに
Python モードで つぶやき Processing に参加している場合、 if 文の除去は重要です。 それは、if 文が存在すると複文にできないため、 if 文に関するインデントが除去できないからです。 これは、インデントのために空白文字を使用しなければならないことを意味し、 文字数圧縮(code golf)妨げることになるからです。
このページでは、以下の作品を題材として、 数式を活用して if 文を除去する方法について説明します。
#つぶやきProcessing
— Koji Saito (@KojiSaito) July 25, 2020
V=vertex
W=strokeWeight
S=stroke
def D(x,y,k):a=16*(k+1);b=48-a;beginShape();V(x,y);V(x+a,y+a);V(x+a-b,y+a+b);V(x-b,y+b);endShape(CLOSE)
size(500,500)
W(5)
S(0,150,255)
for i in range(900):D(i%30*32,i/30*32-16,i/30%2)
W(150)
S(-1)
noFill()
circle(250,250,600) pic.twitter.com/xAtTtH4FHY
模様を構成する四角形についての考察
この作品は和柄の桧を描いたものであり、 左下に伸びる長方形と右下に伸びる長方形を組み合わせたものです。
今、座標 (x,y) をそれぞれの四角形の一番上の座標とし、 長辺の長さを 32、短辺の長さを 16 とすると、 素直に実装すると、
# 左下に伸びる長方形
beginshape()
vertex(x,y)
vertex(x+16,y+16)
vertex(x-16,y+48)
vertex(x-32,y+32)
endshape(close)
# 右下に伸びる長方形
beginshape()
vertex(x,y)
vertex(x+32,y+32)
vertex(x+16,y+48)
vertex(x-16,y+16)
endshape(close)
となります。
if 文を用いた素直な実装
このような場合、例えば変数 k が 0 のときには左下に伸びる長方形、 それ以外の場合は右下に伸びる長方形と、 if 文を使って記述したりします:
beginshape()
if k==0:
vertex(x,y)
vertex(x+16,y+16)
vertex(x-16,y+48)
vertex(x-32,y+32)
else:
vertex(x,y)
vertex(x+32,y+32)
vertex(x+16,y+48)
vertex(x-16,y+16)
endshape(close)
できるだけ条件判断の影響を小さくしたい、という場合は、 もしかすると共通する vertex(x,y) を if ブロックの外に出して、
beginshape()
vertex(x,y)
if k==0:
vertex(x+16,y+16)
vertex(x-16,y+48)
vertex(x-32,y+32)
else:
vertex(x+32,y+32)
vertex(x+16,y+48)
vertex(x-16,y+16)
endshape(close)
とするかもしれません(この方が文字数が少ないし)。
if 文の除去のため抽象的に考えてみる
具体的な値ではなく、抽象的に考えると問題が単純化される場合があります。 ここでもそれを期待して、少し抽象的に考えてみましょう。 長方形が左下に伸びている、もしくは右下に伸びているという状態は、 単に長方形の辺の長さの違いに過ぎません。
なので、ここでは単に座標 (x,y) から右下に伸びる量と 左下に伸びる量に着目して考えてみます。
右下の点の座標は、
# 右下に伸びる場合の右下の座標
vertex(x+16,y+16)
# または
vertex(x+32,y+32)
でした。
一方、左下の点の座標は、
# 右下に伸びる場合の左下の座標
vertex(x-32,y+32)
# または
vertex(x-16,y+16)
でした。
つまり、右下・左下の点の座標を変数 a,b を用いて表すならば、 次のように描画処理は短縮できます:
if k==0:
a=16
b=32
else:
a=32
b=16
beginshape()
vertex(x,y)
vertex(x+a,y+a)
vertex(x+a-b,y+a+b)
vertex(x-b,y+b)
endshape(close)
抽象的に考えたおかげで、beginShape()〜endShape() で囲まれた vertex 関数の部分は 左下に伸びる長方形でも右下に伸びる長方形でも同じ処理となりました。
しかしまだ、a,b の値を決める部分に if 文が残ってしまっています。
演算による if 文の除去
上のコードにある、
if k==0:
a=16
b=32
else:
a=32
b=16
という部分は、k==0 の場合とそうでない場合について、 変数 a,b の数値を決める処理でした。
k==0 の場合とそれ以外の場合、というのは問題が複雑なので、 k=0 の場合と k=1 の場合について考えるようにします (つまり、k が 0 でない場合、ではなく、k が 1 の場合と、今度は具体的にします)。
このように考えると、この部分の問題は、
k=0 の時は、(a,b)=(16,32) となり、
k=1 の時は、(a,b)=(32,16) となる方法を考えれば良い、ということになります。
このような値をとる数式には様々なものがありますが、 例えば、a については a=16+16*k というものが考えられます。
また、b については b=32-16*k という式が考えられます。
ここまでの部分をまとめると、描画処理部のコードは以下のように if 文無しのコードとなります:
a=16+16*k
b=32-16*k
beginshape()
vertex(x,y)
vertex(x+a,y+a)
vertex(x+a-b,y+a+b)
vertex(x-b,y+b)
endshape(close)
if 文が減って、コード量も少なくなりました。
まとめ
このページでは、抽象的に物事をみることで、 if 文を除去し、コード量が減る様子をみてきました。 つぶやき Processing においては文字数削減が重要となる場合もありますし、 特に Python では if 文の圧縮はインデント用の空白文字の削減にも繋がりますので、 その効果は大きいです。
余談ですが、例として挙げたツィートでは、すでに 1 ツィート内に収まっているので、 さらなる文字数削減はしていませんが、変数 a,b の処理部分を見てみると、 さらなる文字数削減ができることに気が付きます。 例えば、実際にツィートした例では a=16*(k+1) となっていますが、 これは素直に a=16+16*k とした方が短いです:
# 文字数比較
a=16*(k+1)
a=16+16*k
逆に b=32-16*k は b=48-a とした方が短いので、
# 文字数比較
b=48-a
b=32-16*k
code golf という視点においては、
a=16+16*k
b=48-a
beginshape()
vertex(x,y)
vertex(x+a,y+a)
vertex(x+a-b,y+a+b)
vertex(x-b,y+b)
endshape(close)
とするのが良いと思います。
もちろん、もっと短縮できる書き方もあるかもしれません。 良い方法を見つけた方は是非とも私の Twitter アカウント @KojiSaito https://twitter.com/KojiSaito まで連絡していただけると嬉しいです。