工学システムとエ学システム。

前者は「こうがくしすてむ」、後者は「えがくしすてむ」
略称は「工シス(こうしす)」もしくは「エシス(えしす)」
英語表記は"esys"で読み方は「いーしす」もしくは「えしす」
狙ったとしか思えない。


ということで他学というか工シスの授業であるプログラミング序論をhaskellでといてみたところ検索キーワード「プログラミング序論」、利用ネットワーク「university of tsukuba」で来た方が。
というか、Google先生で「プログラミング序論」をぐぐってみたら二番目にうちのblogとかそれどういう...
google:プログラミング序論
はてなページランク高すぎ...。

#追記:とか思ったら一気に下がりました。これが噂のGoogleダンスか(違


ということはここでプログラミング序論の課題について書いておくと結構有用?
あーでもあの授業、課題写した側も写された側も両成敗的なノリなんだよね。
ということで、課題の答えそのままでは無く、ちょいとした解説とかどうでしょう?


ということで自意識過剰ぎみに、かつ立場わきまえずに、それでいて超勘違い気味に解説開始。
かなり初級。

課題11

Lv.1の課題。
なんでも引数を表示するだけ。
ポイントとしては

  • 画面に何か出力したい時はprintf
  • printfを使うためには最初に「#include 」と書くべし。おまじないみたいなもの。
  • 「int main( int argc, char** agrv)」を使おう。でもあの先生的には「char* argv[]」がおすすめ*1
  • 引数へのアクセスはargv[0],argv[1]...を使う。
  • argv[0]はプログラムの名前がはいるので、実質argv[1]から。
  • 引数の個数はargcにはいってる。
  • なんだけど、argvの添字*2の始まりは「0から」*3だからargvのargc番目にはアクセスしちゃ駄目。
  • ということでループは「for( i = 1; i < argc; i++ )」を使おう。定型文みたいなものなので覚えておこう。
  • 「文字列」を出力するのには「printf」の第一引数に「"%s"」を使うべし。別に文字列を一個一個char型に分解してもいいけどめんどい。
  • 例えばargv[i]の中身を表示したい場合には「printf("%s\n", argv[i]);」で*4

あたりか。
ほとんど答えというね。
まぁ、そもそも課題自体かなり楽だし。


で、これだけで1ポイントゲットなわけだけど、簡単なうちにポイントをとっておきたい、って人のため課題に12,13も解説。

課題12

