きっかけ
【swift】 1から100未満の素数の和をforを使わずに求める。
こちらの記事を拝見して、業務でfor
文を使わず関数型っぽく書くメリットを考えてみました。
業務で関数型っぽく書くメリット
個人的に業務で関数型っぽく書くことのメリットは
- 読みやすさ
- バグの少なさ
だと思っています。
(ですので、今回はパフォーマンスを最優先していません。)
ですので、僕ならまず「素数かどうか」を判定する関数を書きます。
その関数をフィルター系の関数1に渡して素数だけの数列にする形をとります。
そうすることで、読み手に何をする処理かが伝わりやすくなるのと、
再利用がしやすくなると思います。
**C#**で書きますが、Swift や JavaScript などほかの言語でも同様です。
素数か判定する関数
まず、数値が素数なら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
以下略