2015年03月04日

C#でケブンッリジ関数

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

■問題 (出題者:lunlumo さん)
与えた文章の各単語の最初と最後の文字以外の文字を入れ替えた文章を出力する処理を実装して下さい。元の文章の与え方は特に問いません。
参考: 確かに"読めてしまう"コピペ http://www.itmedia.co.jp/news/articles/0905/08/news021.html

単語を順に取り出し、文字を入れ替えるというだけの なんの変哲もない普通のコードだと思うので、特筆すべき点はないです。
EnumWordsで返すのを string ではなく、StringBuilderにして、 Changeメソッドには、その StringBuilderオブジェクトをそのまま渡して 処理したほうがすこしは効率的かなと思いますが、分かりやすさ優先ということで。


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

namespace Doukaku.Org {
    class Program {
        static void Main(string[] args) {
            string s =
    @"こんにちは みなさん おげんき ですか? わたしは げんき です。
この ぶんしょう は いぎりす の ケンブリッジ だいがく の けんきゅう の けっか
にんげん は もじ を にんしき するとき その さしいょ と さいご の もじさえ
あっていれば じゅんばん は めちゃくちゃ でも ちゃんと よめる という けんきゅう
に もとづいて わざと もじの じゅんばん を いれかえて あります。
どうです? ちゃんと よめちゃう でしょ?
ちゃんと よめたら はんのう よろしく。";
            CambridgeFunction cf = new CambridgeFunction();
            string result = cf.Convert(s);
            Console.WriteLine(result);
            Console.ReadLine();
        }
    }

    class CambridgeFunction {

        public string Convert(string s) {
            Random rnd = new Random();
            string t = s.Replace("\r\n", "\n");
            StringBuilder sb = new StringBuilder();
            foreach (var word in EnumWords(t)) {
                int len = word.Length;
                string cambridgeWord = (len > 3) ? Change(word, len) : word;
                sb.Append(cambridgeWord);
            }
            return sb.ToString();
        }

        // 単語を順に列挙。単語と単語の間の空白も列挙の中に含める。
        private IEnumerable<string> EnumWords(string s) {
            string word = "";
            foreach (var c in s) {
                if (IsDelimiter(c)) {
                    if (word.Length > 0)
                        yield return word;
                    yield return c.ToString();
                    word = "";
                } else {
                    word += c;
                }
            }
            yield return word;
        }

        private bool IsDelimiter(char c) {
            return "。、? \n".Contains(c);
        }

        private static Random rnd = new Random();
        private static string Change(string word, int len) {
            var list = word.ToList();
            int ix = rnd.Next(2, len - 1);
            char c = list[ix];
            list.RemoveAt(ix);
            list.Insert(1, c);
            return new string(list.ToArray());
        }
    }
}

  

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

2014年12月07日

C#で年間カレンダーを表示する

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

■問題 (出題者:186 さん)
nを入力としてn年の年間カレンダーを返すプログラムを作ってください
少なくとも日曜日と土曜日が判別出来るようにしてください
出力は標準出力でもファイルでも構いません
デザインは各自のお好みで

表示とカレンダーを作成する部分を分離したかったので、まずは、CalenderMakerCore という クラスを作成しました。

using System;
using System.Collections.Generic;

namespace Doukaku.Org {
    // カレンダーを作成する部分を、CalenderMakerCoreとして独立させている。
    public static class CalenderMakerCore {
        // IEnumerable<int[]>を返すメソッドで、int[]には日曜日から始まる1週間分の
        // 日にちが入っています。0 は、日付の無い箇所(空白表示)を示している。
        // それが、一ヶ月分のカレンダー上の行数分返されます。
        public static IEnumerable<int[]> GetMonthCalender(int year, int month) {
            List<int> oneWeek = new List<int>();
            DateTime dt = new DateTime(year, month, 1);
            // 空白の箇所(日付の無い箇所)は、0 で示す
            for (int i = 0; i < (int)dt.DayOfWeek; i++)
                oneWeek.Add(0);
            for (; dt.Month == month; dt = dt.AddDays(1)) {
                oneWeek.Add(dt.Day);
                if (dt.DayOfWeek == DayOfWeek.Saturday) {
                    yield return oneWeek.ToArray();
                    oneWeek.Clear();
                }
            }
            if (oneWeek.Count != 0)
                yield return oneWeek.ToArray();
        }
    }
}

