技術memo

関数型ゴースト

基礎プログラミング演習(1)

ご無沙汰してます。 ややあって、友人のプログラミング初心者2人とプログラミング勉強お泊まり会をしましたので、そのレポート的なものを残しておきます。

条件など

  • 2人はプログラミング初心者
  • 入門書の前半くらいまで読みかけ
  • 使用言語は、一人がC++、もう一人がC#
  • 何から書いたらいいのかわからないから、とにかく書いてみたいとのこと
  • 夜22時開始、朝までやれるだけやってみることにしました

進行

  1. 簡単に講義

  2. 演習問題(1)

    • BMI値を計算するCUIアプリを作ってみよう」という定番ネタを出題
    • 手書きで要件を箇条書きして説明、ハイ作ってと渡しました
    • 書いているところに逐次突っ込みを入れる、助け舟を出すなど適当に進行しました
    • 書き方を調べながらのため、2, 3時間ほど経過
    • C#のほうは問題なかったものの、私がC++を全くわからないため、実質的にはCで書いてもらうことになりました
      • クラス等が必要になるまでには勉強しておきます
    • 詰まりポイントは、主に型変換の類が多かった模様です
      • 「入力した文字列を整数に変換しなければいけないのですが?」
        • Cではscanfなので問題なし。C#ではConvert.ToInt32を解説しました
      • 「センチメートルで入力したのに計算はメートル単位です、どうしたらいいのでしょうか?」
        • double 身長_メートル = 身長/100;// 何故か小数点以下が切り捨てられてしまう
        • キャストを噛ませないとint型で割り算してしまいます
  3. 発展問題(1-1, 1-2)

    • 1-1「BMI値に応じて『やせ』『普通』『肥満』と表示しよう」
    • 1-2「数値以外が入力されたり、0や負の値が入力されたら、エラーと表示して再入力させよう」
    • 前者が条件分岐、後者がループ(while文)の演習なのは明白ですね
    • 分岐はすんなり解くものの、ループで二人とも頭を抱えていました
      • 「そもそも変な文字を入力したら、エラーになったりするのですが?」
        • Cでscanf("%d\n", &buf)を使うとここでハマります
        • C#だとint.Parseを使っているとエラーになります
      • 「エラーなら再入力、成功したら終了ってどう書けばいいのでしょうか?」
        • whileやbreakを調べてみましょう
    • プログラミング言語で書き始める前に、日本語で書いてもらったり、フローチャート風の図を描いてもらったり、とにかく手を動かして考えるのに重点をおいて進行しました
    • この時点で夜が明けそうになる(午前6時)ので、いったん就寝しました
    • 空いた時間で、私のほうでお手本プログラムを用意していました
    • 手続き的なべた書きで「とりあえず動く」プログラムができたところで、「関数を使って整理するとこんな感じ」とざっと解説して終了。お疲れ様でした。

問題文全文

演習問題1

  • コマンドラインで動くコンソールアプリ
  • exeファイルを実行すると「身長は?」と表示される
  • 整数(単位はセンチメートル)を入力して決定すると「体重は?」と表示される
  • 整数(単位はキログラム)を入力して決定すると「あなたのBMIはXXです」(XXは計算したBMI値)と表示され、アプリケーションが終了する
  • 注意点として、BMI値は小数点以下まで計算すること

演習問題1-1

演習問題1に加えて、以下の要件を満たせ

  • BMI値が18.5未満なら「痩せ」、25以上なら「肥満」、それ以外なら「普通」と判定する
  • BMI値を表示した後に「あなたは○○体型です」(○○は痩せ、肥満、普通のいずれか)と表示する

演習問題1-2

演習問題1-1に加えて、以下の要件を満たせ

  • 身長と体重を入力する際に、下記条件に当てはまらない場合「エラー。0より大きい整数を入力してください」と表示して、再度入力をさせること
  • もし体重の入力でエラーになったとしても、身長の値の入力には戻らず、体重の値を入力すればよいようにする

条件

  • 0より大きい整数であること
  • 例えば、0, -1, 50.5, aaaaaaなどはすべてエラー

作成したお手本プログラム(C言語)

#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include <stdbool.h>
#include <string.h>

// struct that express int or none value
typedef struct {
    bool isSome;
    int value;
} IntOrNone;

// create some int value
IntOrNone someInt(int v){
    IntOrNone r = { true, v };
    return r;
}

// create none int value
IntOrNone noneInt(){
    IntOrNone r = { false, 0 }; 
    return r;
}

// convert string to int
IntOrNone tryParseInt(char* s){
    char *end;
    long result = strtol(s, &end, 10);
    if(*end != '\0') return noneInt();
    if(result > INT_MAX || result < INT_MIN) return noneInt();
    return someInt((int)result);
} 

// validate input value
IntOrNone validate(char* s){
    IntOrNone x = tryParseInt(s);
    if(!x.isSome) return noneInt();
    if(!(x.value > 0)) return noneInt();
    return x;
}

// compute bmi value
double computeBmi(int h, int w){
    double hMeter = ((double)h)/100;
    return w/(hMeter*hMeter);
}

// define bmi type
typedef enum { Fat, Slim, Normal } BmiType;

// get string of bmi type
char* getBmiTypeName(BmiType t){
    switch(t){
        case Fat:
            return "肥満";
        case Slim:
            return "痩せ";
        default:
            return "普通";
    }   
}

// check bmi type by bmi value
BmiType checkBmiType(double bmi){
    if(bmi < 18.5) return Slim;
    if(bmi >= 25) return Fat;
    return Normal;
}

// write string to console 
void write(char* m){
    printf("%s", m); 
}

// read string by console
char* read(char* buf, int max){
    if(fgets(buf, max, stdin) == NULL){
        return "";
    }
    if(strchr(buf, '\0') != NULL){
        buf[strlen(buf) - 1] = '\0';
    }else{
        while(getchar() != '\n');
    }
    return buf;
}

// get correct integer value
int getInput(){
    char buf[100];
    while(true){
        IntOrNone x = validate(read(buf, 100));
        if(x.isSome) return x.value;
        write("error. 0より大きい整数を入力して下さい\n");
    }   
}

int main(){
    write("身長は?\n");
    int h = getInput();
    write("体重は?\n");
    int w = getInput();
    double bmi = computeBmi(h, w);
    char buf[100];
    sprintf(buf, "あなたのBMIは%fです\n", bmi);
    write(buf);
    sprintf(buf, "あなたは%s体型です\n", getBmiTypeName(checkBmiType(bmi)));
    write(buf);
}
  • CでもOptionが使いたかったのです
  • 学生の頃以来の、数年ぶりに書いたCですが、標準入出力と文字列処理がやはり面倒でした