2014年12月17日

C#でn個の数値を10個ずつ折り返して表示

   このエントリーをはてなブックマークに追加 Clip to Evernote
どう書く?orgに感謝を込めて」シリーズ その57

この問題のオリジナルタイトルは、、「ビンゴの結果を整形表示」です。

■問題 (出題者:raynstard さん)
「重複無し乱数」の続編です。 「重複無し乱数」で作ったbingo関数の結果を下のように「何番目の乱数か」と セットにして10個ずつ折り返して表示するコードを書いてください。

>>> bingo(30)
 1  2  3  4  5  6  7  8  9 10
29 14 16 13 30 15 22 11 25  9

11 12 13 14 15 16 17 18 19 20
23  4 18  5 28 17  8 12 21 20

21 22 23 24 25 26 27 28 29 30
26  6  2 19  1  7 10 27  3 24

>>> bingo(35)
 1  2  3  4  5  6  7  8  9 10
 7 15  3 32  1 16 17 28  6 29

11 12 13 14 15 16 17 18 19 20
19 23 30 26 20  5 12  2 25 31

21 22 23 24 25 26 27 28 29 30
35 13 24 18 11  8 10 34 22 21

31 32 33 34 35
 9  4 27 33 14



前回作成した Bingoクラスに、Printメソッドを追加しました。
表示する個数が100を超えても、数字がくっつかないように工夫しています。 それ以外は、いたって普通のコードです。特に説明するまでもないかと。

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

namespace Doukaku.Org {
    class Program {
        static void Main(string[] args) {
            var result = Bingo.Create(9);
            Bingo.Print(result);
            Console.ReadLine();
        }
    }

    static class Bingo {
        // シャッフル拡張メソッド : Fisher-Yates shuffle アルゴリズムを採用
        public static IEnumerable<int> Create(int n) {
            return Enumerable.Range(1, n).Shuffle();
        }

        // これが新たに追加したメソッド
        public static void Print(IEnumerable<int> list) {
            string head = "";
            string data = "";
            // listの個数によって、幅を調整している。
            string format = string.Format("{{0,{0}}}", (int)(Math.Log10(list.Count()) + 2));
            int i = 1;
            foreach (var val in list) {
                head += string.Format(format, i);
                data += string.Format(format, val);
                if (i++ % 10 == 0) {
                    Print10(head, data);
                    head = "";
                    data = "";
                }
            }
            if (head.Length > 0)
                Print10(head, data);
        }

        private static void Print10(string head, string data) {
            Console.WriteLine(head);
            Console.WriteLine(data);
            Console.WriteLine();
        }


    }

    static class IEnumerableExtentions {
        static Random rnd = new Random();
        // シャッフル拡張メソッド : Fisher-Yates shuffle アルゴリズムを採用
        public static IEnumerable<T> Shuffle<T>(this IEnumerable<T> list) {
            T[] result = list.ToArray();
            for (int i = result.Length - 1; i > 1; i--) {
                int j = rnd.Next(0, i);
                T a = result[i];
                result[i] = result[j];
                result[j] = a;
            }
            return result;
        }       
    }
}
  

Posted by gushwell at 22:30Comments(0)TrackBack(0)

2010年01月23日

再帰メソッドで、yield returnしたい

   このエントリーをはてなブックマークに追加 Clip to Evernote
再帰メソッドの戻り値を IEnumerable<T>にする場合についてまとめてみました。
順列を列挙するプログラムを例に説明します。
まず、以下のコードを見てください。


このコードの問題点は、_GetPermutations というメソッドの中で、Printメソッドを 呼び出している点です。
これだと、順列を求めるコードと、求めた順列を使って処理をするコードを明確に 分離することができません。
Permutationクラスは、いろんな場面で使える構造になっていないわけです。 問題が異なれば、Printメソッドの部分を書き直さなくてはなりません。

これを解決する方法のひとつが、イベントやdelegateを使う方法です。
順列が求まるたびに、イベントを発行すれば、Permutationクラスの利用者が Permutationクラスに手を入れることなく、自由に独自の処理を書くことができます。

ただ、この方法にも欠点があります。
例えば、GUIのあるプログラムで、次へボタンを押すたびに、順列をひとつずつ表示する ことを考えて見ましょう。こういった要求には、イベント発行する方法は上手く対応する ことができません。

Permutationクラスの利用する側で、Next メソッドなどを使い、ひとつずつ取り出せれば 良いですよね。
それには、Enumerate メソッドの戻り値を、IEnumerable<T>にするのが良さそうです。

以下に、戻り値を、IEnumerable<T> にしたPermutationクラスを示します。


注目すべき点は、再帰メソッドを呼び出して、返ってきた戻り値の扱いです(★印)。
return result とは書けません。
foreachで、一つ一つ要素を取り出し、yield return しています。こうすることで、再帰メソッドで、戻り値をIEnumerable<T>にすることができます。

全ての結果を Listなどに溜め込む方法でも、Enumerateメソッドの戻り値をIEnumerable<T>に出来ますが (この場合は、下請けメソッドの_GetPermutationsの戻り値は、IEnumerable<T>にはなりません)、 全てを列挙し終わらないと、Enumerateメソッドから制御を戻せませんので、 順列の元となる要素数が多い場合には、最初のひとつを取り出すのにも多くの時間を 要してしまい好ましくありません。

では、これを使う側のコードを示します。
まずはforeachで取り出すコード


次に、IEnumerator<T>.MoveNext, IEnumerator<T>.Currentを使うコード


ボタンをクリックするごとに、次の順列を取り出し、labelに表示しています。
  
Posted by gushwell at 23:27Comments(0)TrackBack(0)