【アルゴリズム キュー】ざっくりわかるシェルスクリプト14

キュー

キューはデータ構造の一つです。
キューは待ち行列とも呼ばれ、その名の通り行列に並ぶ事を考えるとイメージしやすいです。
行列においては、先に並んだ人ほど優先されます。
キューにデータを追加する場合、データは一番最後に追加されます。
キューにデータを追加する操作をenqueue(エンキュー)と呼びます。
キューからデータを取り出す場合、最も古くに追加されたデータから取り出されます。
キューからデータを取り出す操作をdequeue(デキュー)と呼びます。
このような、先に入れたものを先に出す先入れ先出しの仕組みを
「First In First Out」を略してFIFOと呼びます。

以下の図からもわかるとおり、最初に追加した色の箱から取り出されているのがわかると思います。配列の要素の一番古い要素の添字を指し示すのがpeekです。
配列から要素を取り出すときには、peekを添え字にして取り出すことで、配列全体の中から一番古い要素を取り出すことができます。

プログラムソース

この章で使っているプログラムソースは以下にあります。
03_1Stack.sh 一般的な配列のキュー
03_2Eval_Stack.sh 擬似的な2次元配列で実装したキュー

キューの処理ロジック「リア」と「フロント」

処理は複雑ではありません。むしろシンプルです。
rear(リア)と、front(フロント)の動きをもう一度見てください。
リア(最後尾)、フロント(最前面)はどこか。
追加されるときはリア(最後尾)に。
出すときはフロント(最前面)から。

この図がわかりやすいです。

Bash/シェルスクリプトの配列で実装したキュー

##
# <>queueDisplay()
# 表示
function queueDisplay(){
  for((i=front;i<rear;i++));do
      echo "$i" "${queue[i]}";
  done
  echo "------";
}
##
# <>dequeue()
# デキュー
function dequeue(){
  ((front++));
}
##
# <>enqueue()
# エンキュー
function enqueue(){
  queue[rear++]=$1;
}
##
# <>peek()
# ピーク
function peek(){
  echo "peek :"$front : ${queue[front]};
}
##
# <>execQueue()
# キューの実行
function execQueue(){
  rear=0;   #後ろ端(enqueueされるほう)
  front=0;  #前端(peek/dequeueされるほう)
  enqueue 10;
  enqueue 20;
  enqueue 30;
  enqueue 40;
  echo "データを4つenqueue";
  peek;
  queueDisplay;
  #----
  dequeue;
  dequeue;
  echo "データを2つdequeue";
  peek;
  queueDisplay;
  #----
  enqueue 50;
  echo "データを1つenqueue";
  peek;
  queueDisplay;
  #----
}
##
# メイン
execQueue;
exit;

bash/シェルスクリプトによる疑似2次元配列の実装

#######################################
# 03_2Queue.shを、少しだけオブジェクティブに
# aRray[0].getValue() で値を取得できるように改変した
# 配列にIDと値を入れるだけのbashスクリプト
#######################################
##
# グローバル変数
declare -i nElems=0;
declare -i rear=0;
#
##
# <>setValue() 
# セッター
function setValue() {
  #今後挿入や置き換えに備えてnElemsとは別の変数を用意しておく
  local Elem="$1";      
  local value="$2";
	eval "aRray[$Elem].getValue()      { echo "$value"; }"
}
##
# <>setID()
# セッター
function setID(){
  #今後挿入や置き換えに備えてnElemsとは別の変数を用意しておく
  local Elem="$1";      
  local ID="$2";
	eval "aRray[$Elem].getID()         { echo "$ID"; }"
}
##
# <>queueDisplay()
# 表示
function queueDisplay(){
  for((i=front;i<rear;i++));do
      echo "$i" "$(aRray[$i].getValue)";
  done
  echo "------";
}
##
# <>dequeue()
# デキュー
function dequeue(){
  ((front++));
}
##
# <>enqueue()
# エンキュー
function enqueue(){
  ID=$1;
  value=$2;
  setID     "$rear"    "$ID";      #IDをセット
  setValue  "$rear"    "$value";   #Valueをセット
  ((rear++));
}
##
# <>peek()
# ピーク
function peek(){
  echo "peek :"$front : $(aRray[$front].getValue);
}
##
# <>execQueue()
# キューの実行
function execQueue(){
  rear=0;   #後ろ端(enqueueされるほう)
  front=0;  #前端(peek/dequeueされるほう)
  ID=100;
  enqueue $((ID++)) 10;
  enqueue $((ID++)) 20;
  enqueue $((ID++)) 30;
  enqueue $((ID++)) 40;
  echo "データを4つenqueue";
  peek;
  queueDisplay;
  #----
  dequeue;
  dequeue;
  echo "データを2つdequeue";
  peek;
  queueDisplay;
  #----
  enqueue $((ID++)) 50;
  echo "データを1つenqueue";
  peek;
  queueDisplay;
  #----
}
##
# メイン
execQueue;
exit;

