CodeIQ:【超絶技巧初級テク】ソースコード中のデータを限界まで詰め込もう

私自身が表題の問題を解いた時のプログラムについて解説します。
問題の詳細は「【超絶技巧初級テク】ソースコード中のデータを限界まで詰め込もう」(CodeIQ)を参照してください。

問題の概要

次のような問題です。
#とピリオドで構成された、3×5のビットマップが標準入力から与えられます。
#を1.を0として扱った場合、入力値を36進数に変換したものを標準出力に出力してください。

次のような入力があったとします。
.#.
##.
.#.
.#.
###
出力は次のようになります。
8t3

私のプログラム

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

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

unsigned long long bmp2ull(char* str, size_t max){
	int i=0;
	for(i=0; i<max && str[i]; i++){
		if(str[i] == '#'){ str[i] = '1'; }
		else if(str[i] == '.'){ str[i] = '0'; }
		else{ str[i] = 0; }
	}
	return strtoull(str, NULL, 2);
}

void radix(char* buf, unsigned long long n, int r){
	static char s[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
	 				   'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
					   'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
					   'u', 'v', 'w', 'x', 'y', 'z'};
	int i=0;

	char t[64] = {0};
	if(!n){
		buf[0] = s[0];
		return;
	}

	for(i=62; n; i--){
		t[i] = s[n%r];
		n /= r;
	}
	memcpy(buf, t+i+1, 64-i);
	return;
}

int main(int argc, char* argv[]){
	char str[16] = {0};
	char buf[64] = {0};
	int i=0;
	unsigned long long l = 0;

	for(i=0; fgets(str+i, sizeof(str), stdin) != NULL; i+=3 );

	l = bmp2ull(str, sizeof(str));
	radix(buf, l, 36);
	printf("%s\n", buf);
	return 0;
}

解説

要するに進数変換のプログラムを作りなさい、と言う問題です。

bmp2ull()

入力値(改行文字を除いて連結したもの)をunsigned long longにします。入力値は15文字しかないのでshortで足りますが、微妙に冗長性をもたせてunsigned long longにしました。
やっていることは単純で'#'を'1'に'.'を'0'に置き換え、strtoulの基数を2に指定して数値に変換するだけです。一応、不正な入力値対策で'#'か'.'以外があったらそこで文字列は終わりにしています。

radix()

数値を36進数に変換します。
s[]は36進数で使用する文字のリストを定義した定数です。
進数変換のロジックは調べればいくらでも見つかるので調べて実装しました。
簡単に説明すると、10進数の値を指定された基数で割った余りに対応する文字が進数変換後の一番下の位になります。商が0でなければ変換後の位を1つ上げて商を基数で割って同じ様に余りから変換後の値を決定します。これを商が0になるまで繰り返すということです。
100を36進数にした場合、
100 % 36 = 2余り28 ・・・ 1の位はs
2 % 36 = 0余り2 ・・・ 10の位は2
なので100は36進数で2sというわけです。
このコードではt[]の後ろから変換後の値が埋まるので32行目でbuf[]の先頭から文字列が始まる様にコピーしています。

雑感

そう難しい問題ではありません。一応radix()は汎用的に基数を指定できる様にしたのだから後A〜Zまで使って62進数まで対応してもよかったかな、と思います。