【コメント術】分かりやすいプログラムを書く方法

 昔のプログラミング初学者向けの本の定番である「素因数分解」。著者は親しみや興味が出るからだろうとこのテーマを選んだと思われます。しかし、思いっきりすべっている感もあります。

中学生の数学で習ったし、暗号とかの話にも繋がる素数や素因数分解ですが、正直つまらないし、昔習ったことなど忘れています。

まあ、そんな話より素因数分解を思い出しながら記事を読んでみて下さい。


素因数分解って?

与えられた数字をとにかく整数で割りまくって行くことです。最終的には全部の数字が素数になります。あっ、素数って何? 聞かれそうだが素数とは

「その数と1以外で割り切れない数」

のことです。皆さん大好きなセブンイレブン、7と11を例にすると

7÷1=1
7÷2=3…1
7÷3=2…1
7÷4=1…3
7÷5=1…2
7÷6=1…1
7÷7=1

と、1と7以外では割り切れません。11も

11÷1=1
11÷2=5…1
11÷3=3…2
11÷4=2…3
11÷5=2…1
11÷6=1…5
11÷7=1…4
11÷8=1…3
11÷9=1…2
11÷10=1…1
11÷11=1

と、1と11以外では割り切れません。

素因数分解に話を戻して、例えば30を割り切れるだけ割り切ると

2☓3☓5

となります。ここで出てきた2,3,5は全て素数です。このように全て素数に分解することを素因数分解と呼びます。多分というか絶対に中学一年の最初の数学で習っています。

プログラムで書くとどうなるか?

以下がプログラムで書いた素因数分解です。

  1. #include<stdio.h>
  2. int main(void)
  3. {
  4.     int num,i;
  5.     printf("Enter Natural Number?");
  6.     scanf("%d",&num);
  7.     if(num<0)
  8.     {
  9.         printf("Error %d is not Natural number.\n");
  10.         return 1;
  11.     }
  12.     
  13.     printf("Factoring\n");
  14.     
  15.     if(num==1)
  16.     {
  17.         printf("%d\n",num);
  18.     }
  19.     else
  20.     {
  21.         for(i=2;i<=num;i++)
  22.         {
  23.             if(num%i==0)
  24.             {
  25.                 printf("%d ",i);
  26.                 num/=i;
  27.                 i=1;
  28.             }
  29.         }
  30.     }
  31.     return 0;
  32. }

やっていることは、とにかく割り切れる数字が見つかるまでひたすら割っていくことをやっています。ただ、このプログラムは分かりにくいです。

理由としてはコメントが入っていないことです。これは置いておきます。

次にやたら唐突に出てくる数字が多いところです。ただ、0や1は唐突に出しても良いと妥協されています。それ以外の数字だと

  • 15行目に num=1 とある
  • 21行目にi=2
  • 27行目はi=1なのだが、どうして途中で1なのか?

という部分があります。そうだ、ツッコミはやめてほしいのですけど、目的は素因数分解を極めることでは無いので総当たりのループです。

多少わかりやすくしてみました

以下は修正したプログラムです

  1. #include<stdio.h>
  2. const int ciOne = 1; //やりすぎかもしれないが1も定義
  3. const int ciFirstPrime = 2; //最初の素数は2
  4. int main(void)
  5. {
  6.     int num,i;
  7.     printf("Enter Natural Number?");
  8.     scanf("%d",&num);
  9.     if(num<0)
  10.     {
  11.         printf("Error %d is not Natural number.\n");
  12.         return 1;
  13.     }
  14.     
  15.     printf("Factoring\n");
  16.     
  17.     /* 1が入力された場合はエラーではないが素因数分解の例外として1を出力して処理を終了
  18.     あまりエレガントでは無いが、数学でもそういうルールなので*/
  19.     if(num==ciOne)
  20.     {
  21.         printf("1\n",num);
  22.         return 0;
  23.     }
  24.     
  25.     //数字が1になるまで現在の数を割り続ける。内側のループは最初の素数2から現在の数−1までループ、割り切れたらその数字を出力し、それで割った数を現在の数とする。
  26.     while(num != ciOne){
  27.         for(i=ciFirstPrime;i<=num;i++)
  28.         {
  29.             if(num%i==0) //数字が割り切れたなら
  30.             {
  31.                 printf("%d ",i);
  32.                 num/=i;
  33.                 break;
  34.             }
  35.         }
  36.     }
  37.     
  38.     return 0;
  39. }

コメントが入っているのでズルいだろ? って言われそうですけどそれ以外で直したポイントを説明します。

まず、1という数字が入れられた場合は特例として処理します。メインのループで1を考慮するのも複雑性を上げるだけなので除外しています。

プログラミングの罠として、全て一般化したルールで実装したいという衝動に駆られますが、それにより可読性を下げたり、アルゴリズムが複雑になることは避けたいので特例のパターンは先に抜いておくことをおすすめします。

次に決め打ちの数字1,2を定数として定義しています。2、3行目で const int とある場所です。定数をプログラムの途中に配置すると後でわからなくなるので最初に定義して名前として使うのが良いです。今回は1はciOne、2はciFirstPrimeと定義しています。

34行目にbreakとありますが、一度ループから出ています。そして、その外のループで再び中に入っています。前回のプログラムではループ変数 i=1 と代入されていますが、この1は後でプラス1されて2になることを意図しています。しかし、分かりにくいのでこの方法はやめています。唐突な数字を代入されるより、breakでループを出るという考えの方がわかりやすくミスも有りません。

最後にコメントは、何を処理しているかを重点的に書いています。C言語は読めることを前提にしていますので、コードを見れば分かることは書いていません。

話はずれますが、プログラミングの解説書にかかれているコメントはプログラムを学習するためのコメントですので、本来の書き方とか異なります。決して i=1 のコメントとして「iに1を代入する」なんて書いてはいけないです。 どういう意図で代入するかを必ず書いて下さい。

以上、簡単なプログラムをわかりやすく書く方法の説明でした。

AmazonでC言語の解説本を探す



ウィンドウズアプリ入門 クリップボードにコピーされたらメッセージを受信する方法

 Windowsアプリでコピーされたときにイベントを取得できると便利です。例えばコピーされた内容を画面に表示したい場合などです。 もちろんタイマーイベントで定期的にポーリングをする方法もありますが、やや無駄な処理かもしれません。 Windowsとしてはコピーされたときのイベントを...