2014年08月31日

C#でタブ区切りデータの処理

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

■問題 (出題者:ika さん)
タブ区切りのデータを読み込んで操作をし書き出す方法を教えてください。読み込み・書き出しの方法は任意とします。 与えられるデータは:
・レコードの区切りは改行、カラムの区切りはタブです。
・最初のレコードはヘッダで、カラムの名前が書いてあります。
・それ以降はデータで、第1,4カラムは整数値、第2,3カラムは文字列値です。

この入力データに対して以下の操作をしたものを書き出してください:

・第1カラムの値でデータを昇順にソートする。
・第2カラムと第3カラムをヘッダを含めて入れ替える。
・第4カラムの値にそれぞれ1を加える。

入力の例:
ID      Surname Forename        Age
1       Sato    Hanako  17
0       Suzuki  Taro    18
...

出力の例:
ID      Forename        Surname Age
0       Taro    Suzuki  19
1       Hanako  Sato    18
...

カラムの中にタブが含まれているときにどう表現するのかという問題は無視しています。
たぶん、そこまでは求めていないと思うので。
なので、String.Split()メソッド使えば、それほど難しいことはないですね。

それと、空のファイルのときの処理も入れてません。

LINQ to Objectの拡張機能である インタラクティブエクステンション(Interactive Extensions; Ix)のShare()メソドを使ったコードも書いてみました。

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

namespace Doukaku.Org {
    class Program {
        static void Main(string[] args) {
            {
                var lines = File.ReadAllLines("sample.tsv");

                var head = lines.First().Split('\t');
                Console.WriteLine("{0}\t{1}\t{2}\t{3}",
                    head[0], head[2], head[1], head[3]);

                var body = lines.Skip(1)
                                .Select(s => s.Split('\t'))
                                .OrderBy(e => e[0]);
                foreach (var s in body) {
                    Console.WriteLine("{0}\t{1}\t{2}\t{3}",
                        s[0], s[2], s[1], int.Parse(s[3]) + 1);
                }
            }
            {
                // Ix 使ってみた。あまり変わり映えしない...
                var lines = File.ReadAllLines("sample.tsv").Share();

                var head = lines.First().Split('\t');
                Console.WriteLine("{0}\t{1}\t{2}\t{3}",
                    head[0], head[2], head[1], head[3]);

                var body = lines.Select(s => s.Split('\t'))
                                .OrderBy(e => e[0]);
                foreach (var s in body) {
                    Console.WriteLine("{0}\t{1}\t{2}\t{3}",
                        s[0], s[2], s[1], int.Parse(s[3]) + 1);
                }

            }
        }
    }
}

  

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

2014年08月27日

C#で条件を満たす行を取り除く

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

