🐩
JuliaでQueueを実装する
下心
「JuliaでStackを実装する」という記事で実装したstackは直前に追加した要素を取り出せるデータ構造でした。
しかし、古い要素を取り出すデータ構造も欲しいです。これがqueueと呼ばれる物です。
queueの操作(簡単な説明)
stackと同様、queueの構成要素は以下の3つです。
- 要素の初期化
- 要素の追加
- 要素の削除
- 空(カラ)かの確認
しかし、今回は要素の追加は配列の末尾に、要素の取り出しは配列の先頭から行うため、queueには配列と、その末尾の要素の位置情報の他に先頭の情報も必要です。
実装
まずは初期化です。
mutable struct queue{T}
last :: Int
front :: Int
length :: Int
data :: Array{T,1}
function queue{T}(N::Int) where T
new(1,1,N+1,Array{T,1}(undef,N+1)) #空か満杯かを区別するために、N+1個の要素を用意
end
end
構造体の構成要素はlast, front, length, data
です。ここで以下の2つの工夫があります。
- lengthを定義
- 配列を1つ大きく確保
2つとも後の実装を見ていただくと理由がわかりますが、端的にはそれぞれ
- 要素の追加、削除を繰り返すとindexがどんどん増えるので、それを抑えるため
- queueが空か満室かを区別するため
です。
次に、要素の追加です。ほとんどstackと同様ですが、今回はlast
に配列の最後の要素の次のindexを記憶しています。
加えて、last
がどんどん大きくなることを防ぐために、last=lengthになったら次のindexを1に戻しています。
function push!(q :: queue, sth)
q.data[q.last] = sth
q.last = ifelse(q.last == q.length, 1 , q.last+1)
end
要素の削除もあまり変わりません。front
の要素を削除すると、次の要素の位置はfront+1
となります。ここでも同様に、front
が大きくなりすぎないようにlength
で引き戻しています。
function pop!(q :: queue)
if !isempty(q)
tmp = q.data[q.front]
q.front = ifelse(q.front == q.length, 1 , q.front+1)
return tmp
else
error("queue is empty")
end
end
最後はqueueが空かの確認です。これはlastがfrontと同じ位置かを調べれば良いです。
function isempty(q :: queue)
q.last==q.front
end
動作確認
動作確認には以下のコードを用意しました。今回の実装の良いところはCharもqueueに追加できることです。
q = queue{Char}(10)
for s in 'A':'J'
push!(q,s)
end
while !isempty(q)
println(pop!(q))
end
for s in 'A':'J'
push!(q,s)
end
while !isempty(q)
println(pop!(q))
end
AtCoderなどで使う際には念のため確認してください。
Discussion
スミマセン素朴な疑問です。AtCoder等の競プロやってない人のシロウト質問です。
Juliaには配列をStackやQueueとして扱うための関数群は既に用意されている(
push!
/pop!
/popfirst!
)のですが、それだとパフォーマンス的に問題があって簡単にTLEするからきちんとデータ構造自分で作った方が良い、ということでよろしいでしょうか?(もちろん私も実際にJuliaでプログラミングする際にパフォーマンス目的や可読性目的でこうしたデータ構造に適切な名前(型)を付けて実装すると言うことは良くやるのでその意味では至極真っ当だしその行為にはむしろ肯定的です!)
ありがとうございます。早くなるだろうという先入観からこの記事を書いていましたが、以下のように簡単に調べたところJuliaの関数のほうが速かったです。(早めに気付けて良かったです。)
加えて、今回の実装ではメモリを1.5倍使っているので悪い実装例です。
今回のQueueでは
を4回実行すると
次に、Juliaの関数を使って
従ってJuliaの関数のほうが速い。