プログラミングC
第9回 演習問題

A問題(50点)

演習問題1(リストの実装練習)

プログラム /home/course/prog1/public_html/2024/ex/ex09/prog00.c
ヘッダファイル /home/course/prog1/public_html/2024/ex/ex09/prog00.h は、ハンドアウトLec09-19〜22 (pp.129-130) に出てきた、リストの基本操作を行うプログラムである。
これを各自prog00.c, prog00.h)として自分のディレクトリにコピーし、 ハンドアウトLec09-23 (p.130) 通りの動作を行う事を確認せよ。(これらのファイルは提出しない)

さて、ハンドアウトでは、リストのデータとして int型の構造体メンバー key を考えたが、 もう少し実用的なデータを扱うプログラムに変更する。
今回の演習では、リストのデータとして、学生のID番号 (id)、姓 (surname)、名 (givenname)、及び年齢 (age)を格納するRecord型構造体を考えてみる。

(提出ファイル名: prog01.c)

手順は以下の通り:
  1. prog00.h と以下の手順を基に、studlist.hの空欄を埋めて、ヘッダファイルを完成させなさい。
  2. 手順:
    studlist.h
    /* Record構造体の宣言 */
    typedef struct {
      int id;
      char surname[12];
      char givenname[12];
      int age;
    } Record;
    
    /* node構造体の宣言 */
    typedef ________ ________ *NodePointer;
    struct node {
      ________ data;
      ________ next;
    };
    
    /* プロトタイプ宣言 */
    NodePointer insert(Record); /* 新しいノード挿入関数 */
    NodePointer finditem(int); /* リスト内の重複するノード検索関数 */
    void listprint(void); /* リスト内データの表示関数 */
    NodePointer make_1node(Record, NodePointer); /* 新しいノード作成関数 */
    
    /* グローバル変数 head */
    NodePointer head;
    
  3. prog00.c と以下の手順を基に、prog01.cの空欄を埋めて、プログラムを完成させなさい。
  4. 手順:
    prog01.c
    #include <stdio.h>
    #include <stdlib.h>
    #include ________
    
    int main()
    {
      Record r;
      int i;
     
      /* リストの初期化 */
      head = make_1node( ________, NULL );
    
      listprint();
      
      /* 新しいノードをリストに追加する */
      /* CTL+D でループを抜ける */
      while (1) {
        printf("Insert new student data: (ID Surname Givenname Age) -> ");
        
        if (scanf("%d %s %s %d", ________, ________, ________, ________) != 4) {
          printf("\n");
          break;
        }
        if (insert(________) == NULL) {
          printf("ID %d is already on the list!\n", ________);
        }
        listprint();
      }
      printf("\n");
      return 0;
    }
    
    NodePointer insert(Record r)
    {
      NodePointer newnode;
    
      if (finditem(________) == NULL) { /* 重複するノードの検索 */
        newnode = make_1node(________, ________); /* headの下に新しいノードを作成 */
        ________ = newnode; /* headの下に新しいノードをつなぐ */
        return newnode;
      }
      else return NULL;
    }
    
    void listprint(void)
    {
      NodePointer n;
    
      printf("Head - \n");
      for(n = ________; ________; n = ________){ /* headの次のノードからNULLの前までたどる */
        printf("  %7d %-12s %-12s %d\n", ________, ________, ________, ________);
      }
      printf("\n");
    }
    
    NodePointer finditem(int sid)
    {
      NodePointer n;
    
      for (n = ________; ________; n = ________) { /* headからNULLの一つ前までのノードをたどる */
        if (________ == sid) return ________; /* sidと同じ値が見つかったら一つ手前のノードを指すポインタを返す */
      }
      return NULL;
    }
    
    NodePointer make_1node(Record r, NodePointer p)
    {
      NodePointer n;
    
      if ((n = (________)malloc(________) ) == NULL) { /* メモリ確保 */
        printf("Error in memory allocation\n");
        exit(8);
      }
    
      /* dataとnextに引数の値をセットする */
      ________ = ________; 
      ________ = ________; 
      return n;
    }
    
    