Lv.2の課題。
引数の総文字数を表示する。
ポイント

  • 例のごとく「#include 」をお忘れ無く。
  • で、今回は「#include 」も書いておくと幸せ。
  • 文字数を貯めておくために何か変数を宣言しておくべし。
  • 変数はlenでもlengthでもtotalでもなんでもいいけど初期化を忘れないように。
  • で、例のごとく「for( i = 1; i< argc; i++ )」で引数を一個一個見ていきます。
  • 文字列を数えるのは「strlen」が便利(「#include 」はこのため。
  • argv[i]の文字数を調べたかったら「strlen( argv[i] )」でおっけー。
  • 具体的には「length += stelen( argv[i] );」で。
  • 文字数を調べただけでは意味が無いのでちゃんと最後に「printf」しておこう。
  • 数字を出力する時は「"%d"」だ。

ほとんどこt(略
まぁ、そもそm(略


strlenがなんか黒魔術っぽくて使いたくない、って人は自前でやってもおっけー。
その場合は「#include 」は入らぬ。

char* c;
for( c = argv[i]; *c != '\0'; c++ ) {
	length++;
}

な感じで。ポインタについて一通り理解しているなら分かるハズ。
ポインタは嫌いだ!って人はこういう方法もあり

int j;
for( j = 0; argv[i][j] != '\0'; j++ ) {
	length++;
}

ちなみにこのループが終わったあとのjがstrlenの正体だったり*5


そういや気になったのだがいくら非情報学類だからといって、さすがにもう二年だし、プログラミングの基礎の基はやってるわけであって、じつはこのエントリかなり無駄?
というか「そんな言われなくても知ってるよ!!」的な説明だったりするのかなぁ。
まぁ気にしない。
上にも書いたとおり勘違い気味なエントリなんで。

課題13

Lv.3の課題
オプションの出現回数を調べる。
前の二問に比べるとやや難易度アップ。
とはいえまだまだかなり初級。


なのだが情報学類のうちの友人が悩んでいたのが印象的。


問題自体が理解できない、ってひともいるだろうのでまずは実行例

$ ./a.out -ivh abc.rpm -rf dir1 -lvF *.c
F:1
f:1
h:1
i:1
l:1
r:1
v:2

こんな感じの挙動。

  • "-"から始まってたら「オプション」。
  • オプションはいくつかの英字でなりたっていて、それぞれ個別にカウントしていく。
  • つまり"-abc"と"-a -b -c"は同じ。
  • で、"-"から始まってない場合は無視。
  • 今回の例では"ivh"*6と"rf"*7と"lvF"*8がオプション。
  • あと英字は大文字/小文字の区別があります。

まとめるとこんな感じ。


ということで今回はポイントでまとめずにある程度順を追って解説。

まず、'a'とか'b'とか、それぞれが何回出てきたかを覚えておくための変数が必要となる。
なるからといってこういうのはNG

int aCount = 0;
int bCount = 0;
int cCount = 0;
int dCount = 0;
/* 略 */
int ZCount = 0;

日が暮れます。
いや、やってもいいけど...修行だよ?


ということで配列を使います。
使うのですが、どう使うか。
それは個人個人の好みですが、一例をば。


とりあえず今回の課題はオプションはa..zA..Zのどれかと決まってるっぽいんので(明言はしてないが)とりあえず26*2=52個の要素を持つ配列を用意しておけばいいことが分かります。
ということで

int counts[52];

としてみましょうか。


そもそも、配列を確保したところで英字と添字をどう対応させていけばいいか、つまり、'a'の個数はcounts[0]に、'b'の個数はcounts[1]に...'Z'の個数はcount[51]にという「割り当て」をどうすればよいのか。
そこをクリアしないといけません。


さて、'a'はchar型なわけですが、実は、というかご存知のとおりchar型というのは数字です。
具体的には'a'は97(16進数では0x61), 'b'は98(同0x62) ... 'z'は122(同0x7a)と言う風になっています。
ということは'a'の時counts[0]を、'b'の時counst[1]を増やしたい、という場合には次のようにすればいいことがわかります。

char c = 'a'; /* とか'b'とか */
counts[ c - 97 ]++;

さて、これでおっけーかというと実は駄目で、大文字になると番号が飛びます。
というか、大文字の方が番号が小さいのです。
'A'は65(=0x41), 'B'は66(=0x42) ... 'Z'は90(=0x5a)となります。
ということは大文字と小文字で分けて分けてやらねばならないのです。
ということでこんな感じ

char c = 'a'; /* とか 'b' とか 'A' とか... */
if( 97 <= c && c <= 122 ) {
	/* アルファベット小文字 */
	counts[ c - 97 ]++;
} else if( 65 <= c && c <= 90 ) {
	/* アルファベット大文字 */
	counts[ c - 65 + 26 ]++;
}

ちょっと暗号文になってきました。97とか122とか、まったく説明無しだと何ななんのことやら、となってしまいます。
こういうのを「マジックナンバー」とかいい、忌み嫌われるものであったりします。
こういう場合には一般的には「int smallANum = 97」とか変数にしたり「マクロ」というものを使ったりして名前をつけておくのですが、ここではもっといい方法があります。
先ほども説明したとおり、char型は数字です。ということで

int numAlphabets = 26;
char c = 'a'; /* とか 'b' とか 'A' とか... */
if( 'a' <= c && c <= 'z' ) {
	/* アルファベット小文字 */
	counts[ c - 'a' ]++;
} else if( 'A' <= c && c <= 'Z' ) {
	/* アルファベット大文字 */
	counts[ c - 'A' + numAlphabets ]++;
}

こんな感じになります。やや分かりやすくなったのではないでしょうか。
あと大文字,小文字を一緒の配列で数えてやる義理もないのですからこういう書き方もできます。

int smallCounts[26];
int largeCounts[26];
/* smallCountsとlargeCountsの初期化 */
char c = 'a'; /* とか'b'とか... */
if( 'a' <= c && c <= 'z' ) {
	smallCounts[ c - 'a' ]++;
} else if( 'A' <= c && c <= 'Z' ) {
	largeCounts[ c - 'A' ]++;
}

出力してやる時も注意が必要です。
なにも考えずにprintfすると大変なことになるの注意

for( i = 0; i < 26; i++ ) {
	printf( "%d:%d\n", i, smallCounts[i] );		/* "0:1","1:0","2:1"のように数字が表示されてしまう。 */
	printf( "%c:%d\n", i, smallCounts[i] );		/* 色々あってちょびっとハッピーになれる。やらないように。 */
	printf( "%c:%d\n", i+'a', smallCounts[i] );	/* 正解。 */
}


あと、いっそのことすべてをカウントしてしまう方法もあります。
つまりどういうことかと言うと、char型の変数は256種類の数字しか表すことしかできません。
多分プログラミング入門とかでやったとは思いますが、符号付きだと-128〜127、符号無しでも0〜255までしか数えられません。
ということは、256個の要素を持つ配列を用意してやって、cの値をそのまんま添字にしちゃう方法もあるのです。

int counts[256];
unsigned char c = 'a'; /* とかとか */
counts[ c ]++;

"unsigned"はおまじないだと思ってください。


これで数え終わったら必要な部分だけを持ってくる
つまり

/* まず小文字 */
for( i = 'a'; i <= 'z'; i++ ) {
	printf( "%c:%d\n", i, counts[i] );
}
/* 続いて大文字 */
for( i = 'A'; i <= 'Z'; i++ ) {
	printf( "%c:%d\n", i, counts[i] );
}

こんなんでおっけー。


さて、数える部分はおっけー。
あとはアウトライン。

  • 例のごとく「for( i = 1; i < argc; i++ )」で引数を一個ずつ見ていく。
  • まず引数がオプションかどうかを調べないといけない。
  • オプションかどうかの判断は一文字目が'-'かどうか。
  • 一文字目のアクセスはargv[i][0]でできる。のでifの中に打ち込む。
  • 「== '-'」なら処理をする、とか逆に「!= '-'」ならcontinueとか、まぁ方法は色々。
  • オプションの処理はもう一回ループ。
  • ポインタが得意なら「for( c = &argv[i][1]; *c != '\0'; c++ )」
  • ポインタが苦手なら「for( j = 0; argv[i][j] != '\0'; j++)」
  • などでループして、"*c"なり"argv[i][j]"を↑までにでてきたcの変わりに使ってやれば完成

こんな感じですかねぇ。


文章拙いので分かりづらいとは思いますが、丸写しでなくて、ちゃんと理解して、自分で書いてくださいな。
gOthは自助努力を応援します。



...こんなエントリ役に立つのか?



あ、あと書いてあるソースはコンパイラに通してないので、タイプミスとか根本的な勘違いとかありうるんで気をつけてくださいな。

*1:実質同じなので別にchar**でもいいのだけど...

*2:argv[i]のiのこと。[]の中の数字

*3:ゼロオリジンという

*4:"\n"は改行を示す

*5:色々正確にいうと違う

*6:うちがよく使うrpmのオプション

*7:ディレクトリまるごと削除の際にrmに用いる

*8:lsのオプション。aliasでllとかにしておくとハッピー。