課題

以下のようなプログラムを作成せよ.

目的


実行方法と制限


実行結果

  • 1行目で,プログラムを実行している.
  • 2〜7行目で6個のデータを入力し,
  • 8行目でコマンドshowによって入力内容を確認している. 結果は9行目に表示されている.
  • 10行目でpopコマンドで値を取り出すと,一番最近に入力したデータ, 3が表示される(11行目).
  • 14行目でpopコマンドで値を取り出すと,最後から2番目に入力された 7が表示される(15行目).
  • 定義されていないコマンドを入力すると,その旨が表示される (19行目).
  • すでにデータが空の状態でpopすると(28, 30行目), データがない旨が表示される(29, 31行目).
  • quitでプログラムは終了する(39行目).
[ 1] % ./a.out
[ 2] 3
[ 3] 5
[ 4] 1
[ 5] 2
[ 6] 7
[ 7] 3
[ 8] show
[ 9] [ 3 5 1 2 7 3 ]
[10] pop
[11] 3
[12] show
[13] [ 3 5 1 2 7 ]
[14] pop
[15] 7
[16] show
[17] [ 3 5 1 2 ]
[18] aa
[19] Unknown command "aa"
[20] pop
[21] 2
[22] pop
[23] 1
[24] pop
[25] 5
[26] pop
[27] 3
[28] pop
[29] Stack UnderFull, could not pop.
[30] pop
[31] Stack UnderFull, could not pop.
[32] 3
[33] show
[34] [ 3 ]
[35] pop
[36] 3
[37] pop
[38] Stack UnderFull, could not pop.
[39] quit

理論

  • スタックはLast-In First-Outの振る舞いを持つデータ構造である [1, 10ページ]. すなわち,最も最近に入力したデータから,値を取り出すことができるため, 最初に入力したデータは最も最後に取り出すことができる.
  • これは,丁度,本などを机に積み上げた場合に似ている.
  • スタックの振る舞いの一端を図示すると以下のようになる.


    図1: スタック操作の例

    図1からわかるように, 図中で入力されたデータを全て取り出した後でないと, 最初から入っているデータ10を取り出すことができない.
  • スタック・データ構造は, 関数の入れ子呼び出しの実現や, 数式の計算などソフトウェアの世界で幅広く利用されている. 例えば逆ポーランド記法[2]の計算などに利用される.

プログラム構成

定数・定義

  • MAX_STACK
    • 値: 256
    • 保持できる数値の上限値を指定.
  • MAX_INPUT
    • 値: 既に定義されていなければ512
    • 入力されるコマンド長の上限を指定.システム側で指定されている場合有り.
  • CMD_QUIT
    • 値: "quit"
    • 終了コマンドの文字列.
  • CMD_POP
    • 値: "pop"
    • 値を取り出す命令の文字列.
  • CMD_SHOW
    • 値: "show"
    • 現在入力されている値の表示コマンドの文字列.

共有変数

  • int stack[MAX_STACK]
      入力された値を保持する配列.
  • int stack_ptr
      もっとも最近に入力された値を保持する配列stackの添え字を保持.
      初期値は(-1)
      値は 0 <= stack_ptr < MAX_STACK