実行例
% ./a.out
Head -

Insert new student data: (ID Surname Givenname Age) -> 1301001 AIZU Taro 19
Head -
  1301001 AIZU         Taro         19

Insert new student data: (ID Surname Givenname Age) -> 1301022 BANDAI Hanako 22
Head -
  1301022 BANDAI       Hanako       22
  1301001 AIZU         Taro         19

Insert new student data: (ID Surname Givenname Age) -> 1301033 TSURUGA Shirou 21
Head -
  1301033 TSURUGA      Shirou       21
  1301022 BANDAI       Hanako       22
  1301001 AIZU         Taro         19

Insert new student data: (ID Surname Givenname Age) -> 1301001 AIZU Jiro 18
ID 1301001 is already on the list!
Head -
  1301033 TSURUGA      Shirou       21
  1301022 BANDAI       Hanako       22
  1301001 AIZU         Taro         19
      
Insert new student data: (ID Surname Givenname Age) -> Ctrl-D
% 
[文字列表示の書式指定について]
実行例の氏名表示の部分のように文字列の長さの上限が決まっていて、その範囲で表示すべき文字数に変化がある場合、 %12s のように%sに対する桁数指定を行うと、指定分の桁数の表示幅が確保されて、その桁数内での右詰めの表示になる。また、%-12s のように桁数指定値に負号を付けると左詰めとなる(表示例ではこの方法を用いている)。printf関数の桁数に関する書式指定についてはハンドアウトp.16のLec03-14で説明されている%dについての内容が%sに対しても適用できる。

B問題

演習問題2(リストの拡張)

prog01.cprog02.cにコピーし、 prog02.cに以下のような機能を追加したプログラムにせよ。最初に、/home/course/prog1/public_html/2024/ex/ex09/Student.txtを自分のディレクトリにコピーしなさい。

(提出ファイル名: prog02.cstudlist.h)

prog01.cprog02.c に、コピーしてから、prog02.cを修正すると良い。問題1で使用したstudlist.h をそのまま用いて、変更する必要はない。

ヒント:ファイルからのデータ読み取り機能とノード挿入位置の変更の両者を一括して 解決しようとすると、エラーがあった場合、どちらに問題があるのか切り分けが難しくなるので、一方の処理が完成してから、もう一方の処理を加えるようにすると良い。

実行例
% ./a.out
Head - 
  1301001 AIZU         Taro         19
  1301022 BANDAI       Hanako       22
  1301033 TSURUGA      Shirou       21
    .....(中略)
  1301150 KITAKATA     Testuo       18
  1301164 SUKAGAWA     Yoshiko      18
  1301200 ASHINOMAKI   Masaru       19
14 nodes exist in the list.

Insert new student data: (ID Surname Givenname Age) -> 1301001 AIZU Jiro 18
ID 1301001 is already on the list!
Head - 
  1301001 AIZU         Taro         19
  1301022 BANDAI       Hanako       22
  1301033 TSURUGA      Shirou       21
    .....(中略)
  1301150 KITAKATA     Testuo       18
  1301164 SUKAGAWA     Yoshiko      18
  1301200 ASHINOMAKI   Masaru       19
14 nodes exist in the list.

Insert new student data: (ID Surname Givenname Age) -> 1301100 IKKI Goro 18
Head - 
  1301001 AIZU         Taro         19
  1301022 BANDAI       Hanako       22
  1301033 TSURUGA      Shirou       21
    .....(中略)
  1301150 KITAKATA     Testuo       18
  1301164 SUKAGAWA     Yoshiko      18
  1301200 ASHINOMAKI   Masaru       19
  1301100 IKKI         Goro         18
15 nodes exist in the list.

Insert new student data: (ID Surname Givenname Age) -> Ctrl-D
%

