"BOKU"のITな日常

還暦越えの文系システムエンジニアの”BOKU”は新しいことが大好きです。

Pythonでグラフィックス:「シェルピンスキーのギャスケット」を描くクラスを実装してみました。

今回は、Pythonで「シェルピンスキーのギャスケット」を描くクラスを実装します。

割と有名なやつです。

 

ソースコードと補足説明です

 

パラメータはひとつだけで、単純なので、ソースコードの補足説明でまとめます。

まずは、クラスのソースコードです。

gr_gasket.py

import random

class Gasket():

    def __init__(self):
        self.tx = [0]
        self.ty = [0]

    def tran_1(self,p):
        x = p[0]
        y = p[1]
        x1 = 0.5*x
        y1 = 0.5*y
        return x1, y1

    def tran_2(self,p):
        x = p[0]
        y = p[1]
        x1 = 0.5*x + 0.5
        y1 = 0.5*y + 0.5
        return x1, y1

    def tran_3(self,p):
        x = p[0]
        y = p[1]
        x1 = 0.5*x + 1.0
        y1 = 0.5*y
        return x1, y1

    def get_index(self):
        prob = [0.333,0.333,0.333]
        r = random.random()
        c = 0
        sump = []
        for p in prob:
            c += p
            sump.append(c)
        for item,sp in enumerate(sump):
            if(r <= sp):
                return item
        return len(prob)-1

    def tran(self,p):
        trans = [self.tran_1,self.tran_2,self.tran_3]        
        tindex = self.get_index()
        t = trans[tindex]
        x,y = t(p)
        return x, y

    def draw(self, n=5000):
        x1 = 0
        y1 = 0
        for i in range(n):
            x1,y1 = self.tran((x1,y1))
            self.tx.append(x1)
            self.ty.append(y1)
        return self.tx,self.ty

補足説明です。 

メインの処理は「draw()」です。

def draw(self, n=5000):
    x1 = 0
    y1 = 0
    for i in range(n):
        x1,y1 = self.tran*1
        self.tx.append(x1)
        self.ty.append(y1)
    return self.tx,self.ty

座標を示す「x1、y1」を更新しながら、「n」で指定した回数繰り返します。

デフォルトでは5000回です。

x1,y1を更新する処理は「tran()」です。

def tran(self,p):
    trans = [self.tran_1,self.tran_2,self.tran_3]
    tindex = self.get_index() 
    t = trans[tindex]
    x,y = t(p)
    return x, y

pというタプルで(x1,y1)を受け取ります。

それを引数に使って、「tran_1」「tran_2」「tran_3」のいずれかのメソッドをよびだします。

どのメソッドを呼び出すかを決めるのは「get_index()」です。

def get_index(self):
    prob = [0.333,0.333,0.333]
    r = random.random()
    c = 0
    sump = []
    for p in prob:
        c += p
        sump.append(c)
    for item,sp in enumerate(sump):
        if(r <= sp):
            return item
    return len(prob)-1

 各1/3(=0.333)ずつを加算した値と、乱数を比較して、乱数が合計値よりも小さかったら、そのインデックスを返します。

まあ・・などと偉そうにいいますが、これで図形がプロットできる理屈が完全に理解できているか?・・と聞かれると、少々厳しいです。

こうしたらいいのを経験的に知ってますということで・・すいません。

あとは、入力された「x1とy1」の変換の仕方が3パターンあるだけです。

1パターン目

x1 = 0.5*x
y1 = 0.5*y

2パターン目

x1 = 0.5*x + 0.5

y1 = 0.5*y + 0.5

3パターン目

x1 = 0.5*x + 1.0

y1 = 0.5*y

このパターンをランダムに繰り返していくわけです。

 

実行してみます

 

上記のクラスを使って、図形を描くソースコードです。

gr_gasket_do.py

import matplotlib.pyplot as plt
import gr_gasket as gt

f = gt.Gasket()
x,y = f.draw()
plt.plot(x,y,'.')
plt.title('シェルピンスキーのガスケット')
plt.show()

gasket.pyの名前でクラスを定義したファイルを保存した前提です。 

インポートして、Gasketクラスオブジェクトを生成して、draw()を呼ぶだけです。

それを、matplotlibでプロットします。

なお、タイトルは日本語にしていますけど、matplotlibはデフォルトだと日本語表示できません。

まだの場合は以下の記事を参考に、日本語化します。

arakan-pgm-ai.hatenablog.com

さて、描かれる図形です。 

おおーー。

f:id:arakan_no_boku:20181205231056j:plain

これについては、パラメータで「n」を大きくしても、あまり変化がないので面白くありません。

だから。

おおーー。

シェルピンスキーがギャスケットが描けた!

以上!

みたいな感じです。

ちょっと、つまらんですね。

 

ちょっとだけ小遊びです

 

nを思い切り小さく「80」くらいにしてみます。

そして、プロットを点ではなく、直線で結ぶように変更しています。

import matplotlib.pyplot as plt
import gr_gasket as gt

f = gt.Gasket()
x,y = f.draw(n=80)
plt.plot(x,y)
plt.title('シェルピンスキーのガスケット')
plt.show()

そうすると、こんな感じになります。

f:id:arakan_no_boku:20181205232325j:plain

これが、n=250くらいにしてみると。

f:id:arakan_no_boku:20181205232806j:plain

ああ。

ランダムなんだ・・というのが、良くわかります。

出来上がった図形だけ見ると、整然と描かれたように見えますけど、途中経過でいくとこんな風にランダムにプロットされていて、それが4~5000回繰り返すと、整然とした画像に収束する・・。

実に不思議で面白い。

そう思うわけです。

ではでは。

*1:x1,y1