砂漠の旅人(たびと)

UNIX / MS-DOS 時代から電脳砂漠を旅しています

素数を表示するプログラム作成はそんなに難しかったのだろうか?

こんにちは、たびとです。

あるとき、新人君に C# のプログラムを手伝って貰おうと考え、自己学習をお願いしたところ、 翌日には「1週間で C# の基礎が学べる本」を買ってきて、やる気マックスでした。

彼は大学でも C 言語を学習していたので、C# 特有の言語仕様を覚えれば、 後は大丈夫であろうと思っていました。 自己学習が終わり、書籍の内容は 8割ほど理解できていたのに、 練習問題の素数を表示するプログラムは、全く作れませんでした。 最終的にインターネットで調べても、自分の言葉で方式を説明することができない状況でした。

なぜ、彼は素数を表示するプログラムが作れなかったのでしょうか。

この記事の対象者

  • 将来システムエンジニア(SE)になりたいと思っている方
  • 新米 SE にプログラムを教えようと思っている方
  • 初心者向け書籍の学習が現場でどれぐらい役に立つのか知りたい方

ある新人 SE の自己学習の結果

新人君が購入した「1週間で C# の基礎が学べる本」の構成は以下の通りです。

  • 1日目 はじめの一歩 ・・・ コンピュータの基礎
  • 2日目 変数と分岐条件 ・・・ C言語学習済みなら不要
  • 3日目 繰り返しと配列 ・・・ C言語学習済みなら不要
  • 4日目 オブジェクト指向1
  • 5日目 オブジェクト指向2
  • 6日目 コレクション・デリゲート・例外処理 ・・・ 新人にデリゲートは不要
  • 7日目 実戦練習

学習に向けたアドバイス

C言語を学習しているのであれば、「オブジェクト指向、コレクション、例外処理」の 4 ~ 6 日目を中心に学習するだけで十分な内容だと思います。

C言語で関数ポインタを使えるなら、デリゲートを覚える価値はあるかもしれませんが、 初心者にデリゲートを覚えるのは無理があると思います。

新人君には、このようなアドバイスと共に、Visual Studio をインストールし、 書籍の内容をコーディングしながら実際に動作を確認するように指示をしました。

自己学習を終えて

彼の自己学習が終わり、進捗状況を確認するために、書籍の内容を質問すると、 8割ぐらいの正答率だったので、実際に練習問題を解いてもらうことにしました。

練習問題の中で一番難しかった「1から100までの素数を表示せよ」を考えさせて、 どのようなアプローチで解くのかを白板に書いて説明するように指示したところ、 なかなか答えることができません。

結局、行き詰ってしまったので、インターネットで調べてもらって、 再度チャレンジしたのですが、書いてあることは理解できても、 実際のプログラム開発となると、満足に動作するものが作れない結果となりました。

なぜプログラムが作れなかったのか

新人君は、自己学習のときから、「わからないことを直ぐインターネットで調べる」 傾向が強くありました。 それ自体は悪いことではないと思いますが、調べる前に仮説を立てるとか、 調べた後に考察するとか、そういった自分で考えることが極端に少なかったと記憶しています。

インターネットに頼り過ぎて、「 自分で考える訓練 」が欠落していたと思われます。

素数を表示するプログラム

新人君が作れなかった「素数を表示するプログラム」を実際に作ってみましょう。 Visual Studio 2022 の C# .NET 6 を用いて作ったので、Main() 関数はありません。 それと、ここで紹介するプログラムの書き方は書籍の内容とは異なります。

直感で閃いたプログラム

新人君に素数のアプローチを考えてもらっている間、 暇だったので、素数を表示するプログラムを作ることにしました。

素数の問題を見た瞬間「プッチ神父」のセリフを思い出しながら、 だいたい 15 分ぐらいで完成させました。

素数は「1と自分自身の数で割れる」ということは、 自分よりも小さい素数で割れれば合成数であるので、 素数テーブルを作って、対象の数値を割っていけば、 素数がかぞえられると考えて作りました。

