CodeIQ:進捗どうですか?

私自身が表題の問題を解いた時のプログラムについて解説します。
問題の詳細は「進捗どうですか?」(CodeIQ)を参照してください。

問題の概要

次のように1行でパラメータが入力されます。

99,33,>

パラメータを「X,Y,C」とするとYがXの何パーセントかを計算(端数切り捨て)して、その数だけだけCを出力せよ、と言う問題です。上記の例なら「>」が30個並びます。

私のプログラム

C言語で解答しています。

#include <stdio.h>

void calcProgress(unsigned long total, unsigned long now, char c){
	unsigned long p = 0;
	int i=0;

	if(total < now){
		printf("invalid");
		return;
	}

	p = (100*now)/total;
	for(i=0; i<p; i++){
		putc(c, stdout);
	}
	return;
}

int main(int argc, char* argv[]){
	unsigned long now = 0;
	unsigned long total = 0;
	char c;
	char str[1024] = {0};

	while( fgets(str, sizeof(str), stdin) != NULL ){
		sscanf(str, "%lu,%lu,%c", &total, &now, &c);
		calcProgress(total, now, c);
	}

	return 0;
}

解説

特に解説するところがあるようなプログラムではありませんが、それでは何にもならないので解説します。

オーバーフロー対策

このプログラムの本体は
  void calcProgress(unsigned long total, unsigned long now, char c)
です。が、Cのプログラムで重要な点がmain()に1つあります。
製品としてのプログラムを作ったことがある人なら常識ですが、入力をscanf()ではなく、fgets()とsscanf()で取得しています(25〜26行目)。fgets()は第2引数-1バイトしか読み込まず、最後に0をつけてくれるのでバッファオーバフローに対して安全です。CodeIQの問題を解く分にはオーバーフローを気にしなくても良いかもしれませんが、こういうことは癖にしてしまった方が良いと思います。

calcProgress()

あえてポイントを挙げるなら次の2点でしょうか。

1つ目はエラー処理(7〜10行目)です。一般的にエラーで関数を抜けられる場合、先頭の方で処理してしまってさっさと関数を終了する方が良いです。このif文の条件を反対にして正常系をifブロック内に書くとネストが深くなって可読性が下がります。今回のプログラムはエラー条件が1つしかありませんが、エラー条件が増えるとこの違いは大きくなります。

2つ目は12行めの計算です。普通に考えるとパーセンテージは「now / total * 100」ですが、この通り計算するとC言語では整数同士の割り算では端数は切り捨てられるので、この問題は常に0です。これに正直に対応すると、
  (int)((double)now / total * 100)
と言ううっとおしい式になります。先に100をかけておけばシンプルに書けるというわけです(例えばnow=1、total=30なら100/30になるので自動的に端数が切り捨てられて3になります)。

雑感

この問題は誰がやっても(C言語なら)こういう解答になると思うのですが、どうも高評価だったらしいのです。これを投稿した日に転職エージェントから連絡があってそのメールにそう書かれていたと言うのが根拠ですが……。