キャプチャ

超実戦的プログラミングとは

「超実戦的プログラミング」という言葉だけを聞くと、なにやら難しいもののように感じますが、「シンプルな解答を積み上げて複雑な問題を解決することができるプログラミング手法」だと言えば、ちょっとは興味を持っていただけるのではないでしょうか。

これはUNIXというOS(Operation System、オペレーションシステム)の思想にも通ずる話です。UNIXを開発したケン・トンプソンという人物がいます。彼はMulticsという複雑怪奇なOSを作っていましたが、このOS開発の経験後になって「Simple is Best!」だと気づくのです。

ビジネスの現場では「KISSの原則」というものもありますね。「KISS」は「Keep it Simple, Stupid!」を略したものです。あ、断定してはクソリプつくからマズいのでした。

※ 諸説あります。

問題に対してシンプルな答えを導き出すためには、「何を以てシンプルであると言えるのか」という自分なりの答えを持っておくことが大切なのではないかと思います。

そのためのエクササイズとして、簡単な問題を色々な方法で解いてみるという方法もあります。簡単な入門課題として「ズンドコメソッド」、そしてその派生元となった有名な入門課題「FizzBuzz」を紹介します。

ズンドコメソッド

「FizzBuzz」というプログラミングの入門課題がありますが、その上級編として「ズンドコメソッド」というものがあることをTwitterで知りました。名前からして、全王さまもおったまげ!な歌唱力を持つ氷川きよしさんの『ズンドコ節』が出自なのでしょう。

とても面白い課題なので、プログラムを組んでみました。まず最初に思い浮かんだのは、これまでに出現した「ズン」と「ドコ」を2進数で管理するアルゴリズムです。

「ズン」は1、「ドコ」は0だと割り当てておけば、5つのビットの並びが「11110」となったときに「キヨシ!」と叫べばよいということになります。実装してみたものが次のソースコードです。

#include <stdio.h>

int prand(void) {
   static int seed = 1123;
   return (seed = seed * 2376 % 1571) & 1;
}

int main(void) {
   int recent = 0;
   int value = 0;

   for(int i = 0; i < 100; i++) {
       value = prand();
       printf("%s", value ? "z" : "d");
       if ((recent = ((recent << 1 | value) & 0x1F)) == 0x1E)
           printf(" --- Kiyoshi!!!\n");
   }

   return 0;
}

prandは疑似乱数(pseudo random number)を発生させる関数です。疑似乱数のシードを初期化していないのは、メインロジックを変更した際に検証できるようにしておくためです。prand関数はスタブなのです。

実行結果

dzzzzd --- Kiyoshi!!!
dzddzdzzdzdzzzddzdzzzzzd --- Kiyoshi!!!
ddzdzzdddzzdzzdzdzzzzd --- Kiyoshi!!!
ddzzzzzzzzd --- Kiyoshi!!!
zzzzzzzd --- Kiyoshi!!!
dddzzddzzzddzzddddddzzddzzzdd

何かが違う

のんびりと長男坊を風呂に入れていたら、ふと気がついたのですが、この問題は「FizzBuzz」の延長にあるのですから、ビット演算を使うのは間違っているような気がしました。せっかく実装したのに10分くらい無駄にしてしまいました。設問はちゃんと裏まで読まなければなりません。

そこで設問を見直しました。

「FizzBuzz」は余剰演算子を使うので、やはり「ズンドコメソッド」でも余剰演算子を使うのだろうか?と考えました。しかし、「ズン」が5回以上出現してから「ドコ」になることもあるので違います。

しばらく考えてみた結果、どうやら連続した「ズン」の登場回数を数えるズンズンカウンタを設けるのが設問レベル的にも正解だと気づきました。

次のソースコードは、ズンズンカウンタを使った実装例です。

#include <stdio.h>

int prand(void) {
  static int seed = 1123;
  return (seed = seed * 2376 % 1571) & 1;
}

int main(void) {
  int zz_counter = 0;

  for(int i = 0; i < 100; i++) {
      if (prand()) {
          printf("z");
          zz_counter++;
      }
      else {
          printf("d");
          if (zz_counter >= 4)
              printf(" --- Kiyoshi!!!\n");
          zz_counter = 0;
      }
  }

  return 0;
}

prand関数の返す値は変えていませんので、実行結果が正しいかどうかは、先ほどの実行結果と比較することで検証することができます。