演習問題3(削除の実装)

演習問題2のプログラムにノードを削除する機能を追加する。

prog02.cprog03.c に、studlist.hstudlist03.h にコピーしてから、prog03.cstudlist03.h を適宜修正して作成すること。今度はヘッダファイルも修正する必要があるので注意すること。

(提出ファイル名: prog03.c, studlist03.h)
  1. delete 関数を追加する。
    1. リストを削除する方法については、以下の説明を参考すること。
      説明はここ :この説明はハンドアウトハンドアウトLec09-24〜28 (pp.130-131) に記述があった)
      なお、どうしても分からない場合はスタッフ等に聞くこと。
    2. delete 関数のプロトタイプは
      NodePointer delete(int);
      とし、以下のような動作を行う。
      1. 引数で指定された数をID番号とみなし、リスト内の構造体変数 data のメンバー id が同じ値をもつノードを検索する
      2. 一致するノードが存在すればそのノードを削除し、 一つ前のノードポインタをリターンする
      3. 一致するノードが存在しなければ、 NULL をリターンする。
  2. studlist03.hdelete 関数のプロトタイプ宣言を追加する。
  3. 演習問題2のプログラムにおけるファイルからのデータ読み取りが済んだ後で、実行例のように対話的にノードの追加または削除を選んで実行可能になるように、main 関数を修正する。
実行例
% ./a.out
Head - 
  1301001 AIZU         Taro         19
  1301022 BANDAI       Hanako       22
  1301033 TSURUGA      Shirou       21
    .....(中略)
  1301150 KITAKATA     Testuo       18
  1301164 SUKAGAWA     Yoshiko      18
  1301200 ASHINOMAKI   Masaru       19
14 nodes exist in the list.

Choose operation (1: Insert, 0: Delete, ^D: Finish) -> 1
Insert new student data: (ID Surname Givenname Age) -> 1301100 IKKI Goro 18
Head -
  1301001 AIZU         Taro         19
  1301022 BANDAI       Hanako       22
  1301033 TSURUGA      Shirou       21
    .....(中略)
  1301150 KITAKATA     Testuo       18
  1301164 SUKAGAWA     Yoshiko      18
  1301200 ASHINOMAKI   Masaru       19
  1301100 IKKI         Goro         18
15 nodes exist in the list.

Choose operation (1: Insert, 0: Delete, ^D: Finish) -> 0
Delete ID -> 1209001
ID 1209001 is not found.
Head -
  1301001 AIZU         Taro         19
  1301022 BANDAI       Hanako       22
  1301033 TSURUGA      Shirou       21
    .....(中略)
  1301150 KITAKATA     Testuo       18
  1301164 SUKAGAWA     Yoshiko      18
  1301200 ASHINOMAKI   Masaru       19
  1301100 IKKI         Goro         18
15 nodes exist in the list.

Choose operation (1: Insert, 0: Delete, ^D: Finish) -> 0
Delete ID -> 1301022
Head - 
  1301001 AIZU         Taro         19
  1301033 TSURUGA      Shirou       21
    .....(中略)
  1301150 KITAKATA     Testuo       18
  1301164 SUKAGAWA     Yoshiko      18
  1301200 ASHINOMAKI   Masaru       19
  1301100 IKKI         Goro         18
14 nodes exist in the list.