実行結果

bash-5.1$ bash 03_2Eval_Queue.sh
データを4つenqueue
peek :0 : 10
0 10
1 20
2 30
3 40
------
データを2つdequeue
peek :2 : 30
2 30
3 40
------
データを1つenqueue
peek :2 : 30
2 30
3 40
4 50
------
bash-5.1$

循環キュー

映画館の待ち行列なら、先頭の一人が列を去ったら、列全体が前に進みます。
キューでは削除のたびに全ての項目を前に詰めて移動しますが、その時間が無駄です。
むしろ項目はそのままにしておいて、キューのフロント(前端)やリア(後端)が動いた方が簡単なのです。
しかしその場合の問題は、キューの後端がすぐに配列の終端に達してしまいます。
まだ満杯ではないのに新たなデータを挿入できないというこの問題を解決するために、循環キューでは、frontとrearの矢印は配列の先頭へラップアラウンド(最初に戻る)します。
その結果として循環キューというものができあがります。リングバッファとも呼ばれます。
キューと循環キューの本質的な違いは、キューは循環キューよりも多くのスペースを消費するのに対し、循環キューはキューのメモリ浪費を制限するように考案されたということです。

Bash/シェルスクリプトの配列で実装したキュー

##
#
function display(){
  for((n=0;n<nElems;n++));do
    echo "$n" "${array[n]}";
  done
  echo "------";
}
##
#
function insert(){
  array[nElems++]="$1";
}
##
#
function setArray(){
  nElems=0;
  for((i=0;i<$1;i++));do
    insert $(echo "$RANDOM");
  done
}
##
#
function CircularQDisplay(){
  for((i=0;i<maxSize;i++));do
      echo "$i" "${queue[i]}";
  done
  echo "------";
}
##
#
function CircularQDequeue(){
  ((front++));
  ((front==maxSize))&&{ front=0; }
}
##
#
function CircularQEnqueue(){
  (( rear==(maxSize-1) ))&&{ rear=-1; }
  queue[++rear]=$1;
}
##
#
function CircularQPeek(){
  echo "peek :front :$front  rear : $rear  peek : $front ${queue[front]} ";
}
##
#
function execCircularQ(){
  rear=-1; #後ろ端(enqueueされるほう)
  front=0; #前端(peek/dequeueされるほう)

  maxSize=5 #キューの項目数

  CircularQEnqueue 10;
  CircularQEnqueue 20;
  CircularQEnqueue 30;
  CircularQEnqueue 40;
  echo "データを4つenqueue";
  CircularQPeek;
  CircularQDisplay;
#exit;
  #----
  CircularQDequeue;
  CircularQDequeue;
  echo "データを2つdequeue";
  CircularQPeek;
  CircularQDisplay;
#exit;
  #----
  CircularQEnqueue 50;
  echo "データを1つenqueue";
  CircularQPeek;
  CircularQDisplay;
#exit;
  #----
  # CircularQ
  CircularQEnqueue 60;
  CircularQEnqueue 70;
  echo "データを2つenqueue";
  CircularQPeek;
  CircularQDisplay;
#exit;
  #---
  CircularQDequeue;
  CircularQDequeue;
  CircularQDequeue;
  echo "データを3つdequeue";
  CircularQPeek;
  CircularQDisplay;
}
##
#
execCircularQ;
exit;