このクラスを使って、ConsoleCalenderクラスを定義します。
一ヶ月分のカレンダーを表示する PrintMonthCalender メソッドを定義し、 1年分を表示する PrintYearCalender メソッドは、このPrintMonthCalender を12回呼び出しています。

このようにクラスを分離すれば、コンソール出力以外でも、いろんな用途にCalenderMakerCore が使えるようになりますよね。

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

namespace Doukaku.Org {

    // CalenderMakerCoreを利用して、カレンダーを作成する。
    // このクラスは、表示に専念
    class ConsoleCalender {
        public void PrintYearCalender(int year) {
            for (int m = 1; m <= 12; m++) {
                PrintMonthCalender(year, m);
                Console.WriteLine();
            }
        }

        public void PrintMonthCalender(int year, int month) {
            GetMonthCalender(year, month).ForEach(s => Console.WriteLine(s));
        }

        IEnumerable<string> GetMonthCalender(int year, int month) {
            List<string> lines = new List<string>();
            yield return string.Format("      {0}年{1}月", year, month);
            yield return " 日 月 火 水 木 金 土";
            var seq = CalenderMakerCore.GetMonthCalender(year, month);
            var q = seq.Select(w => w.Aggregate("", (line, d) => line += String.Format("{0,3:#}", d)));
            foreach (var s in q)
                yield return s;
        }
    }
}

で、最後は、このConsoleCalender クラスを呼び出す Mainメソッドを定義します。

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

namespace Doukaku.Org {
    class Program {
        static void Main(string[] args) {
            ConsoleCalender calender = new ConsoleCalender();
            calender.PrintYearCalender(2014);

            // calender.PrintYearCalender(2014,11);  2014/11だけ表示 こんな呼び出し方もできる。
            Console.ReadLine();
        }
    }
}

  
Posted by gushwell at 21:40Comments(0)TrackBack(0)

2014年08月17日

C#で隣り合う二項の差を求める

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

■問題 (出題者:にしお さん)
整数のリストがxsが与えられたときに、隣り合う2要素の差のリストを作る関数diffを作ってください。
サンプル入力
[3, 1, 4, 1, 5, 9, 2, 6, 5]
サンプル出力
[-2, 3, -3, 4, 4, -7, 4, -1]  
どの問題もそうですが、いろんな書き方が考えられます。
この問題では4つのバージョンを書いてみました。

最初は、IList<int> を受け取って、添え字を使って各要素にアクセスするもの。 配列など他のコレクションを受け取れないのが欠点です。

■C#のコード(その1)
using System;
using System.Collections.Generic;
using System.Linq;

namespace Doukaku.Org {
    class Program {
        static void Main(string[] args) {
            var nums = new List<int> { 3,1,4,1,5,9,2,6,5 };
            var diff = Diff(nums);
            PrintList(diff);
            Console.ReadLine();
        }

        // IList<int>を受け取り、IList<int>を返す例
        static IList<int> Diff(IList<int> xs) {
            List<int> result = new List<int>();
            for (int i = 0; i < xs.Count() - 1; i++) {
                result.Add(xs[i + 1] - xs[i]);
            }
            return result;
        }

        static void PrintList(IEnumerable<int> nums) {
            string s = string.Join(", ", nums.Select(x => x.ToString()).ToArray());
            Console.WriteLine("[{0}]", s);
        }
    }
}


2つめは IEnumerable<int>を受け取り、 yield return で結果を列挙する例 (MoveNext、Currentを使用) やっぱり、MoveNext, Currentは面倒くさいですね。

■C#のコード(その2)
        static IEnumerable<int> Diff(IEnumerable<int> xs) {
            var ite = xs.GetEnumerator();
            if (ite.MoveNext())
                for (int prev = ite.Current; ite.MoveNext(); prev = ite.Current) {
                    yield return ite.Current - prev;
                }
        }

次は、IEnumerable<int>を受け取り、 yield return で結果を列挙する例 (foreachを使用) ループの中で、最初か最初でないかを判断しているところが気に入らないです。