久しぶりに、"http://ja.doukaku.org/" にアクセスしたら、Not found になっていました。完全に閉鎖されてしまったようですね。
でも気にせず、このシリーズは続けます(^^;;

■問題 (出題者:にしお さん)
ファイルから1行ずつ読み込み、"#"で始まる行だけを取り除いてファイルに出力するコードを書いてください。
サンプル入力
hello!
# remove this
# don't remove this
bye!

サンプル出力
hello!
# don't remove this
bye!

#"で始まる行だけを取り除くプログラムだとつまらないので、タイトルどおりに「条件を満たす行を取り除くメソッド」を書いてみた。。
つまり条件を引き数として渡せるようにしてみた。

using System;
using System.IO;


namespace Doukaku.Org {
    class Program {
        static void Main(string[] args) {
            RemoveLines("sample.txt", "sampleOut.txt", s => s.StartsWith("#"));
        }       
        private static void RemoveLines(string infile, string outfile, Predicate<string> judge) {
            using (StreamReader sr = new StreamReader(infile))
            using (StreamWriter sw = new StreamWriter(outfile)) {
                while (!sr.EndOfStream) {
                    string s = sr.ReadLine();
                    if (!judge(s))
                        sw.WriteLine(s);
                }
            }
        }
    }
}

この例では、引き数として

  s => s.StartsWith("#")

を渡しているが、

  s => s.Length == 0

とすれば、長さが 0 の行を取り除くことができる。

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

2014年08月24日

C#で並行処理

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

■問題 (出題者:sumim さん)
数値(たとえば1から10)と、アルファベット(たとえばAからJまで)を順に出力する別々のループ処理を並行に実行させ、共通の出力先に出力する極力シンプルなコードを書いてください。
念のため、実行後、出力先に数値とアルファベットが混ざって出力されている(たとえば、数値がすべて出力されてからアルファベットが続く…というふうになっていない)ことを確認してください。混ざってさえいれば、それぞれ1文字ずつ交互である必要はありませんし、もちろん交互でも構いません。
出力先や出力方法は自由です。標準出力、テキストファイル、コンテナオブジェクト(配列、リスト、コレクション)など使いやすいもので構いません。
例として Squeak Smalltalk でのコードと結果を示します。シンプルなコードなので Smalltalk に馴染みがない人も、おおよその内容は掴めると思います。
| out |
out := OrderedCollection new.

[(1 to: 10) do: [:each | out add: each. Processor yield]] fork.
[($A to: $J) do: [:each | out add: each. Processor yield]] forkAndWait.

^out asArray  "=> #(1 $A 2 $B 3 $C 4 $D 5 $E 6 $F 7 $G 8 $H 9 $I 10 $J) "


もちろん可能であれば、疑似である必要はありません。^^;


出力先はいちばん簡単と思われる標準出力としました。
まずは、Threadクラスを使ったコード。

using System;
using System.Linq;
using System.Threading;
using System.Collections;
using System.Threading.Tasks;

namespace Doukaku.Org {
    class Program {
        static void Main(string[] args) {
            ParallelTest();
            Console.ReadLine();
        }

        // Threadを使った例
        private static void ParallelTest() {
            Thread th1 = new Thread(PrintSeq);
            Thread th2 = new Thread(PrintSeq);
            th1.Start("ABCDEFGHIJKLMNOPQRSTUVWXYZ");
            th2.Start("abcdefghijklmnopqrstuvwxyz");
            th1.Join();
            th2.Join();
        }

        // 引き数 obj は、IEnumerableを実装している必要がある。引き数の値を順に出力する。
        // PrintSeqが直ぐに終わらないように、途中でウエイトしている。
        private static void PrintSeq(object obj) {
            foreach (var x in (IEnumerable)obj) {
                Console.Write(x.ToString());
                Thread.Sleep(2);
            }
        }
    }
}

いちおう、Taskクラスを使ったコードも書いてみた。これくらい単純な問題ならば、Taskクラス使うのも悪くない。

using System;
using System.Linq;
using System.Threading;
using System.Collections;
using System.Threading.Tasks;

namespace Doukaku.Org {
    class Program {
        static void Main(string[] args) {
            ParallelTest();
            Console.ReadLine();
        }

        static void ParallelTest() {
            var task1 = Task.Factory.StartNew(PrintSeq, "ABCDEFGHIJKLMNOPQRSTUVWXY");
            var task2 = Task.Factory.StartNew(PrintSeq, "abcdefghijklmnopqrstuvwxyz");
            task1.Wait();
            task2.Wait();
        }

        // 引き数 obj は、IEnumerableを実装している必要がある。引き数の値を順に出力する。
        // PrintSeqが直ぐに終わらないように、途中でウエイトしている。
        private static void PrintSeq(object obj) {
            foreach (var x in (IEnumerable)obj) {
                Console.Write(x.ToString());
                Thread.Sleep(2);
            }
        }
   }
}
  
Posted by gushwell at 23:00Comments(0)TrackBack(0)

2014年08月20日

C#で出力の一時停止と再開

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

今回の投稿が、1111回目となります \(^o^)/

■問題
起動すると、標準出力に1秒毎に'a'の1文字を出力し続けるプログラムで、 以下の条件を満たすものを「どう書く?」
'q'キーが押されるとプログラムは終了する
出力中に'p'キーが押されると一時停止する
一時停止中に'p'キーが押されると出力を再開する 
         ※ すみなせん、出題者不明です。

3つの方法(Timer, Thread, Task)で書いています。
BackgroundWorkerで使うって方法もあるけど、それほど代わり映えしないので書いてません。
まずは、Timerを使ったコード
using System;
using System.Threading;
using System.Threading.Tasks;

namespace Doukaku.Org {
    class Program {
        static void Main(string[] args) {
            Execute();
            Console.WriteLine("Fin");
        }

        // System.Timers.Timerを使ったコード
        private static void Execute() {
            using (var timer = new System.Timers.Timer()) {
                timer.Interval = 1000;
                timer.Elapsed += (s, e) => Console.Write('a');
                timer.Enabled = true;
                while (true) {
                    var key = Console.ReadKey(false);
                    if (key.KeyChar == 'q')
                        break;
                    if (key.KeyChar == 'p') {
                        timer.Enabled = !timer.Enabled;
                    }
                }
                timer.Enabled = false;
            }
        }
    }
}
次はThreadを使ったコード
private static void Execute() {
    ManualResetEvent mre = new ManualResetEvent(true);
    Thread thread = new Thread(
        () => {
            while (true) {
                Console.Write('a');
                Thread.Sleep(1000);
                mre.WaitOne();
            }
        }
    );
    thread.Start();
    bool suspend = false;
    while (true) {
        var key = Console.ReadKey(false);
        if (key.KeyChar == 'q') {
            mre.Set();
            break;
        }
        if (key.KeyChar == 'p') {
            if (suspend)
                mre.Set();
            else
                mre.Reset();
            suspend = !suspend;
        }
    }
    thread.Abort();
}

最後は、Taskを使った例。
なんだか、だんだん複雑になっていくなー。 前のコードと同じようにManualResetEventを使って、一時停止を実現しているけど、他によい方法はないのかしら?
Taskクラスは複雑すぎて僕には使いこなせません。Threadのほうがはるかに単純で扱いやすいと思うのは僕だけでしょうか?

private static void Execute() {
    CancellationTokenSource tokenSource = new CancellationTokenSource();
    ManualResetEvent mre = new ManualResetEvent(true);   
    var task = new Task(
        () => {
            while (!tokenSource.IsCancellationRequested) {
                Console.Write('a');
                Thread.Sleep(1000);
                mre.WaitOne();
            }
        },
        tokenSource.Token
    );
    try {
        task.Start();
        bool suspend = false;
        while (true) {
            var key = Console.ReadKey(false);
            if (key.KeyChar == 'q') {
                mre.Set();
                break;
            }
            if (key.KeyChar == 'p') {
                if (suspend)
                    mre.Set();
                else
                    mre.Reset();
                suspend = !suspend;
            }
        }
        tokenSource.Cancel();
        task.Wait();
    } finally {
        task.Dispose();
    }
}
  
Posted by gushwell at 21:54Comments(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年08月10日

C#で文字列に含まれる単語の最初の文字を大文字にする

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

■問題
文字列に含まれる単語について、それぞれの単語の最初の文字を大文字にしてください。 たとえば、"LL future" と与えられたときは "LL Future" と出力します。
"LL day and night" と与えられたときは "LL Day And Night" と出力します。
与えられる文字列はリテラルで表記する、標準入力で与えられる、引数で与えられるなどは自由とします。
出展: http://ll.jus.or.jp/2008/program/doukaku.html

この問題は、単語をどう認識するのかでコードが変わってきますね。
単純に空白で区切られているもの とするのか、もうすこし厳密にやるのか。
後者は正規表現使うことになりますね。
ここでは、空白で区切られているとした場合のコードを2つ、正規表現で定義された単語のケースを1つ 書いてみました。

■C#で書いたコード (その1)
using System;
using System.Text;
using System.Threading;
using System.Globalization;

namespace Doukaku.Org {
    class Program {
        static void Main(string[] args) {
            Console.WriteLine(ToUpper1St("LL day and night"));
            Console.WriteLine(ToUpper1St("LL future"));
            Console.WriteLine(ToUpper1St("Microsoft   windows  server."));
            Console.WriteLine(ToUpper1St("hello, i am a cat"));
            Console.WriteLine(ToUpper1St("日本語abc漢字"));
            Console.WriteLine(ToUpper1St("αβγ"));
            Console.WriteLine(ToUpper1St("abc"));
            Console.WriteLine(ToUpper1St("a=b+c"));
            string s = ToUpper1St("een");
            Console.WriteLine(ToUpper1St("een"));
            Console.ReadLine();
        }

        static string ToUpper1St(string s) {
            var words = s.Split(' ');
            StringBuilder sb = new StringBuilder();
            foreach (var word in words) {
                sb.Append(ToTitleCase(word) + " ");
            }
            return sb.ToString().TrimEnd(' ');
        }

        static string ToTitleCase(string s) {
            CultureInfo cultureInfo = Thread.CurrentThread.CurrentCulture;
            TextInfo textInfo = cultureInfo.TextInfo;
            return textInfo.ToTitleCase(s);
        }
    }
}
■C#で書いたコード (その2)
using System;
using System.Text;
using System.Threading;

namespace Doukaku.Org {
    class Program {
        static void Main(string[] args) {

            ... 省略 ...
        }

        static string ToUpper1St(string s) {
            StringBuilder sb = new StringBuilder();
            char prev = ' ';
            foreach (var c in s) {
                if (Char.IsWhiteSpace(prev) && Char.IsLower(c))
                    sb.Append(Char.ToUpper(c));
                else
                    sb.Append(c);
                prev = c;
            }
            return sb.ToString();
        }
    }
}
■C#で書いたコード (その3)
using System;
using System.Text;
using System.Threading;
using System.Globalization;
using System.Text.RegularExpressions;

namespace Doukaku.Org {
    class Program {
        static void Main(string[] args) {
            ... 省略 ...
        }

        // \W : 単語に使用される文字以外の任意の文字と一致します。
        // \w : 単語に使用される任意の文字と一致します。
        static string ToUpper1St(string s) {
            var reg = new Regex("(\\W*)(\\w+)");
            return reg.Replace(s, (m) => m.Groups[1] + ToTitleCase(m.Groups[2].Value));
        }

        static string ToTitleCase(string s) {
            CultureInfo cultureInfo = Thread.CurrentThread.CurrentCulture;
            TextInfo textInfo = cultureInfo.TextInfo;
            return textInfo.ToTitleCase(s);
        }

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

2014年08月06日

C#で指定された日の存在する週を求める

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

■問題
年、月、日を入力すると、指定された日の存在する週の月曜日から金曜日までを 出力するコードを書いてください。 週が月や年をまたぐケースが正しく動いているかに気をつけて投稿してください。
このお題は匿名での投稿をもとに作成しました。ご投稿ありがとうございます。

作成したGetWeekDaysメソッドは、与えられた日が存在する週の月曜日から金曜日までに日にちを列挙するメソッドです。
DayOfWeek列挙体の値の範囲は 0 (DayOfWeek.Sunday) 〜 6 (DayOfWeek.Saturday) なので、 現在の日から、DayOfWeek列挙体の値(intにしたもの)を引けば、日曜日が求まります。
これが求まれば、月曜日から金曜日を求めるのは簡単ですね。
DateTime構造体を使ってるので、月や年をまたぐケースは特に意識する必要はないです。 

PrintWeekDaysメソッドは、このGetWeekDaysメソッドを呼び出した結果を出力しています。

■C#で書いたコード

using System; using System.Collections.Generic; using System.Linq; namespace Doukaku.Org { class Program { static void Main(string[] args) { PrintWeekDays(new DateTime(2013,9, 27)); Console.WriteLine(); PrintWeekDays(new DateTime(2013, 11, 1)); Console.WriteLine(); PrintWeekDays(new DateTime(2013, 12, 31)); Console.WriteLine(); Console.ReadLine(); } static void PrintWeekDays(DateTime date) { var days = GetWeekDays(date).ToList(); days.ForEach(dt => Console.WriteLine(dt.ToString("yyyy/MM/dd (ddd)"))); } static IEnumerable<DateTime> GetWeekDays(DateTime date) { // 日曜日の日を求める int dow = (int)date.DayOfWeek; date = date.AddDays(-dow); // 月から金までを返す for (int i = 1; i < 6; i++) yield return date.AddDays(i); } } }
  
Posted by gushwell at 23:00Comments(0)TrackBack(0)

2014年08月03日

C#で九九の表示(桁合わせ)

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

■問題
掛け算の九九を下のように表示してください。
1 * 1 =  1
1 * 2 =  2
1 * 3 =  3
(略)
7 * 8 = 56
7 * 9 = 63
8 * 1 =  8
8 * 2 = 16
8 * 3 = 24
8 * 4 = 32
(略)
9 * 8 = 72
9 * 9 = 81
なお、この問題は掛け算をどうやるかではなく、どうやって右端がそろうようにレ イアウトするかに重点を置いているので、下のようなへこんだ出力は禁止です。
7 * 9 = 63
8 * 1 = 8
8 * 2 = 16

※ ごめんなさい出題者の方が分かりません。

右端そろえは、string.Format 使えば簡単に出来ますね。
using System;
using System.Linq;

namespace Doukaku.Org {
    class Program {
        static void Main(string[] args) {
            PrintTimesTable();
            Console.ReadLine();
        }

        private static void PrintTimesTable() {
            for (int i = 1; i <= 9; i++)
                for (int j = 1; j <= 9; j++) {
                    string s = string.Format("{0} * {1} = {2,2}", i, j, i * j);
                    Console.WriteLine(s);
                }
        }
    }
}

でも、これだけだと面白くないので、LINQ to Object 使って書いてみました。
まずは、クエリ構文使ったコード。

  private static void PrintTimesTable() {
      var q = from i in Enumerable.Range(1, 9)
              from j in Enumerable.Range(1, 9)
              select string.Format("{0} * {1} = {2,2}", i, j, i * j);
      q.ToList().ForEach(Console.WriteLine);
  }

次は、メソッド構文を使ったコード。 やってるいることは同じです。

  private static void PrintTimesTable() {
      Enumerable.Range(1, 9)
          .SelectMany(i => Enumerable
.Range(1, 9) .Select(j => string.Format("{0} * {1} = {2,2}", i, j, i * j))) .ToList() .ForEach(Console.WriteLine); }
  
Posted by gushwell at 21:12Comments(0)TrackBack(0)