2 部データ構造とアルゴリズムI レポート課題

本講義では 2 通のレポートで評価します。 レポートは A4 縦の様式で PDF 形式で作成して下さい。 またプログラムは C 言語で作成しなさい。

変更履歴

2020/6/11
課題1初出
2020/6/26
課題1 Makefile修正
2020/6/27
課題1 追加テスト
2020/7/16
課題2初出
2020/7/16
課題1eq 関数仕様変更

課題2

レポート締切日: 2020年8月5日(水)23:59
提出先: BOX にファイル名を 学籍番号-2.pdfとして、 アップロードすること

解答法として、分割コンパイルによる別ファイルにプログラムを書く方 法と、下記のテストプログラムに関数を追記する方法の二通りがあるが、 どちらでも良い。 プログラムの解説は、自分で作成した関数について行うこと。 こちらで提供したテストプログラムの説明は基本的には不要である。 但し、自分で作成した関数を説明するのに必要な内容には言及すること。

また、課題1で作成した関数については説明無しで使用して構わない。

なお、締め切り前は何度でも提出可能だが、締切後の提出は受け付けない。 複数の提出があった場合は、締切直前のファイルを有効とする。

問題

文字列でラベルの付いた数値を、線形リストで管理し、ラベル毎に集計し、出力する。

始めに課題1で扱った ITEM 型を持つ線形リストを定義するため、 ITEMLIST 型を下記のように定める。


typedef struct itemlist {
  ITEM item;
  struct itemlist* next;
} ITEMLIST;

また、本課題において、next が NULL のノードを線形リストの番兵とする。 そのノードの item には何も入れないこととする。

本問題を解くにあたり、ヘッダファイル itemlist.h の他、テストデータを定義 したファイル data21.h と、 Makefile を後に与える。 また、課題1で提供、作成したファイルは説明無く使用する。

問題2-1

ITEMLIST の線形リストのポインタを与えると内容を表示する void printList(ITEMLIST *p) を作成せよ。 但し、ファイル名を printlist.c とする。 また、課題1で作成した関数を使用しても良い。

これと、下記のテストプログラムなどを結合して実行し、出 力結果を示せ。

mondai21.c


#include "itemlist.h"
#include "data21.h"
int main(void){
  printList(&d7);
  return 0;
}
出力例2-1
tokushima: 0
tokyo: 1
chiba: 3
tokyo: 2
chiba: 4
kanagawa: 3
tokyo: 5

問題2-2

malloc 関数を使用して、 ITEMLIST 型の初期化した領域のポインターを一つ得る ITEMLIST* newList(void) を作成せよ。 但し、メモリが確保できなかった時は NULL を返すこと。 この関数のファイル名を newlist.c とする。 また、課題1で作成した関数を使用しても良い。

これと、下記のテストプログラムなどを結合して実行し、出 力結果を示せ。

mondai22.c


#include <stdio.h>
#include "itemlist.h"
#include "data21.h"
#define SIZE sizeof(data)/sizeof(data[0])

int main(void){
  ITEMLIST *d[SIZE];
  ITEMLIST *prev;
  int i,j;
  prev=NULL;
  for(i=0;i<SIZE;i++){
    if((d[i]=newList())==NULL){
      fprintf(stderr,"メモリーエラー\n");
      for(j=i-1;j>=0;j--){
	free(d[j]);
      }
      return 1;
    }
    d[i]->item=data[i];
    d[i]->next=prev;
    prev=d[i];
  }
  printList(d[SIZE-1]);
  for(i=SIZE-1;i>=0;i--){
    free(d[i]);
  }
  return 0;
}
出力例2-2
(null): 0
tokushima: 0
tokyo: 1
chiba: 3
tokyo: 2
chiba: 4
kanagawa: 3

問題2-3

ITEMLIST の線形リストのポインタを与えると、ポインタの先の線形リスト の領域を free で開放する void delList(ITEMLIST* p) を作成せよ。 但し、ITEM 中の文字列のメモリは開放しなくて良い。 この関数のファイル名を dellist.c とする。 また、課題1で作成した関数を使用しても良い。

これと、下記のテストプログラムなどを結合して実行し、出 力結果を示せ。