関数の仕様

  • void show()
      説明 現在保持している数値を画面に表示
      参照する共有変数 stack, stack_ptr
      入出力 保持している数値を画面に表示
  • int pop(int* r)
      説明 最も最近入力されたデータを(*r)に返す.
      引数 r: 値を返す変数のアドレス.
      返り値 返す値があれば1, なければ0.
      参照・変更する共有変数 stack, stack_ptr
  • int push(int r)
      説明 データrをプログラムに記憶させる.
      引数 r: 記憶させる値.
      返り値 記憶できれば1, できなければ0.
      参照・変更する共有変数 stack, stack_ptr
  • int digit_string(char* s)
      説明 数値のみで構成される文字列か否かを判定する.
      引数 s: 判定する文字配列の先頭アドレス.NULLは受け入れられない.
      返り値 数値のみなら1, そうでなければ0. 長さ0の文字列も0を返す
  • main()
      説明 ユーザーからの入力を受け取り, それに従い他の関数を呼び出し,場合によっては結果や警告を表示する.
      変更する共有変数 stack_ptr: (-1)に初期化する.(プログラム62行目)
      入出力 標準入力からのユーザー入力. 標準出力へ関数返り値や警告などを出力.
      呼び出すユーザー定義関数 show, pop, push, digit_string.
      ユーザー定義関数から参照されるローカル変数 char ibuf[MAX_INPUT]: プログラム61行目.digit_stringに引数として与えられる,
      char ibuf[MAX_INPUT]: 整数値に変換され pushに引数として与えられる.
      ユーザー定義関数から変更されるローカル変数 int r: プログラム74行目.popに&rが渡すことでpop内で変更される.

アルゴリズム

  • void show():
    1. i=0〜stack_ptr まで以下の処理を繰り返す.
      1. stack[i]を画面表示.
  • int pop(int* r):
    1. もしstackに値が保持されていれば,(プログラム33行参照)
      1. (*r)にstack[stack_ptr]の値を代入.
      2. stack_ptrを1減らす.
      3. 1を返す.
    2. そうでなければ0を返す.
  • int push(int r):
    1. もしstackに値を追加できるのなら,(プログラム43行参照)
      1. stack_ptrを1増やす.
      2. stack[stack_ptr]にrの値を代入.
      3. 1を返す.
    2. そうでなければ0を返す.
  • int digit_string(char* s):
    1. もし,sが空文字列なら0を返す.
    2. sを先頭から順に最後まで走査する.
      1. もし *s が数字でなければ0を返す.
    3. 1を返す.
  • main():
    1. stack_ptrを(-1)に初期化.
    2. 標準入力からのデータを char ibuf[] に保存し, 入力データが無くなるまで以下を繰り返す.
      1. ibufが CMD_QUITと等しければ,繰り返しを抜ける.
      2. ibufが CMD_SHOWと等しければ,show() を呼び,繰り返し先頭に戻る.
      3. ibufが CMD_POPと等しければ,
        1. ローカル変数 int r を確保する.
        2. pop(&r)を実行する.
        3. popの返り値が0なら警告を表示して,繰り返し先頭に戻る.
        4. popの返り値が1ならrを画面に表示して,繰り返し先頭に戻る.
      4. ibufが 全て数字で出来た文字列ならば,(digit_stringで判定)
        1. ibufを整数に変換し,push関数に渡す.(プログラム83行参照)
        2. 返り値が0なら警告を表示.
        3. 繰り返し先頭に戻る.
      5. 該当するコマンドがなく,数値でもなければ警告を表示し, 繰り返しの先頭に戻る.

考察

スタック構造を操作する関数を pop, push, show に限定したことで, スタックに対する操作をシンプルにまとめることができた. 実際のスタックデータはグローバル変数として実装したが, これをスタックを操作する関数からのみ参照できる形にすれば, プログラムの保守性は向上すると思われる. その理由は,現時点ではスタックに関係ない他の関数が自由に スタックデータの実体(stack[]およびstack_ptr)を操作することができ, 整合性のないデータに改竄してしまう恐れがあるからである. 具体的にスタックデータを特定の関数からのみ参照できるようにするには, 例えば,スタックを操作する関数を1つのソースファイルに集め, スタックデータ(配列)をstatic宣言する方法などがある. これによって, そのファイルに属さない関数からはスタックデータを参照できなくなる.