どうしても目で確認したい場合は、検索した文字列に色がつくテキストエディタを使うとよいでしょう。実行結果を貼りつけて、"zzzzd"(ズンズンズンズンドコ)を検索します。

FizzBuzz問題

「FizzBuzz問題」は、超がつくほど有名な入門課題です。「与えられた自然数に対して次の処理を行うロジックを書け!」というものです。

・3の倍数のときは"Fizz"と出力
・5の倍数のときは"Buzz"と出力
・15の倍数のときは"FizzBuzz"と出力

色々な実装方法がありますが、たとえば、次のような実装が考えられます。検証を楽にために連番を与え、15の倍数で折り返します。

#include <stdio.h>

int main(void) {
  int mod3 = 0;

  for(int i = 1; i < 100; i++) {
      printf(" %d:", i);
      if (mod3 = !(i % 3))
          printf("Fizz");
      if (!(i % 5))
          printf("Buzz%s", mod3 ? "!\n" : "");
  }

  return 0;
}

実行結果

1: 2: 3:Fizz 4: 5:Buzz 6:Fizz 7: 8: 9:Fizz 10:Buzz 11: 12:Fizz 13: 14: 15:FizzBuzz!
16: 17: 18:Fizz 19: 20:Buzz 21:Fizz 22: 23: 24:Fizz 25:Buzz 26: 27:Fizz 28: 29: 30:FizzBuzz!
31: 32: 33:Fizz 34: 35:Buzz 36:Fizz 37: 38: 39:Fizz 40:Buzz 41: 42:Fizz 43: 44: 45:FizzBuzz!
46: 47: 48:Fizz 49: 50:Buzz 51:Fizz 52: 53: 54:Fizz 55:Buzz 56: 57:Fizz 58: 59: 60:FizzBuzz!
61: 62: 63:Fizz 64: 65:Buzz 66:Fizz 67: 68: 69:Fizz 70:Buzz 71: 72:Fizz 73: 74: 75:FizzBuzz!
76: 77: 78:Fizz 79: 80:Buzz 81:Fizz 82: 83: 84:Fizz 85:Buzz 86: 87:Fizz 88: 89: 90:FizzBuzz!
91: 92: 93:Fizz 94: 95:Buzz 96:Fizz 97: 98: 99:Fizz

素直な書き方の答えは、ググるとたくさん出てくると思います。ちなみに「ググれ」は、英語だと「Google it!」と言うらしいですね。チクりとする言い方は「Google is your friend」なんだとか。「ボールは友達」と述べるどこかの少年を思い出してしまいました。

超実戦的学習法

簡単なプログラムは頭の体操にはちょうどよいです。実装方法も複数考えていると解くのも楽しくなってくると思います。解答のバリエーションを考えることはやや実戦的です。

やや実戦的を超実戦的にするためには、解いた答えを比較・検証しなければなりません。たとえば、こんな感じです。

・ある答えはプログラムが冗長だけど読みやすい
・ある答えは処理は高速だけどメモリを食う
・ある答えは、若干処理に時間が掛かるけど、複数実行できる
・ある答えは分岐処理が多いけど、別の答えなら分岐が少ない
・ある答えは最初の実装が面倒だけどメンテは楽になる

こういった答えを知っておいた上で、さらに対処法を考えることが学習効果が高いと思います。さきほどの例なら次のようになります。

・プログラムを簡潔にしつつ読みやすくするには?
・プログラムの高速処理を保ったままメモリ使用量を減らすには?
・複数実行しながら処理速度をあげるには?
・分岐の数を減らした効果を測るには?
・最初の実装をもっと楽にするには?

これらの例について答えは書きませんが、ヒントとなるキーワードを書きます。これらのキーワードを見つけるまで掘り進めることができれば、学習する目的が絞り込まれます。キーワードが絞り込まれているので、技術書を選ぼうと思ったときも楽になるでしょう。

・コーディングルール
・パフォーマンス
・ボトルネック
・循環的複雑度
・車輪の再発明

プログラミングに限ったものなのかもしれませんが、効果的な学習方法は「解決するべき命題を絞り込む」というプロセスと、絞り込んだ命題について学んで、それをフィードバックすることだと言えるように思います。


この記事が気に入ったらサポートをしてみませんか?