最終行は、素数テーブルに格納した数値を文字列に変換し、カンマ区切りに変更して出力しています。

const int N = 100;
var primes = new List<int>();           // 素数テーブル
for (int i = 2; i <= N; i++)
{
    var isComposite = false;            // 合成数=True
    foreach (var p in primes)
    {
        if (i % p == 0)
        {
            isComposite = true;         // 素数で割れれば合成数
            break;
        }
    }
    if (!isComposite) primes.Add(i);    // 合成数でなければ素数
}
Console.WriteLine(String.Join(", ", primes.Select(p => p.ToString()).ToArray()));

出力結果は以下の通りです。

2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97

インターネットで調べた後の改良版

2 は素数だとわかっているため、3 から開始します。 このとき、カウントアップは偶数をスキップできるため、 +2 ずつ加算します。 最後に先頭に 2 を追加して終了です。

const int N = 100;
var primes = new List<int>();           // 素数テーブル
for (int i = 3; i <= N; i += 2)
{
    var isComposite = false;            // 合成数=True
    foreach (var p in primes)
    {
        if (i % p == 0)
        {
            isComposite = true;         // 素数で割れれば合成数
            break;
        }
    }
    if (!isComposite) primes.Add(i);    // 合成数でなければ素数
}
primes.Insert(0, 2);                    // 先頭に2を挿入する
Console.WriteLine(String.Join(", ", primes.Select(p => p.ToString()).ToArray()));

出力結果は以下の通りです。

2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97

エラトステネスのふるい版

これも素数テーブルには最初に 2 を格納し、3 から開始します。 ふるいに掛ける数は、対象のルート値を切り上げた値で良いため、 それをループさせます。 上記と同様に自分以外で割れたら合成数なので、それで素数を判定します。

const int N = 100;
var primes = new List<int>() { 2 };     // 素数テーブル
for (int i = 3; i <= N; i += 2)
{
    // 自身(i)未満の数で割り切れるかを確認する
    var isComposite = false;
    for (var j = 2; j < Math.Sqrt(i) + 1; j++)
    {
        // 自身(i)以外の数で割り切れるなら合成数
        if (i % j == 0)
        {
            isComposite = true;
            break;
        }
    }
    if (!isComposite) primes.Add(i);    // 合成数でなければ素数
}
Console.WriteLine(String.Join(", ", primes.Select(p => p.ToString()).ToArray()));

出力結果は以下の通りです。

2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97

まとめ

素数の表示に関してもインターネットで調べてみると色々な書き方があるのには驚きました。 正常に動作すると、後は速度を追求するモノが多くみられましたが、 ここでは、そこまでは追及しないことにします。

さて、この「1週間で C# の基礎が学べる本」の書籍を使って学習すれば、 開発の現場では、どれぐらい役に立つのでしょうか。

結論からいうと、「それぐらい知っていて当たり前」程度なので、 あまり過度の期待はしない方が良いと思います。 それより、書籍の中身は適度に覚えて、実際のプログラムを作るとに 「あの辺りに、似たようなことが書いてあったな」と書籍を参考にしながら、 実際のプログラムを作る能力を磨いた方がいいです。

それに加えて、今回の素数プログラムのように、作った後もインターネットで調べて、 先人の知恵をフィードバックして作り直して理解を深めることも必要な作業です。

つまり、開発の世界においては、仮説を立てて開発し、作った後は色々調べて考察するといった アウトプットとインプットのサイクルを何度も回すことが効率的だと考えます。

今回の新人君へのプログラム開発の教育を通して、 改めて「 自分で考えること(アウトプット) 」の重要性を再認識しました。 皆さんも、書籍やインターネットからのインプットのみで、わかった気になって、 同じ轍を踏まないように気を付けましょう。

最後に参考サイトを掲載しておきます。

では、皆さん、よい旅を。

参考サイト