mondai23.c


#include <stdio.h>
#include "itemlist.h"
#include "data21.h"
#define SIZE sizeof(data)/sizeof(data[0])-1

int main(void){
  ITEMLIST *d[SIZE];
  ITEMLIST *prev;
  int i,j;
  prev=NULL;
  for(i=0;i<SIZE;i++){
    if((d[i]=newList())==NULL){
      fprintf(stderr,"メモリーエラー\n");
      if(i>0){
	delList(d[i-1]);
      }
      return 1;
    }
    d[i]->item=data[i];
    d[i]->next=prev;
    prev=d[i];
  }
  printList(d[SIZE-1]);
  delList(d[SIZE-1]);
  return 0;
}
出力例2-3
tokushima: 0
tokyo: 1
chiba: 3
tokyo: 2
chiba: 4
kanagawa: 3

問題2-4

ITEMLIST の線形リストのポインタと ITEM の要素 を与えると、線形リストの先頭にその要素を追加する ITEMLIST* add(ITEMLIST* p, ITEM it) を作成せよ。 但し、この関数は追加した要素のポインタを返すこととし(pと同じ)、 領域が確保できなかった時は NULL を返すとする。 この関数のファイル名を add.c とする。 また、課題1で作成した関数を使用しても良い。

これと、下記のテストプログラムなどを結合して実行し、出 力結果を示せ。

mondai24.c


#include <stdio.h>
#include "itemlist.h"
#include "data21.h"
#define SIZE sizeof(data)/sizeof(data[0])-1

int main(void){
  ITEMLIST *p;
  int i,j;

  if((p=newList())==NULL){
      fprintf(stderr,"メモリーエラー\n");
      return 1;
  }
  for(i=0;data[i].name!=NULL;i++){
    if(add(p,data[i])==NULL){
      fprintf(stderr,"メモリーエラー\n");
      return 1;
    }
  }
  printList(p);
  
  delList(p);
  return 0;
}
出力例2-4
tokushima: 0
tokyo: 1
chiba: 3
tokyo: 2
chiba: 4
kanagawa: 3
tokyo: 5

問題2-5

ITEMLIST の線形リストのポインタと ITEM の要素 を与えると、線形リストの中を検索し、ITEM の nameが同じ要素のポインター を返す ITEMLIST* findList(ITEMLIST* p, ITEM it) を作成せよ。 但し、発見できなかった場合は NULL を返すものとする。 この関数のファイル名を findlist.c とする。 また、課題1で作成した関数を使用しても良い。

これと、下記のテストプログラムなどを結合して実行し、出 力結果を示せ。

mondai25.c


#include <stdio.h>
#include "itemlist.h"
#include "data21.h"

