📘

コマンドラインでバブルソートを可視化してみた

に公開

今回はPythonを使ってバブルソートをコマンドラインで可視化しながらソートするコードを作ってみました。

バブルソートとは

バブルソートとはリスト内のアイテムをソートするためのアルゴリズムの一つで最もシンプルなものとなります。計算ロジックは以下になります。

  1. インデックスi=0、上限インデックスupper_iを`リストの要素数-1``で初期化する
  2. iとi+1の要素の大小を比較し、iの値の方が大きかった場合iとi+1の値を入れ替える
  3. iの値を1つ増やし2を実行する
  4. iの値がupper_iと一致したらi=0に設定してupper_idを1減らす
  5. upper_idが0になるまで繰り返す

となります。要は、隣り合う値をひたすら比較していけば最終的にはソートできるよねという考え方になります。確かに可能ですが計算効率はかなり悪く、要素数をnとするとO(n^2)の計算時間を必要とします。なお、O(n^2)はビッグオー記法による最悪計算時間の表記であり、ここでは詳細は解説しませんが、要素数が多くなればなるほどどんどん計算効率がひどくなると思ってもらっていいと思います。

Pythonで実装してみる

バブルソートの実装

まずはバブルソートの実装をしてみます。

import time

def bubble_sort(array):
    length = len(array)
    for i in range(length):
        for j in range(length - i -1):
            if array[j] > array[j+1]:
                array[j], array[j+1] = array[j+1], array[j]
                show_graph()
                time.sleep(sleep)

なお、show_graph関数は後ほど説明shます。二重のfor分で先ほど書いたロジックを愚直に実装しています。一周するごとに確認する要素の上限インデックスが1つずつ少なくなっていくようになっています。値のスワップは以下のようにして簡単に書くことができます。

array[j], array[j+1] = array[j+1], array[j]

配列の可視化ロジックの実装

さて、いよいよこのブログの味噌である、配列要素をターミナル上で可視化する方法を実装します。まずはコードの全体像をお見せします。

start_row = 1
start_col = 4

def move_cursor(row, col):
    print(f"\033[{row};{col}H", end='')


def clear_screen():
    print("\033[2J\033[H", end='')

  
 def show_graph(data=array):
     for r in range(item_num):
         show_sign = ["*" if (item_num-r) <= item else " " for item in data]
         move_cursor(start_row + r, start_col)
         left_text = f"{item_num-r}".zfill(2) + "|"
         # print(left_text + ''.join(['*' for _ in range(10)]))
         print(left_text + ''.join(show_sign))

まずclear_screen関数は、プログラム開始時に一度だけ呼び出すのですが、これを実行すると画面上に表示された全ての文字がクリアされ、カーソルが画面左上に移動します。

次にmove_cursor関数は、指定した場所にカーソルを移動させる機能になります。これを利用することで1行ごとにリストを表示していく機能が実現できます。

最後に、リストの値の表示方法について解説します。for文で1行ずつリストの値を可視化していくのですが、その行のどの場所に値があるかは以下の部分で計算しています。各インデックスの値を縦向きに表示しているので、指定した値より大きい値が設定されている列には*が、そうでない場所は に設定しています。

show_sign = ["*" if (item_num-r) <= item else " " for item in data]

実行してみる

最終的なコードの完成形は以下になります。clickを使って引数として要素数と描画のsleep時間を指定するようにしています。また、入力配列はrandom.shuffleを使ってシャッフルしています。

import click
from random import shuffle
import time

def move_cursor(row, col):
    print(f"\033[{row};{col}H", end='')

def clear_screen():
    print("\033[2J\033[H", end='')



@click.command()
@click.argument("item_num", type=int)
@click.option("-s", "--sleep", type=float, default=0.01)
def main(item_num, sleep):
    # 初期化
    clear_screen()
    
    array = [i for i in range(1, item_num + 1)]
    shuffle(array)
    
    start_row = 1
    start_col = 4
    
    def bubble_sort():
        length = len(array)
        for i in range(length):
            for j in range(length - i -1):
                if array[j] > array[j+1]:
                    array[j], array[j+1] = array[j+1], array[j]
                    show_graph()
                    time.sleep(sleep)
    
    
    def show_graph(data=array):
        for r in range(item_num):
            show_sign = ["*" if (item_num-r) <= item else " " for item in data]
            move_cursor(start_row + r, start_col)
            left_text = f"{item_num-r}".zfill(2) + "|"
            print(left_text + ''.join(show_sign))
    
    
    bubble_sort()


if __name__ == "__main__":
    main()

実行は以下のようにします(uvを使っています)

uv run main.py 70 -s 0.01

実行すると以下のようになります(容量の関係でFPS落としていますが、本来はぬるぬる動きます)。

まとめ

今回はバブルソートをターミナルで可視化するためのプログラムを組んでみました。YouTubeなどでこのような動画をみたことはあったのですが、いざ自分で作ってみると画面のクリア方法だったりがとても勉強になりました。ぜひ皆さんもソートアルゴリズムの可視化に挑戦してみてください。

参考URL

https://zenn.dev/akasan/articles/034598cbd096e2

https://zenn.dev/akasan/articles/39f81f8bd15790

Discussion