bash/シェルスクリプトによる疑似2次元配列の実装

#######################################
# 03_3CircularQueue.shを、少しだけオブジェクティブに
# aRray[0].getValue() で値を取得できるように改変した
# 配列にIDと値を入れるだけのbashスクリプト
#######################################
##
# グローバル変数
declare -i nElems=0;
declare -i rear=0;
#
# <>display()  
# 配列を表示
function display(){
  for((i=0;i<nElems;i++)){
    echo -n "aRray[$i]  \
    ID: " $( aRray[$i].getID ) " \
    Value:" $( aRray[$i].getValue ) ; 
    echo "";
  }
}
##
# <>setValue() 
# セッター
function setValue() {
  #今後挿入や置き換えに備えてnElemsとは別の変数を用意しておく
  local Elem="$1";      
  local value="$2";
	eval "aRray[$Elem].getValue()      { echo "$value"; }"
}
##
# <>setID()
# セッター
function setID(){
  #今後挿入や置き換えに備えてnElemsとは別の変数を用意しておく
  local Elem="$1";      
  local ID="$2";
	eval "aRray[$Elem].getID()         { echo "$ID"; }"
}
##
# <> insert
# 配列の要素に値を代入
function insert(){
  local ID=$1;          #100からの連番
  local value=$2;       #配列に代入される要素の値
  setID     "$nElems"    "$ID";      #IDをセット
  setValue  "$nElems"    "$value";   #Valueをセット
  ((nElems++));
}
##
# <> set Array
# 配列を作成
function setArray(){
  local N=$1;           #すべての要素数
  local ID=100;         #100からの連番
  local value;          #配列に代入される要素の値
  for((i=0;i<N;i++)){
    value=$( echo $RANDOM );
    insert $((ID++)) $value;
  }
}
# <> init Array
# 配列を作成
function initArray(){
  local N=$1;           #すべての要素数
  local ID=100;         #100からの連番
  local value=null;          #配列に代入される要素の値
  for((i=0;i<N;i++)){
    insert $((ID++)) $value;
  }
}
##
function CircularQDisplay(){
  for((i=0;i<maxSize;i++));do
      echo "$i" $(aRray[$i].getValue);
  done
  echo "------";
}
##
#
function CircularQDequeue(){
  ((front++));
  ((front==maxSize))&&{ front=0; }
}
##
#
function CircularQEnqueue(){
  ID=$1;
  value=$2;
  if(( rear==(maxSize-1) ));then 
    rear=-1; 
  fi
  ((rear++));
  setID     "$rear"    "$ID";      #IDをセット
  setValue  "$rear"    "$value";   #Valueをセット
}
##
#
function CircularQPeek(){
  echo "peek :front :$front  rear : $rear  peek : $front $(aRray[$front].getValue) ";
}
##
#
function execCircularQ(){
  rear=-1; #後ろ端(enqueueされるほう)
  front=0; #前端(peek/dequeueされるほう)
  ID=100;

  maxSize=5 #キューの項目数
  initArray $maxSize;

  CircularQEnqueue $((ID++)) 10;
  CircularQEnqueue $((ID++)) 20;
  CircularQEnqueue $((ID++)) 30;
  CircularQEnqueue $((ID++)) 40;
  echo "データを4つenqueue";
  CircularQPeek;
  CircularQDisplay;
  #----
  CircularQDequeue;
  CircularQDequeue;
  echo "データを2つdequeue";
  CircularQPeek;
  CircularQDisplay;
  #----
  CircularQEnqueue $((ID++)) 50;
  echo "データを1つenqueue";
  CircularQPeek;
  CircularQDisplay;
  #----
  # CircularQ
  CircularQEnqueue $((ID++)) 60;
  CircularQEnqueue $((ID++)) 70;
  echo "データを2つenqueue";
  CircularQPeek;
  CircularQDisplay;
  #---
  CircularQDequeue;
  CircularQDequeue;
  CircularQDequeue;
  echo "データを3つdequeue";
  CircularQPeek;
  CircularQDisplay;
}
##
#
# 実行
execCircularQ;
exit;