int main(void){
  ITEMLIST *p;
  ITEMLIST *q;
  int i,j;

  if((p=newList())==NULL){
      fprintf(stderr,"メモリーエラー\n");
      return 1;
  }
  for(i=0;data[i].name!=NULL;i++){
    if(add(p,data[i])==NULL){
      fprintf(stderr,"メモリーエラー\n");
      delList(p);
      return 1;
    }
    printf("list ");
    printList(p);
    printf("-----------\n");
    for(j=0;data[j].name!=NULL;j++){
      printf("item ");
      printItem(&data[j]);
      q=findList(p,data[j]);
      printf("target ");
      if(q==NULL){
	printf("NULL\n");
      }else{
	printItem(&(q->item));
      }
      printf(".\n");
    }
    printf("==============\n");
  }
  delList(p);
  return 0;
}
出力例2-5
list tokyo: 5
-----------
item tokyo: 5
target tokyo: 5
.
item kanagawa: 3
target NULL
.
item chiba: 4
target NULL
.
item tokyo: 2
target tokyo: 5
.
item chiba: 3
target NULL
.
item tokyo: 1
target tokyo: 5
.
item tokushima: 0
target NULL
.
==============
list kanagawa: 3
tokyo: 5
-----------
item tokyo: 5
target tokyo: 5
.
item kanagawa: 3
target kanagawa: 3
.
item chiba: 4
target NULL
.
item tokyo: 2
target tokyo: 5
.
item chiba: 3
target NULL
.
item tokyo: 1
target tokyo: 5
.
item tokushima: 0
target NULL
.
==============
list chiba: 4
kanagawa: 3
tokyo: 5
-----------
item tokyo: 5
target tokyo: 5
.
item kanagawa: 3
target kanagawa: 3
.
item chiba: 4
target chiba: 4
.
item tokyo: 2
target tokyo: 5
.
item chiba: 3
target chiba: 4
.
item tokyo: 1
target tokyo: 5
.
item tokushima: 0
target NULL
.
==============
list tokyo: 2
chiba: 4
kanagawa: 3
tokyo: 5
-----------
item tokyo: 5
target tokyo: 2
.
item kanagawa: 3
target kanagawa: 3
.
item chiba: 4
target chiba: 4
.
item tokyo: 2
target tokyo: 2
.
item chiba: 3
target chiba: 4
.
item tokyo: 1
target tokyo: 2
.
item tokushima: 0
target NULL
.
==============
list chiba: 3
tokyo: 2
chiba: 4
kanagawa: 3
tokyo: 5
-----------
item tokyo: 5
target tokyo: 2
.
item kanagawa: 3
target kanagawa: 3
.
item chiba: 4
target chiba: 3
.
item tokyo: 2
target tokyo: 2
.
item chiba: 3
target chiba: 3
.
item tokyo: 1
target tokyo: 2
.
item tokushima: 0
target NULL
.
==============
list tokyo: 1
chiba: 3
tokyo: 2
chiba: 4
kanagawa: 3
tokyo: 5
-----------
item tokyo: 5
target tokyo: 1
.
item kanagawa: 3
target kanagawa: 3
.
item chiba: 4
target chiba: 3
.
item tokyo: 2
target tokyo: 1
.
item chiba: 3
target chiba: 3
.
item tokyo: 1
target tokyo: 1
.
item tokushima: 0
target NULL
.
==============
list tokushima: 0
tokyo: 1
chiba: 3
tokyo: 2
chiba: 4
kanagawa: 3
tokyo: 5
-----------
item tokyo: 5
target tokyo: 1
.
item kanagawa: 3
target kanagawa: 3
.
item chiba: 4
target chiba: 3
.
item tokyo: 2
target tokyo: 1
.
item chiba: 3
target chiba: 3
.
item tokyo: 1
target tokyo: 1
.
item tokushima: 0
target tokushima: 0
.
==============

問題2-6

ITEMLIST の線形リストのポインタと ITEM の要素 を与えると、ITEM の name 毎に数を集計する ITEMLIST* totalList(ITEMLIST* p, ITEM it) を作成せよ。 但し、この関数は集計先の要素のポインタを返すこととし(pと同じ)、 領域が確保できなかった時は NULL を返すとする。 この関数のファイル名を totallist.c とする。 また、課題1で作成した関数を使用しても良い。

これと、下記のテストプログラムなどを結合して実行し、出 力結果を示せ。

mondai26.c


#include <stdio.h>
#include "itemlist.h"
#include "data21.h"

int main(void){
  ITEMLIST *p;
  int i;

  if((p=newList())==NULL){
      fprintf(stderr,"メモリーエラー\n");
      return 1;
  }
  for(i=0;data[i].name!=NULL;i++){
    printf("item ");
    printItem(&data[i]);
    if(totalList(p,data[i])==NULL){
      fprintf(stderr,"メモリーエラー\n");
      delList(p);
      return 1;
    }
    printList(p);
    printf("-----------\n");
  }
  delList(p);
  return 0;
}
出力例2-6
tem tokyo: 5
tokyo: 5
-----------
item kanagawa: 3
kanagawa: 3
tokyo: 5
-----------
item chiba: 4
chiba: 4
kanagawa: 3
tokyo: 5
-----------
item tokyo: 2
chiba: 4
kanagawa: 3
tokyo: 7
-----------
item chiba: 3
chiba: 7
kanagawa: 3
tokyo: 7
-----------
item tokyo: 1
chiba: 7
kanagawa: 3
tokyo: 8
-----------
item tokushima: 0
tokushima: 0
chiba: 7
kanagawa: 3
tokyo: 8
-----------

ヘッダファイル、 Makefile

