勇気ある加藤の猛り

元気はないけど、勇気があります

Pythonで数字を反時計回りの螺旋状に表示するアルゴリズム

こんにちは勇気ある加藤です。

指定された巻き数の、渦巻きを表示するプログラムをPythonで書きました。
ちなみに数字をらせん状に並べると、素数の分布に規則性があるとかないとかというのが、下図の「ウラムの螺旋」になりますが、ちょっと何言ってんのか分かんないので、このブログでは数字を並べるまでにとどめておきます。


https://upload.wikimedia.org/wikipedia/commons/thumb/1/1d/Ulam_spiral_howto_all_numbers.svg/266px-Ulam_spiral_howto_all_numbers.svg.png
https://upload.wikimedia.org/wikipedia/commons/thumb/3/3c/Ulam_spiral_howto_primes_only.svg/266px-Ulam_spiral_howto_primes_only.svg.png

wikipedia: ウラムの螺旋



使用するモジュールについて

itertools

itertoolsモジュールはイテレータに関するさまざまなコンテナを提供してくれる、めちゃくそ便利なモジュールです。すべてのPythonistaにとっての頼れる兄貴的存在ですね。今回は循環リストitertools.cycleを使うためにインポートします。

math

mathモジュールは、math.log10で数値の桁数をもとめるためにインポートしています。



実装

import math
import itertools



def print_spiral(spiral):
    """
    螺旋の二次元リストを整形して表示する

    args: [[4, 3, 2], [5, 0, 1], [6, 7, 8]]
    output:
        4 3 2
        5 0 1
        6 7 8
    """

    # 最大値の桁数を求める
    end = spiral[-1][-1]
    digit = int(math.log10(end) + 1)

    # 桁数分だけ右詰めする書式をつくる
    base = "{: >" + str(digit) + "} "

    for i in spiral:
        for j in i:
            print(base.format(j), end="")
        print("\n", end="")



if __name__ == "__main__":
    LOOP = 4
    WIDTH = (2*LOOP) + 1

    E = (1, 0)
    N = (0, -1)
    W = (-1, 0)
    S = (0, 1)
    DIRECTION = itertools.cycle((E, N, W, S))

    x = LOOP
    y = LOOP
    step = 1    # 進んだ距離
    corner = 1  # まがり角の位置

    # 二次元リストを初期化
    spiral = []
    for i in range(WIDTH):
        spiral.append([0 for j in range(WIDTH)])

    for i in range(WIDTH * WIDTH):
        # まがり角に到達したら方向転換
        if step >= corner:
            step = 1
            direction = next(DIRECTION)
            dx, dy = direction

            # X方向に進むとき、まがり角が遠くなる
            if direction == E or direction == W:
                corner += 1
                
        spiral[y][x] = i
        step += 1
        x += dx
        y += dy

    # 螺旋を表示する
    print_spiral(spiral)


LOOPの値を変えると巻き数が変わります。stepcornerという変数が、このプログラムのみそで、以下の画像のように、ある角から次の角までの距離は、東方向と西方向へ方向転換する際に増加しています。そのため現在の移動量をstep、次の曲がり角までの距離をcornerとして比較しています。
f:id:katoh4u:20180324194815p:plain



まとめ

私は過去にプログラムクイズにハマっていた時期がありまして、今回の問題も過去のソースをあさっていたら出てきたものです。最初に書いた時から3~4年たっているので、さすがに前よりはうまく書けるだろうと思い書き直してみたのですが、書き直したコードも全然よくできたアルゴリズムではないですね。まあ要件は満たしているし、私は自分に優しいタイプなのでこれで良しとしておきます。さよなら