【Pythonデータ構造】キューとスタック
キュー(queue)とスタック(stack)はコンピュータにおける最も基本的なデータ構造です。どちらも一列に並んだデータですが、それぞれ取り出し方が異なります。
キューの構造は FIFO (First In First Out : 先入れ先出し) とよばれ、下の概念図にあるように、格納順にデータを取り出せる仕組みになっています (一番古いデータから順に取り出します)。
一方、スタックはキューとは逆に新しいデータから先に取り出します。この仕組みを LIFO (Last In First Out : 後入れ先出し) とよびます (下図参照)。
Python には queue モジュールにキューとスタック構造が、それぞれ queue.Queue, queue.LifoQueue というクラス名で用意されています。
queue.Queue()
queue.Queue() は FIFO (First in First out) キューオブジェクトのコンストラクタです。試しにキューを作成して、put() メソッドでデータを格納してみます。
# PYTHON_QUEUE # In[1] # queueモジュールをインポート import queue # キューを作成 q = queue.Queue() # データを用意 data = ["red", "blue", "green"] # キューにデータを格納する for i in data: q.put(i)
コンストラクタに引数 maxsize を指定していないので、要素の格納数に上限はありません。キューが満杯であるときに True を返す Queue.full() メソッドを使って確認してみましょう。
# In[2] # キューが満杯になっていることを確認 print(q.full()) # False
Queue.qsize() でキューのサイズ (要素数) を取得できます。
# In[3] # キューのサイズを取得 print(q.qsize()) # 3
今度はキューが空になるまで要素を取り出してみます。
# In[4] # キューが空になるまで要素を取り出す while not q.empty(): print(q.get()) # red # blue # green
キューに入れた順に要素が取り出されています。
さらに要素を取り出そうとすると、Empty エラーを送出します。
# In[5] # さらに要素を取り出す print(q.get(block=False)) # Empty:
block 引数が True になっていると、要素が取り出せるようになるまで (新しい要素が追加されるまで) 待機を続けます。空のキューからデータを取り出すことを試みた場合に、即座にエラーを送出したいならば block に False を渡してください。timeout でエラーを送出するまでの時間を指定することもできます。
# In[6] # 5秒以内に要素を取り出せなければEmptyエラーを送出 print(q.get(timeout=5)) # Empty:
queue.empty() でキューが空になっていることを確認できます。
# In[7] # キューが空になっていることを確認 print(q.empty()) # True
次は maxsize=3 を指定して、格納上限数 3 のキューを生成して、10 個の要素を詰め込むことを試みます。
# In[8] # 格納上限数3のキューを作成 q = queue.Queue(3) # キューにデータを格納する for i in range(10): q.put(i, block=False) # Full:
上限数を超えた段階で満杯であることを示す Full エラーが送出されました。queue.full() で上限まで詰め込まれていることを確認してみます。
# In[9] # キューが満杯であることを確認 print(q.full()) # True
キューの要素をすべて取り出してみます。
# In[10] # キューが空になるまで要素を取り出す while not q.empty(): print(q.get()) # 0 # 1 # 2
queue.LifoQueue()
queue.LifoQueue() は LIFO (Last In First Out) のキュー、すなわちスタックのコンストラクタです。一番最後に格納された要素から順に取り出すことができます。
# PYTHON_LIFOQUEUE # In[1] import queue # LIFOキュー(スタック)を作成 s = queue.LifoQueue() # データを用意 data = ["red", "blue", "green"] # キューにデータを格納する for i in data: s.put(i) # キューが空になるまで要素を取り出す while not s.empty(): print(s.get()) # green # blue # red
queue.Queue() と同様に、引数 maxsize で格納できるデータの上限数を設定できます。備えるメソッドも queue.LifoQueue オブジェクトと同じです。
queue.PriorityQueue()
queue.PriorityQueue() は優先順位付きキューを生成します。データを格納するたびに、キューの内部が昇順ソートされます。たとえば、数値を格納すると、put() メソッドで小さい順に取り出せます。
# PYTHON_PRIORITYQUEUE # In[1] import queue # 優先順位付きキューを生成 p = queue.PriorityQueue() # データを用意 numbers = [5, 3, 7, 1, 4] # キューにデータを格納 for i in numbers: p.put(i) # キューが空になるまで要素を取り出す while not p.empty(): print(p.get(), end=" ") # 1 3 4 5 7
引数 maxsize で格納するデータの上限数を設定できます。備えるメソッドは queue.LifoQueue オブジェクトと同じです。
collections.deque()
collections モジュールには deque (デック) とよばれる特殊なキューが用意されています。deque は double-ended queue の省略形で、その名の通り、双方向から追加・削除が可能であることを意味しています。ほぼ list と同じ機能を備えるコンテナですが、list が追加や削除に O(n) のコストを要するのに対し、deque は O(1) のコストで処理できます。
deque.append() はアイテムを右側 (後ろ) から追加します。
# PYTHON_DEQUE # In[1] import collections q = collections.deque() # デックに0~2の数字を右側から追加 for i in range(3): q.append(i) # デックの中身を表示 print(q) # deque([0, 1, 2])
左側 (先頭) に追加するときは、deque.leftappend() を使います。
# In[2] # デックに左側から文字列"a"を追加 q.appendleft("a") print(q) # deque(['a', 0, 1, 2])
deque.insert() は任意の位置にアイテムを追加します。
# In[3] # インデックス3の位置に"b"を追加 q.insert(3, "b") print(q) # deque(['a', 0, 1, 'b', 2])
deque.pop() は右側からアイテムを取り出します。
# In[4] # 右側から要素を1つ取り出す print(q.pop()) print(q) # 2 # deque(['a', 0, 1, 'b'])
deque.popleft() は左側からアイテムを取り出します。
# In[5] # 左側から要素を1つ取り出す print(q.popleft()) print(q) # a # deque([0, 1, 'b'])
deque はリストなどのイテラブルからデータをまとめて引き継ぐこともできます。
# In[6] # リストを定義 list_1 = ["a", "b", "c"] # リストの要素をキューに格納する q = collections.deque(list_1) # キューの中身を表示 print(q) # deque(['a', 'b', 'c'])
deque.extend() は右側からイテラブルを付け加えます。
# In[7] # リストを定義 list_2 = [0, 1] # キューの右側にリストを付け加える q.extend(list_2) # キューの中身を表示 print(q) # deque(['a', 'b', 'c', 0, 1])
deque.leftextend() は左側からイテラブルを付け加えます。
# In[8] # リストを定義 list_3 = [2, 3] # キューの左側にリストを付け加える q.extendleft(list_3) # キューの中身を表示 print(q) # deque([3, 2, 'a', 'b', 'c', 0, 1])
deque.rotate(n) はアイテムを右に n 個ずらします。
# In[9] # キューの要素を右に3個ずらす q.rotate(3) print(q) # deque(['c', 0, 1, 3, 2, 'a', 'b'])
コメント
【AI補足解説】スレッドセーフなQueueクラスでは、複数のスレッドが同時に要素を追加したり、要素を取り出したりすることができます。このとき、要素の追加や取り出しの順序が正しく保たれ、データの整合性を保つことができます。
例えば、複数のスレッドが同時にQueueに要素を追加しようとした場合、スレッドセーフなQueueクラスでは、内部的に適切なロック(排他制御の仕組み)が行われ、要素の追加が競合しないように制御されます。同様に、複数のスレッドが同時に要素を取り出そうとした場合も、適切な順序で要素が取り出されます。
スレッドセーフなQueueクラスを使用することで、複数のスレッドが同時にQueueにアクセスしても、データの整合性が保たれ、競合状態やデータの破損を防ぐことができます。これにより、プログラムが安全かつ正確に動作することができます。
ただし、注意点として、スレッドセーフなQueueクラスを使用しても、他の共有変数やリソースへのアクセスがスレッドセーフでない場合、依然として競合状態や問題が生じる可能性があります。スレッドセーフなQueueクラスの使用は、プログラム全体のスレッドセーフ性を確保するための一部です。
スレッドセーフなQueueクラスは、並行処理やマルチスレッドプログラミングのコンテキストで非常に便利です。PythonのqueueモジュールのQueueクラスやPriorityQueueクラスは、スレッドセーフな実装を提供しています。