itemlist.h


#include "item.h"
typedef struct itemlist {
  ITEM item;
  struct itemlist* next;
} ITEMLIST;
void printList(ITEMLIST* p);
ITEMLIST* newList(void);
void delList(ITEMLIST* p);
ITEMLIST* add(ITEMLIST* p, ITEM item);
ITEMLIST* findList(ITEMLIST* p, ITEM item);
ITEMLIST* totalList(ITEMLIST* p, ITEM it);

data21.h


#include <stdlib.h>
#include "data2.h"

ITEMLIST d0={{0},NULL};
ITEMLIST d1={{t1,5},&d0};
ITEMLIST d2={{"kanagawa",3},&d1};
ITEMLIST d3={{"chiba",4},&d2};
ITEMLIST d4={{t2,2},&d3};
ITEMLIST d5={{"chiba",3},&d4};
ITEMLIST d6={{"tokyo",1},&d5};
ITEMLIST d7={{t3,0},&d6};

Makefile


CC=gcc
mondai26.exe:	mondai26.o
mondai26.exe:	printitem.o  eq.o
mondai26.exe:	printlist.o newlist.o dellist.o add.o findlist.o totallist.o
	$(CC) -o $@ $^
mondai25.exe:	mondai25.o
mondai25.exe:	printitem.o  eq.o
mondai25.exe:	printlist.o newlist.o dellist.o add.o findlist.o
	$(CC) -o $@ $^
mondai24.exe:	mondai24.o
mondai24.exe:	printitem.o
mondai24.exe:	printlist.o newlist.o dellist.o add.o
	$(CC) -o $@ $^
mondai23.exe:	mondai23.o
mondai23.exe:	printitem.o 
mondai23.exe:	printlist.o newlist.o dellist.o
	$(CC) -o $@ $^
mondai22.exe:	mondai22.o
mondai22.exe:	printitem.o
mondai22.exe:	printlist.o newlist.o
	$(CC) -o $@ $^
mondai21.exe:	mondai21.o
mondai21.exe:	printitem.o
mondai21.exe:	printlist.o
	$(CC) -o $@ $^
clean:
	rm	*.o *.exe

課題1

レポート締切日: 2020年7月1日(水)23:59
提出先: BOX にファイル名を 学籍番号-1.pdfとして、 アップロードすること

解答法として、分割コンパイルによる別ファイルにプログラムを書く方 法と、下記のテストプログラムに関数を追記する方法の二通りがあるが、 どちらでも良い。 プログラムの解説は、自分で作成した関数について行うこと。 こちらで提供したテストプログラムの説明は基本的には不要である。 但し、自分で作成した関数を説明するのに必要な内容には言及すること。

なお、レポートは締め切り後も提出できます。 同じファイル名で複数のバージョンが存在できます。 締め切り内に採点可能なレポートがある場合は、最も締切に近いものだけを採 点します。 締め切り内に有効なレポートがない場合、最も新しいレポートを遅れレポー トとして減点して採点します。

問題

文字列でラベルの付いた数値を、ラベル毎に集計し、出力する。

始めにラベル付きの数値のデータ型 ITEM を下記のように定める。


typedef struct{
  char* name;
  int num;
} ITEM;

name メンバはラベルを表す文字列、num メンバは数値を意味する。

本問題を解くにあたり、ヘッダファイル item.h の他、テストデータを定義 したファイル data.h と、 Makefile を後に与える

問題1-1

ITEM の変数のポインタを与えると内容を表示する void printItem(ITEM *p) を作成せよ。 但し、ファイル名を printitem.c とする。

これと、下記のテストプログラム, item.h, data.h を結合して実行し、出 力結果を示せ。

mondai11.c


#include "item.h"
#ifndef DATAFILE
#include "data.h"
#endif
#if DATAFILE == 2
#include "data2.h"
#endif

int main(void){
  printItem(&data[0]);
  printItem(&data[1]);
  printItem(&data[2]);
  printItem(&data[3]);
  return 0;
}
出力例1-1
tokyo: 5
kanagawa: 3
chiba: 4
tokyo: 2

問題1-2