実行結果

データを4つenqueue
peek :front :0  rear : 3  peek : 0 10 
0 10
1 20
2 30
3 40
4 null
------
データを2つdequeue
peek :front :2  rear : 3  peek : 2 30 
0 10
1 20
2 30
3 40
4 null
------
データを1つenqueue
peek :front :2  rear : 4  peek : 2 30 
0 10
1 20
2 30
3 40
4 50
------
データを2つenqueue
peek :front :2  rear : 1  peek : 2 30 
0 60
1 70
2 30
3 40
4 50
------
データを3つdequeue
peek :front :0  rear : 1  peek : 0 60 
0 60
1 70
2 30
3 40
4 50
------

「ざっくり」シリーズのご紹介

【アルゴリズム 再帰】ざっくりわかるシェルスクリプト15
https://suzukiiichiro.github.io/posts/2022-10-07-01-algorithm-recursion-suzuki/
【アルゴリズム キュー】ざっくりわかるシェルスクリプト14
https://suzukiiichiro.github.io/posts/2022-10-06-01-algorithm-queue-suzuki/
【アルゴリズム スタック】ざっくりわかるシェルスクリプト13
https://suzukiiichiro.github.io/posts/2022-10-06-01-algorithm-stack-suzuki/
【アルゴリズム 挿入ソート】ざっくりわかるシェルスクリプト12
https://suzukiiichiro.github.io/posts/2022-10-05-01-algorithm-insertionsort-suzuki/
【アルゴリズム 選択ソート】ざっくりわかるシェルスクリプト11
https://suzukiiichiro.github.io/posts/2022-10-05-01-algorithm-selectionsort-suzuki/
【アルゴリズム バブルソート】ざっくりわかるシェルスクリプト10
https://suzukiiichiro.github.io/posts/2022-10-05-01-algorithm-bubblesort-suzuki/
【アルゴリズム ビッグオー】ざっくりわかるシェルスクリプト9
https://suzukiiichiro.github.io/posts/2022-10-04-01-algorithm-bigo-suzuki/
【アルゴリズム 2次元配列編】ざっくりわかるシェルスクリプト8
https://suzukiiichiro.github.io/posts/2022-10-03-01-algorithm-eval-array-suzuki/
【アルゴリズム 配列準備編】ざっくりわかるシェルスクリプト7
https://suzukiiichiro.github.io/posts/2022-10-03-01-algorithm-array-suzuki/
【アルゴリズム 配列編】ざっくりわかるシェルスクリプト6
https://suzukiiichiro.github.io/posts/2022-09-27-01-array-suzuki/
【grep/sed/awkも】ざっくりわかるシェルスクリプト5
https://suzukiiichiro.github.io/posts/2022-02-02-01-suzuki/
【grep特集】ざっくりわかるシェルスクリプト4
https://suzukiiichiro.github.io/posts/2022-01-24-01-suzuki/
【はじめから】ざっくりわかるシェルスクリプト3
https://suzukiiichiro.github.io/posts/2022-01-13-01-suzuki/
【はじめから】ざっくりわかるシェルスクリプト2
https://suzukiiichiro.github.io/posts/2022-01-12-01-suzuki/
【はじめから】ざっくりわかるシェルスクリプト1
https://suzukiiichiro.github.io/posts/2022-01-07-01-suzuki/

【TIPS】ざっくりわかるシェルスクリプト
https://suzukiiichiro.github.io/posts/2022-09-26-01-tips-suzuki/

書籍の紹介

【アルゴリズム 再帰】ざっくりわかるシェルスクリプト15

【アルゴリズム 再帰】ざっくりわかるシェルスクリプト15

【アルゴリズム スタック】ざっくりわかるシェルスクリプト13

【アルゴリズム スタック】ざっくりわかるシェルスクリプト13