■C#のコード(その3)   
        static IEnumerable<int> Diff(IEnumerable<int> xs) {
            bool first = true;
            int prev = int.MinValue;
            foreach (var n in xs) {
                if (!first)
                    yield return n - prev;
                prev = n;
                first = false;
            }
        }

そして最後が、再帰を使ったもの。 メソッドが2つに分かれてしまうのが、玉に瑕ですね。

■C#のコード(その4)
static IEnumerable<int> Diff(IEnumerable<int> xs) {
    if (xs.Any())
        return Diff5(xs.First(), xs.Skip(1));
    return new int[] { };
}

static IEnumerable<int> Diff(int prev,IEnumerable<int> xs) {
    if (xs.Any()) {
        int b = xs.First();
        yield return b - prev;
        foreach (var x in Diff5(b, xs.Skip(1)))
            yield return x;
    }
}

どれも一長一短ですね。あなたならどう書く?
  
Posted by gushwell at 23:00Comments(0)TrackBack(0)

2014年07月20日

C#で13日の金曜日を数え上げる

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

■問題 (出題者:takano32 さん)
今日から2013年12月31日までの、13日の金曜日とその総数を表示してください。 余力のあるものはこのプログラムを短くしてみたり、短くしてみたり、短くしてください。
オリジナル:http://ll.jus.or.jp/2008/program/doukaku.html


最後の「余力のあるものは...」の部分は無視しています。
引き数ひとつのEnumFriday13は、指定日以降の13日の金曜日を無限に列挙しています。これを呼び出す引き数2つのメソッドで、TakeWhileを使い有限個にしています。
なお、もう2013年12月31日は過ぎてしまったので、2015年12月31日までとしています。
ついでに、引き数ひとつのEnumFriday13を利用し、2014年1月1日以降の 13日の金曜日を10個まで求めています。

結果は載せませんので、答えが知りたい方は是非実行してみてください。

■C#で書いたコード
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;


namespace Doukaku.Org {
    class Program {
        static void Main(string[] args) {
            var list = EnumFriday13(DateTime.Today, new DateTime(2015, 12, 31)).ToList();           
            foreach (var d in list)
                Console.WriteLine(d.ToShortDateString());
            Console.WriteLine("{0} days", list.Count());
            Console.ReadLine();

            var list2 = EnumFriday13(new DateTime(2014, 1, 1)).Take(10).ToList();
            foreach (var d in list2)
                Console.WriteLine(d.ToShortDateString());
            Console.WriteLine("{0} days", list2.Count());
            Console.ReadLine();
       
        }

        // 引き数 fromDateからtoDateの間の 13日の金曜日を列挙する
        static IEnumerable<DateTime> EnumFriday13(DateTime fromDate, DateTime toDate) {
            return EnumFriday13(fromDate).TakeWhile(d => d <= toDate);
        }

        // 引き数 date以降の 13日の金曜日を列挙する
        static IEnumerable<DateTime> EnumFriday13(DateTime date) {
            if (date.Day > 13)
                date = date.AddMonths(1);
            date = new DateTime(date.Year, date.Month, 13);
            while (true) {
                if (date.DayOfWeek == DayOfWeek.Friday) {
                    yield return date;
                }
                date = date.AddMonths(1);
            }
        }
    }
}

  
Posted by gushwell at 23:00Comments(0)TrackBack(0)

2010年11月23日

コンソールで処理中であることを示す文字を表示

   このエントリーをはてなブックマークに追加 Clip to Evernote
元ネタ いろいろ備忘録日記 - コンソールで処理中であることを示す文字を表示

おもしろそうだったので、僕もやってみました。

指定した複数の値を繰り返す」で示したRepeat拡張メソッドを使って、gsf_zero1 さんが書いたコードを書き換えています。

以下に示すコードのProgressメソッドが、処理中であることを示すメソッドです。
gsf_zero1 さんは、ラムダ式としていましたが、これをメソッドにしています。

このメソッドの中で、Repeatメソッドを使い、コンソールに表示する文字を繰り返し取得しています。
僕のコードでは、'.' 'o' 'O' '@' 'O' 'o' という6文字を繰り返して取り出し、コンソールに表示しています。
別の文字を表示したいなら、文字列 ".oO@Oo" の部分を"|/-" など別のものに置き換えるだけです。


  
Posted by gushwell at 23:10Comments(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)