ユーザーとの対話をmain関数に集約したため, ユーザーインタフェース様式の変更に柔軟に対応できると思われる. 例えば,現時点では標準入出力を利用してユーザーとのインタラクションを行うが, スタック操作関数の変更なしに, GUIを用いたインタラクションに置きかえることが可能である. しかし, 唯一,show関数のみが直接に標準出力への出力を行っているため, この部分は修正すべきである.例えば,

    int show(int s[], int n)
    説明 現在保持している数値を返す
    参照する共有変数 stack, stack_ptr
    引数 s: 値を返す配列の先頭アドレス, n: 配列の要素数
    返り値 0: 返す値がない.-1: 返す値のほうがnより大きい, それ以外: 現在保持しているデータ数
などとすれば,インタフェースの変更へも柔軟に対処できるようになる.

参考文献・資料

  1. 中田育男. コンパイラ. 新コンピュータサイエンス講座. オーム社, Jun. 1995. ISBN 4-274-13013-4, 193 pages, 値段 3200円+税,
  2. http://msdos.cs.shinshu-u.ac.jp/mc/lesson/rpolish.html (2000年12月現在)

プログラムリスト

[ 1] /* $xId: main.c,v 1.3 2000-12-17 16:33:23+09 kaiya Exp kaiya $ */
[ 2] 
[ 3] #include <stdio.h>
[ 4] 
[ 5] #define MAX_STACK   256
[ 6] #ifndef MAX_INPUT
[ 7] #define MAX_INPUT   512 /* max size of a char input buffer */
[ 8] #endif
[ 9] 
[10] #define CMD_QUIT    "quit"
[11] #define CMD_POP     "pop"
[12] #define CMD_SHOW    "show"
[13] 
[14] /* prototype dec's */
[15] 
[16] void show(void);
[17] int pop(int*);
[18] int push(int);
[19] int digit_string(char*);
[20] 
[21] int stack[MAX_STACK];
[22] int stack_ptr;
[23] 
[24] void show(){
[25] int i;
[26]     printf("[ ");
[27]     for(i=0; i<stack_ptr+1; i++)
[28]         printf("%d ", stack[i]); 
[29]     printf("]\n");
[30] }
[31] 
[32] int pop(int* r){
[33]     if(0 <= stack_ptr && stack_ptr < MAX_STACK){
[34]         (*r)=stack[stack_ptr];
[35]         stack_ptr--;
[36]         return 1;
[37]     }else{ /* underflow */
[38]         return 0;
[39]     }
[40] }
[41] 
[42] int push(int r){
[43]     if(stack_ptr+1<MAX_STACK){
[44]         stack_ptr++;
[45]         stack[stack_ptr]=r;
[46]         return 1;
[47]     }else{
[48]         return 0;
[49]     }
[50] }
[51] 
[52] int digit_string(char* s){
[53]     if(!*s)
[54]         return 0; /* length zero string */
[55]     for(; *s; s++)
[56]         if(!isdigit((int)*s)) return 0;
[57]     return 1;
[58] }
[59] 
[60] main(){
[61] char ibuf[MAX_INPUT];
[62]     stack_ptr=(-1);
[63] 
[64]     while(gets(ibuf)!=NULL){
[65]         if(!strcmp(ibuf, CMD_QUIT))
[66]             break;
[67] 
[68]         if(!strcmp(ibuf, CMD_SHOW)){
[69]             show();
[70]             continue;
[71]         }
[72] 
[73]         if(!strcmp(ibuf, CMD_POP)){
[74]         int r;
[75]             if(!pop(&r))
[76]                 printf("Stack UnderFull, could not pop.\n");
[77]             else
[78]                 printf("%d\n", r);
[79]             continue;
[80]         }
[81] 
[82]         if(digit_string(ibuf)){
[83]             if(!push(atoi(ibuf)))
[84]                 printf("Stack Full, could not push.\n");
[85]             continue;
[86]         }
[87] 
[88]         printf("Unknown command \"%s\"\n", ibuf);
[89]     }
[90]     exit(0);
[91] }

$Id: index.html,v 1.10 2000-12-23 19:06:46+09 kaiya Exp kaiya $