LoginSignup
9
9

More than 5 years have passed since last update.

業務で関数型っぽく書くメリットを『1から100未満の素数の和をforを使わずに求める。』を例に考えてみる

Last updated at Posted at 2018-08-02

きっかけ

【swift】 1から100未満の素数の和をforを使わずに求める。

こちらの記事を拝見して、業務でfor文を使わず関数型っぽく書くメリットを考えてみました。

業務で関数型っぽく書くメリット

個人的に業務で関数型っぽく書くことのメリットは

  • 読みやすさ
  • バグの少なさ

だと思っています。
(ですので、今回はパフォーマンスを最優先していません。

ですので、僕ならまず「素数かどうか」を判定する関数を書きます。
その関数をフィルター系の関数1に渡して素数だけの数列にする形をとります。

そうすることで、読み手に何をする処理かが伝わりやすくなるのと、
再利用がしやすくなると思います。

C#で書きますが、SwiftJavaScript などほかの言語でも同様です。

素数か判定する関数

まず、数値が素数ならtrue、そうでない場合はfalseを返す関数を作ります。
例題の解き方だけを考えるだけなら、
1 から 99 までを数え上げて足す処理の中に素数判定を組みこんだほうが効率が良いですが、
あえて、読みやすさ、再利用性のために小さなメソッドに切り出しています。

// 素数なら true を返す
bool IsPrime(int n) {
  if (n == 1) return false;
  if (n == 2) return true;
  var boundary = (long)Math.Floor(Math.Sqrt(n));
  for (long i = 2; i <= boundary; i++) {
    if (n % i == 0) return false;
  }
  return true;
}

上記の関数は C#プログラミングのイディオム/定石&パターンを参考にしました。

素数判定でfor使わない解法は @albireo さんにコメントしていただいたので、そちらへどうぞ。

1から100未満の素数の和をforを使わずに求める

先ほどのIsPrimeを使って1から100未満の素数の和forを使わずに求めたいと思います。
ここから関数型っぽくなります。

var sum =
  Enumerable.Range(1, 99)
    .Where(n => IsPrime(n))
    .Sum();
Console.WriteLine(sum); // 1060
  • Enumerable.Range(1, 99):1 から 99 まで
  • .Where(n => IsPrime(n)):素数だけを2
  • .Sum();: 足す。

かなり読みやすいのではないでしょうか?3

IsPrimeを定義しないと何をやっているか読みにくくなります。

var sum =
  Enumerable.Range(1, 99)
    .Where(n => {
      // 素数かどうか判定する <- みたいなコメントがいる。(IsPrime なら自明)
      if (n == 1) return false;
      if (n == 2) return true;
      var boundary = (long)Math.Floor(Math.Sqrt(n));
      for (long i = 2; i <= boundary; i++) {
        if (n % i == 0) return false;
      }
      return true;
    })
    .Sum();

IsPrimeを再利用

numbersには何かしらランダムな数列が入っていると思ってください。

// 素数だけをカウント
var count = numbers.Count(n => IsPrime(n));

// すべて素数か?
bool isAllPrime = numbers.All(n => IsPrime(n));

// 素数が混じっているか?
bool containsPrime = numbers.Any(n => IsPrime(n));

// 最大の素数
int max = numbers.Where(n => IsPrime(n)).Max();

// 数列中に最初に現れる素数
int first = numbers.First(n => IsPrime(n))

素数をX個取り出すような実装も簡単です。

// 無限に素数を返すイテレータ
IEnumerable<int> PrimeGen() {
  int n = 1;
  while (true) {
    if (IsPrime(n)) yield return n;
    n++;
  }
}
// 素数を5個取り出す
var primes = PrimeGen().Take(5);  //=> 2, 3, 5, 7, 11 のシーケンス

2018/08/03 23:11 追記

色々、アドバイスいただいたので、それに従ってIsPrimeを書き直してみました。

static class IntExtention {
    public static bool IsPrime(this int n) {
        if (n < 2) return false;
        var primes = new List<int>();
        return Enumerable.Range(2, (int) Math.Sqrt(n) - 1).All(i => {
            if (primes.All(p => i % p != 0)) {
                if (n % i == 0) return false;
                primes.Add(i);
            }
            return true;
        });
    }
}
foreach (var n in Enumerable.Range(1, 99)) {
    Console.WriteLine($"{n}: {n.IsPrime()}");
}
1: False
2: True
3: True
4: False
5: True
6: False
7: True

以下略

  1. Swift や JS のfilterとか C# の Where とか関数を引数にとってフィルタリングするメソッドのことを言っています。 

  2. Where(IsPrime)のように関数そのものを渡すこともできます。 

  3. Aggregateでも良いと思います。 

9
9
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
9