【第五回】C#におけるCollectionsの基本のキを理解する【Queue<​T>クラスを利用する】

はい第五回です。
第四回目はこちらです。
今回は実際にC#のプログラミングでキューを使ってみたいと思います。
ただし例のごとく、僕のプログラミング歴は約3ヶ月と短いので、間違っている可能性山のごとしです。
その点だけご留意ください。

Queue<​T>クラスを利用する

C#でキューを用いる場合、一般的にはQueue<​T>クラスを利用するでしょう。
Queue<​T>クラスの詳細はQueue<​T> Classを参照してください。

さて、スタックと同じく、Queue<​T>クラスの特徴も見てみましょう。
まずは引用です。

原文:

This class implements a generic queue as a circular array.

翻訳:

このクラスは、循環する配列としてジェネリックキューを実装します。

循環する配列とは、配列を無駄なく利用するための機能の一つですね。
配列と言えば普通、横に向かってポンポンポンと要素が並べられるものをイメージすると思いますが、循環する配列は円をイメージするとわかりやすいと思います。
つまり、配列の最初と最後のインデックスがつながっていて、一つの円を形成しているイメージです。
循環する配列の詳しいアルゴリズムの解説はここの本題ではないので(というか僕も人に説明できるほど理解していない)、とにもかくにも「効率的に配列を使うためのアルゴリズムだ」とだけ覚えておいてください。

キューの主な使い方は3つ

例のごとく引用からです。
原文:

Three main operations can be performed on a Queue<T> and its elements:
Enqueue adds an element to the end of the Queue<T>.
Dequeue removes the oldest element from the start of the Queue<T>.
Peek peek returns the oldest element that is at the start of the Queue<T> but does not remove it from the Queue<T>.

翻訳:

Queue<T>とその要素に対し、以下の3つの主要な操作が実行できます。
・Enqueue:Queue<T>の末尾に要素を追加します。
・Dequeue:Queue<T>が生成されたときから見て、最も古い要素を取り除きます。
・Peek :Queue<T>が生成されたときから見て、最も古い要素を戻り値として返しますが、その要素はQueue<T>から取り除かれません。

要はPush(T)がEnqueue(T)になって、Pop()がDequeue()になった感じですね。
もちろん、並んだ順で処理される(キュー)のか、上に積まれた最後のものから処理される(スタック)のかの違いはありますよ。

Queue<​T>のメソッドを使ってみる

ほとんどStack<T>と同じ感じです。
Contains()とClear()があって、あとはQueue<T>固有のメソッドがあると覚えておけば、あたらずと言えども遠からずです。
Countプロパティがあるのも、List<T>やStack<T>と共通ですね。

要素を追加する

キューの末尾に要素を追加するには、Enqueue(​T)を使用します
スタックのところでも似たようなことを書きましたが、末尾以外に要素を追加したい場合、キューは利用すべきではありません
というのも、末尾以外に要素を追加してしまうと、キュー本来の使い方から乖離してしまい、あとからコードを読んだ人が混乱してしまうからです(あとからコードを読むというのは、よく言われていることですが「未来の自分」も含まれていますよ)。
「本来の使い方から外れる」というのは、管理がしにくくなるというデメリットが多いんですね。

さてスタックと同じく具体的なコードを示しておきます。

Queue<string> queue_month = new Queue<string>();
for (int i = 1; i <= 12; i++)
{
queue_month.Enqueue(i.ToString() + “月”);
}

デバッグ画面でqueue_monthの要素を確認してみてください。
1月から12月まで追加されているはずです。

要素を取り出す

要素を取り出すには、Dequeue()とPeek()があります
Dequeue()は取り出した値を配列から消しますが、Peek()は消さないんですね。
この辺もまんまStack<T>と同じです。

では、サンプルコードを示します。
先ほどのコードに、以下のコードを追加してください。

while (queue_month.Count != 0)
{
Console.WriteLine(queue_month.Dequeue());
}

これでコンソール画面に1月から12月まで表示されますね。
Peek()を使うと、1月が表示され続けて無限ループするのも同じです。

13月や14月を追加してみる

さて、スタックの項目では
「6月まで要素を取り出したあと、「13月」や「14月」を追加するプログラムを書こうとしてみてください。とても手間がかかることがわかると思います」
と書きました

ではキューではどうなのでしょうか?
実際にやってみましょう。

まず、6月までの要素をDequeue()します
イメージしやすいように言えば、カレンダーが7月になるまでめくるということです。
まずは先ほど書いたwhile文を全て削除して、代わりに次のコードを挿入してください。

for(int i = 1; i < 7; i++)
{
queue_month.Dequeue();
}

これでカレンダーは今7月になっています。
ところが7月のうちに、政府のおエライさんが「今年から一年は14月まであることにする」と宣言しました
このプログラムのままでは、12月までしか表示してくれません。
カレンダーに13月と14月を追加してみましょう。
と言ってもコード自体はとっても簡単です。

queue_month.Enqueue(“13月”);
queue_month.Enqueue(“14月”);

たったのこれだけで(スマートさにかけるコードですが、まあサンプルと思って大目に見てください)、13月と14月が追加されました。
同じことをスタックで実装しようと思うと、とっても手間がかかるんですね(具体的なコードは書きませんが、興味があればスタックで同じことをやってみてもよいかもしれません。すごく面倒です)。
このシリーズの第二回で「適切なコレクションの型を選ぶのは大切」と言ったのは、まさにこういうことなんです。

キューの説明は終わり

いかがでしたでしょうか。
スタックとキューの違いがぼんやりとでもわかっていただければ、筆者冥利に尽きます。
次回はDictionary<TKey,TValue>を扱いたいと思います。
そして、たぶん最終回ですね。
ああ疲れた

フォローする