Choose operation (1: Insert, 0: Delete, ^D: Finish) -> Ctrl-D
%
[エラー処理について]
この問題の実行例では、プログラムの基本的な想定通りの入力しか試していません。例えばノードの追加、削除、プログラムの終了を選ぶところで、それぞれの操作を指定するコマンドだけを試しています。しかし、開発者が予期していなかった操作を使用者がしてしまうこともあり得ますので、実用的なプログラムではそのような場合に備えてのエラー処理が重要になります。さらに、このプログラムではループを使って繰り返し処理をしているため、一つ前の操作で予期しない入力があると次の操作で問題が顕在化する場合も出てきます。特に、scanf関数ではキーボードからの入力が一旦格納させる入力バッファ(これはプログラム本体ではなくOSやターミナルが管理します)との相互作用で問題が生じます。この問題では上で指示された仕様が満たされていればよく、エラー処理については問いませんが、解答例ではそこまで考慮したものを準備していますので、より深く理解したい人は解答例の公開後に内容をよく見てみてください。
具体的にはChoose operationの入力部分で数値入力が必要とされるが、間違ってアルファベット等の文字が入力するとプログラムが暴走する。回答例ではエラー処理としてrewind()関数を使用プログラムを提示している。なお、今回の演習の回答としてrewind()関数を使用しなくても良いとする。

Extra問題

演習問題4(降順/昇順の挿入)

演習問題2のプログラムを改良して、ノードをIDの降順、または昇順に挿入する機能を追加する。

prog02.cprog04.c に、studlist03.hstudlist04.h にコピーしてから、prog04.cstudlist04.h を適宜修正して作成すること。

(提出ファイル名: prog04.c, studlist04.h)

基本的には、 プログラムとする。ただし、 ようにすること。
また、このプログラムではリストにデータを挿入する関数 NodePointer insert_sort(Record, char); を作成して利用せよ。 ここで、insert_sort 関数の 第一引数は入力ノードを示す引数、 第二引数は降順か昇順かを指定する引数である。
この関数は、リストの先頭からIDを調べていき、 に入力ノードを挿入する関数である。この関数を用いると指定された並べ替えが実現できるが、 リストの末尾の処理に気をつけて作成すること。
なお科目の指定と降順/昇順の指定は、実行例のように最初にキーボードから与えるものとする。

実行例
% ./a.out
Enter sort mode (d:descending / a:ascending) -> d
Head - 
  1301200 ASHINOMAKI   Masaru       19
  1301164 SUKAGAWA     Yoshiko      18
  1301150 KITAKATA     Testuo       18
  1301123 SOUMA        Hikaru       19
  1301099 IWAKI        Youko        18
    .....(中略)
  1301007 FUKUSHIMA    Ichiro       20
  1301004 BANGE        Jiro         18
  1301001 AIZU         Taro         19
14 nodes exist in the list.

Insert new student data: (ID Surname Givenname Age) -> 1301100 IKKI Goro 18
Head - 
  1301200 ASHINOMAKI   Masaru       19
  1301164 SUKAGAWA     Yoshiko      18
  1301150 KITAKATA     Testuo       18
  1301123 SOUMA        Hikaru       19
  1301100 IKKI         Goro         18
  1301099 IWAKI        Youko        18
    .....(中略)
  1301001 AIZU         Taro         19
15 nodes exist in the list.

Insert new student data: (ID Surname Givenname Age) -> Ctrl-D
%

% ./a.out
Enter sort mode (d:descending / a:ascending) -> a
Head - 
  1301001 AIZU         Taro         19
  1301004 BANGE        Jiro         18
  1301007 FUKUSHIMA    Ichiro       20
    .....(中略)
  1301150 KITAKATA     Testuo       18
  1301164 SUKAGAWA     Yoshiko      18
  1301200 ASHINOMAKI   Masaru       19
14 nodes exist in the list.

Insert new student data: (ID Surname Givenname Age) -> 1301003 INAWASHIRO Fumiko 18
Head - 
  1301001 AIZU         Taro         19
  1301003 INAWASHIRO   Fumiko       18
  1301004 BANGE        Jiro         18
  1301007 FUKUSHIMA    Ichiro       20
    .....(中略)
  1301150 KITAKATA     Testuo       18
  1301164 SUKAGAWA     Yoshiko      18
  1301200 ASHINOMAKI   Masaru       19
15 nodes exist in the list.

Insert new student data: (ID Surname Givenname Age) -> Ctrl-D
%