ITEM の配列を与えると全て内容を表示する関数 void printItems(ITEM *p) を作成せよ。 但し、ITEM 配列の最後には番兵として、 name メンバが NULL である要素が 含まれているとする。

mondai12.c


#include "item.h"
#ifndef DATAFILE
#include "data.h"
#endif
#if DATAFILE == 2
#include "data2.h"
#endif

int main(void){
  printItems(data);
  return 0;
}
出力例1-2
tokyo: 5
kanagawa: 3
chiba: 4
tokyo: 2
chiba: 3
tokyo: 1

問題1-3

ITEM の入っている2つの変数のポインタが与えられた時、 どちらかが一方が NULL なら 0とし、 それぞれの name メンバが一致していたら 1, 一致してなかったら 0 を返 す関数 int eq(ITEM* a, ITEM* b) を作成せよ。 なお、作成にあたって、講義で取り上げてない strcmp 関数を説明なしに 使用しても良い。

mondai13.c


#include <stdio.h>
#include <stdlib.h>
#include "item.h"
#ifndef DATAFILE
#include "data.h"
#endif
#if DATAFILE == 2
#include "data2.h"
#endif

int main(void){
  ITEM *p;
  ITEM *q;
  for(p=data; p->name != NULL; p++){
    for(q=data; q->name != NULL; q++){
      printf("%d ",eq(p,q));
    }
    printf("\n");
  }
  return 0;
}
出力例1-3
1 0 0 1 0 1 
0 1 0 0 0 0 
0 0 1 0 1 0 
1 0 0 1 0 1 
0 0 1 0 1 0 
1 0 0 1 0 1 

問題1-4

ITEM の配列 dic に対して、 ITEM の変数 key の name メンバと一致してい る要素を検索し、存在すればそのdic 内のポインタを、検索中に name が NULL である番兵まで到達すればその番兵のポインタを返す関数 ITEM* find(ITEM* dic, ITEM* key) を作成せよ。 なお、作成にあたって、 1-3 で作成した eq 関数を使用して良い。

mondai14.c


#include <stdlib.h>
#include "item.h"
#ifndef DATAFILE
#include "data.h"
#endif
#if DATAFILE == 2
#include "data2.h"
#endif

int main(void){
  ITEM data2={"ibaragi",1};
  ITEM* p;
  for(p=data; p->name != NULL; p++){
    printItem(find(data,p));
  }
  printItem(find(data,&data2));
  return 0;
}
出力例1-4
tokyo: 5
kanagawa: 3
chiba: 4
tokyo: 5
chiba: 4
tokyo: 5
(null): 0

問題1-5

ITEM の集計甩配列 dic と、ITEM の配列である入力データ data を与えると、 data の各要素に対して、 name メンバ毎に num を集計する関数 void total(ITEM* dic, ITEM* data) を作成せよ。 なお、 1-4 の find などの、各問題で既に作成した関数は自由に説明なしに 利用して良い。

mondai15.c


#include "item.h"
#ifndef DATAFILE
#include "data.h"
#endif
#if DATAFILE == 2
#include "data2.h"
#endif

int main(void){
  ITEM result[48]={0};
  total(result,data);
  printItems(result);
  return 0;
}
出力例1-5
tokyo: 8
kanagawa: 3
chiba: 7

item.h


typedef struct {
  char *name;
  int num;
} ITEM;

void printItem(ITEM* p);
void printItems(ITEM* p);
int eq(ITEM* a, ITEM* b);
ITEM* find(ITEM* dic, ITEM* key);
void total(ITEM* dic, ITEM* data);

data.h


#include <stdlib.h>
ITEM data[]={{"tokyo",5},{"kanagawa",3},{"chiba",4},{"tokyo",2},{"chiba",3},{"tokyo",1},{NULL,0}};

Makefile


CC=gcc
mondai15.exe:	mondai15.o total.o find.o eq.o printitems.o printitem.o
	$(CC) -o $@ $^
mondai14.exe:	mondai14.o find.o eq.o printitems.o printitem.o
	$(CC) -o $@ $^
mondai13.exe:	mondai13.o eq.o
	$(CC) -o $@ $^
mondai12.exe:	mondai12.o printitems.o printitem.o
	$(CC) -o $@ $^
