C# yield を使ってみた

アバター
いわん
記事: 32
登録日時: 9年前

C# yield を使ってみた

投稿記事 by いわん » 9年前

C# の yield、前から興味はあったけど使ったことがなかったので素数を列挙する関数を yieldを使って作ってみた。
ほかに実用的な使い道は思いつかなかったけど、使いこなせれば便利なのかな?

CODE:

using System;
using System.Collections.Generic;
using System.Text;

namespace YieldSample
{
    class Program
    {
        static void Main(string[] args)
        {
            int n;
            n = 0;
            foreach (int p in Prime())
            {
                ++n;
                if (n == 100000)
                {
                    Console.WriteLine(string.Format("{0}番目の素数:{1}", n, p));
                    break;
                }
            }
            n = 0;
            foreach (int p in Prime())
            {
                ++n;
                if (n == 1000000)
                {
                    Console.WriteLine(string.Format("{0}番目の素数:{1}", n, p));
                    break;
                }
            }
        }

        static List aPrime = new List();
        public static IEnumerable Prime()
        {
            // 最初の素数 2
            if (aPrime.Count == 0)
            {
                aPrime.Add(2);
            }
            yield return 2;
            // 以降、素数候補を 3から始めて 2ずつ加算しながら検証する
            int np = 1;     // 出力済みの素数順位
            int next = 3;   // 次の素数候補
            while (true)
            {
                if (np < aPrime.Count)
                {
                    // 既に作成済みの素数を出力する
                    yield return aPrime[np];
                    next = aPrime[np] + 2;
                    ++np;
                }
                else
                {
                    // 次の素数を見つけてリストに追加&出力する
                    foreach (int prime in aPrime)
                    {
                        if ((Int64)next < (Int64)prime * prime)
                        {
                            // 因数となる素数がなかったので新しくリストに追加して出力する
                            aPrime.Add(next);
                            yield return next;
                            ++np;
                            break;
                        }
                        if (next % prime == 0)
                        {
                            // 因数となる素数があったため次の素数候補を検証する
                            break;
                        }
                    }
                    next += 2;
                }
            }
        }
    }
}

YuO
記事: 947
登録日時: 14年前

Re: C# yield を使ってみた

投稿記事 by YuO » 9年前

標準のLinq演算子(=System.Linq.Enumerableのメソッド) にないものを追加したり,ストリームをIEnumerableに変換したりするのに便利です。
後者の例だと,TextFieldParserでCSVを読み込む時に,1行分のstring[]を何らかのオブジェクトにしてそのままyieldで返すようにすると,そのままLINQが使えるようになりますs。
もっと簡単な例ですが,

CODE:

using System;
using System.IO;

public class TextReaderExtension
{
    public static IEnumerable AsEnumerable (this TextReader reader)
    {
        if (reader == null) throw new ArgumentNullException(nameof(reader));
        string s;
        while ((s = reader.ReadLine()) != null) yield return s;
    }
}
なんていう拡張メソッドを用意しておくと,StreamReaderなどをIEnumerableに変換できるため,Where使った絞り込みなど,便利になることがあります。

まぁ,yieldが出てきた2.0の頃はLINQが無かったので,LINQのWhereやSelectみたいなことを書くのにも便利だったのですが,今はLINQで済むので出番は減った感じはあります。

アバター
いわん
記事: 32
登録日時: 9年前

Re: C# yield を使ってみた

投稿記事 by いわん » 9年前

なるほど、ファイルデータを IEnumerableに変換するのは便利そうですね。
LINQの勉強もまだこれからですが AsEnumerableメソッドで利用できるは覚えておきます。
ありがとうございます。

アバター
いわん
記事: 32
登録日時: 9年前

Re: C# yield を使ってみた

投稿記事 by いわん » 9年前

yield の勉強のつもりで作った素数を列挙するプログラムだけど、素数を見つけるほうに興味が移ってしまい^^;さらに高速にとエラトステネスのふるいを使ってみた。
再利用を考えて static class にしてみたけど、インデクサが使えないんだね。
列挙じゃなくて配列のように使いたかったんだが。なんかいい方法ないかな。

CODE:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace prime
{
    static class Prime
    {
        private static List aPrime = new List();
        public static IEnumerable _enum()
        {
            // 最初の素数
            if (aPrime.Count == 0)
            {
                aPrime.Add(2);
                aPrime.Add(3);
            }
            // 作成済み素数出力/新しい素数を見つけるための情報を取得
            int next_x = aPrime[aPrime.Count - 1] + 2;  // 新しい素数候補
            int sieve_prime_order = 0;  // ふるいを作るための最大素数の順位
            int out_order;  // 出力済み素数順位
            for (out_order = 0; out_order < aPrime.Count; ++out_order)
            {
                yield return aPrime[out_order];
                if ((Int64)aPrime[out_order] * aPrime[out_order] < next_x)
                {
                    sieve_prime_order = out_order;
                }
            }
            int sieve_from = aPrime[sieve_prime_order] * aPrime[sieve_prime_order]; // ふるいの最小値
            ++sieve_prime_order;
            int sieve_to = aPrime[sieve_prime_order] * aPrime[sieve_prime_order];   // ふるいの最大値
            int sieve_prime = aPrime[sieve_prime_order];    // ふるいを作るための最大素数
            // 新しい素数を探す
            while (true)
            {
                int sieve_size = sieve_to - sieve_from; // ふるいサイズ
                bool[] ab = new bool[sieve_size];   // ふるい(false:素因数なし true:素因数あり)
                // sieve_prime 以下の全ての素数について、ふるい中のその素数の倍数に true を設定する
                foreach (int p in aPrime)
                {
                    for (int x = (p - (sieve_from % p)); x < sieve_size; x += p)
                    {
                        ab[x] = true;
                    }
                    if (p == sieve_prime)
                    {
                        break;
                    }
                }
                // ふるいの中の false となっている全ての値を素数として出力する
                for (int i = next_x - sieve_from; i < sieve_size; ++i)
                {
                    if (!ab[i])
                    {
                        aPrime.Add(sieve_from + i);
                        yield return sieve_from + i;
                    }
                }
                // ふるいを作成するための素数を1つ追加して情報を更新する
                sieve_from = sieve_to;
                ++sieve_prime_order;
                sieve_to = aPrime[sieve_prime_order] * aPrime[sieve_prime_order];
                sieve_prime = aPrime[sieve_prime_order];
                next_x = sieve_from + 2;
            }
        }
    }
}