mondai11.exe:	mondai11.o printitem.o
	$(CC) -o $@ $^
clean:
	rm *.o *.exe


mondai15.o: item.h data.h
mondai14.o: item.h data.h
mondai13.o: item.h data.h
mondai12.o: item.h data.h
mondai11.o: item.h data.h
printitem.o: item.h
printitems.o: item.h printitem.c
eq.o: item.h
find.o: item.h eq.c
total.o: item.h find.c

追加テスト(2020/6/27)

学生からの質問を受けた結果、mondai11.cからmondai15.cに関して、問題通 りに作らな くても同じ結果例になるものが容易に作れることがわかった。 そのため、mondai11.cからmondai15.cまでに変更を加えた。 但し、変更前、変更後とも、コンパイルオプションを加えない限りは同じプ ログラムが生成される。

課題の目標は設問のとおりにプログラムを作ることであり、実行結果が等し いプログラムであれば全てが合格するわけではない。 その意味で、この追加テストは必須ではなく、レポートに反映しなくても良 い。 一方、理論的に、完璧なプログラムテストは存在しないので、プログラムテ ストは常に目安に過ぎない。 その意味で、よりよいプログラムテストを使うことには意味がある。 ブラックボックステストでは、通らないのはダメであるが、ダメなプログラム でも通ってしまうものがある。

追加テストデータ data2.h


#include <stdlib.h>
char t1[]="tokyo";
char t2[]="tokyo";
char t3[]="tokushima";

ITEM data[]={{t1,5},{"kanagawa",3},{"chiba",4},{t2,2},{"chiba",3},{"tokyo",1},{t3,0},{NULL,0}};

テスト手順

  1. 上記data2.h をファイルとして保存する
  2. monda11.c, monda12.c, monda13.c, monda14.c, monda15.c, Makefile を修正する
  3. make clean を実行
  4. 追加テストを行うには、次のようにして make コマンドを実行する。 あるいは、単独で gcc を使う場合は次のようにする。この場合、それぞれ a.exe が作られるが、実行ファイル名を指定した場合は、さらに -o mondai11.exe などと指定する。

追加データによる実行結果

mondai11.exe(変化なし)

tokyo: 5
kanagawa: 3
chiba: 4
tokyo: 2

mondai12.exe

tokyo: 5
kanagawa: 3
chiba: 4
tokyo: 2
chiba: 3
tokyo: 1
tokushima: 0

mondai13.exe

1 0 0 1 0 1 0 
0 1 0 0 0 0 0 
0 0 1 0 1 0 0 
1 0 0 1 0 1 0 
0 0 1 0 1 0 0 
1 0 0 1 0 1 0 
0 0 0 0 0 0 1

mondai14.exe

tokyo: 5
kanagawa: 3
chiba: 4
tokyo: 5
chiba: 4
tokyo: 5
tokushima: 0
(null): 0

mondai15.exe

tokyo: 8
kanagawa: 3
chiba: 7
tokushima: 0

レポート作成上の注意

  1. レポートはグラフの表現など特殊な事情がない限り、必ず白黒で作成する こと。
  2. プログラミングのレポートでは必ずプログラムの説明をすること。その時 に、一行一行を日本語に直訳するのではなく、プログラムを機能毎に区分し、 機能の実現方法を説明すること。 プログラムに一行ずつコメントを入れてもプログラムの説明とは見なしません。
  3. 「問題を解きなさい」という問に対して「解きました。合ってました」は 正解ではありません。 「プログラムを作りなさい」という問題については、作成の手順の説明をする こと。 「テストしなさい」という問題についてはテストの手法、合否判定の基準、結 果の検討などを説明しなさい。
  4. プログラムが短くて説明がしづらい場合は、ポインタなどの関係を図示す るなどして工夫してください。
  5. 出力例は個々に異なりますので、手計算であらかじめ正しい値の求め方を 示し、値を提示してテストの合否を判定しなさい。
  6. レポートは手書きでも良いですが、プログラムの実行結果だけは必ずコン ピュータの出力の印刷にして下さい。
  7. 考察は必ず書いて下さい。ネタがない人は以下を参考にして下さい。

坂本直志 <[email protected]>
東京電機大学工学部情報通信工学科