2014年11月30日

C#で指定コマンドを別プロセスで起動

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

■問題 (出題者 : todogzm さん)
与えられた文字列のコマンドを、別プロセスで実行してください。 異なるPIDのプロセスが立ち上がり、指定したコマンドを実行することが条件です。
あわせて、実行結果のリターンコードと、別プロセスが出力した標準出力を受け取る方法も記載してください。
今回投稿する上で、別プロセスとして実行するコマンドの与え方は自由ですが、実行した結果、何らかの損害を与えるようなコマンドは埋め込まないようにお願いします。

以前掲載した「外部の実行ファイルを呼び出し」と「標準入力と標準出力のリダイレクト」の知識があれば、難しくありませんね。
起動したコマンドが出力した標準出力の内容を取り込めるので、知っていれば役立つときがあるかもしれません。

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

namespace Doukaku.Org {
    class Program {
        static void Main() {

            // 標準出力をリダイレクトしてプロセスを起動
            string cmd = "help xcopy";
            int ix = cmd.IndexOf(' ');
            string pgm = (ix >= 0) ? cmd.Substring(0, ix) : cmd;
            string args = (ix >= 0) ? cmd.Substring(ix + 1) : "";
            Process p = new Process();
            ProcessStartInfo psi = new ProcessStartInfo(pgm);
            psi.Arguments = args;
            psi.RedirectStandardOutput = true;
            psi.UseShellExecute = false;
            p.StartInfo = psi;
            p.Start();

            // リダイレクトされた標準出力を読む
            string output = p.StandardOutput.ReadToEnd();

            // プロセス終了を待つ
            p.WaitForExit();

            // 結果を標準出力に表示する
            Console.WriteLine(output);

            // 終了コードの取得
            Console.WriteLine("終了コード={0}", p.ExitCode);

            Console.ReadLine();
        }
    }
}

  

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

2014年11月25日

C#で「アルファベットの繰り上がり問題」を解く

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

■問題 (出題者 : にしお さん)
Excelの桁表示は 1桁目はA、2桁目はB、以下C、D、 E…とすすみ、Zの次はAA AB AC…と続きます。AZの次はBAです。
この表記法で1から100までを表示してください。出力結果は下記のサンプルの「...」の部分に適切な文字列を埋めたものになります。

A, B, C, ... CU, CV

アルファベットの文字列を基に次の文字列は何かをもとめるというやり方(例えば、AZの次は、一桁目がZだから、一桁目をAにして二桁目は繰り上げて、Aの次のBにする)ではなく、整数から対応する文字列に変換するメソッドを作って、それを、1から100まで繰り返すという方法を採りました。
そういう意味では「繰り上がり」という問題の趣旨からは外れているかもしれませんね。

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

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

        static void Do() {
            Console.Write(ToAlphabet(1));
            for (int i = 2; i <= 100; i++) {
                Console.Write(", " + ToAlphabet(i));
            }
            Console.WriteLine();
        }

        static string ToAlphabet(int n) {
            string r = "";
            int syo = (--n) / 26;
            if (syo > 0)
                r += (char)('A' + syo - 1);
            return r + (char)('A' + (n % 26));
        }
    }
}

ただし、上のコードは "ZZ"以上の数に対応できないという大きな制限があります。
そこで、書き直したのが次のToAlphabetメソッドです。
"ZZ" のつぎは、"AAA"となります。 これならば桁数に制限はありません。int の最大値まで扱えるはずです。
まあ、問題は 100までということなので、上のコードでも良いとは思います。

static string ToAlphabet(int n) {
    string r = "";
    do {
        int amari = (n-1) % 26;
        r = (char)('A' + amari) + r;
        n = (n-1) / 26;
    } while (n > 0);
    return r;
}

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

2014年11月19日

C#で与えられた数字のケタ数を調べる

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

このシリーズも、とうとう50回めです。まだストックがあるので、もうしばらくは続けるつもりです。

■問題  (出題者 : susuさん)
与えられた数字のケタ数と、最大桁の位を求めてください。
数字が2469なら4桁で最大桁は1000の位です。
600なら3と100、1なら1と1です。

最初に思い浮かんだ方法で書いたコードは、文字列に変換して桁数を求めるもの。
2つの値を求めなくちゃいけないので、戻り値は、Tuple を使いました。
でも、Item1 とかItem2という無味感想な名前は、僕の好みじゃないなー。

using System;

namespace Doukaku.Org {
    class Program {
        static void Main(string[] args) {
            Resolve(9);
            Resolve(99);
            Resolve(100);
            Resolve(-4532);
            Resolve(98765);
            Console.ReadLine();
        }

        static void Resolve(long n) {
            dynamic ans = DigitNumber(n);
            Console.WriteLine("{0} の桁数は {1}桁です。最大桁は {2}の位です", n, ans.Item1, ans.Item2);
        }

        static Tuple<int, long> DigitNumber(long n) {
            int fig = Math.Abs(n).ToString().Length;
            long place = (int)Math.Pow(10, fig - 1);
            return Tuple.Create(fig, place);
        }
    }
}

でも、数学の問題なのに、文字列使うのはやだなーと思い、 次に書いたのが、文字列にせずに、桁数を求めたもの。
1を10倍ずつしていって、もとの数の絶対値より大きくなるまで繰り返せば、 桁数が分かります。

static Tuple<int, long> DigitNumber(long n) {
    long absn = Math.Abs(n);
    int fig = 0;
    long place;
    for (place = 1; place <= absn; place *= 10)
        fig++;
    return Tuple.Create(fig, place);
}

だったら、log10でも見つかるよね、ということで書いたのが次のメソッド。

static Tuple<int, long> DigitNumber(long n) {
    int fig = (int)Math.Log10(Math.Abs(n)) + 1;
    long place = (long)Math.Pow(10, fig - 1);
    return Tuple.Create(fig, place);
}

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

2014年11月16日

C#でRegexを使い文字列の一部を書き換える

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

元の問題の題は「税込み価格への修正」なのですが、タイトルを変更しています。

■問題 (出題者:にしお さん)
ここにチラシの原稿があります。例えば「ダイコン150円、ハクサイ120円、ジャガイモ30円」のような文字列です。
法改正によって商品の値段は税込み表示にしないといけなくなりました。
そこで、与えられた文字列の中から税抜き価格を見つけ出し、 税込み価格に変更した文字列を返す関数を作ってください。
なお、税抜き価格は半角の数字の連なりで、かつ半角の数字の連なりはすべて税抜き価格だとします。
「9,800円」「百円」「100円」「100g80円」などのような記述はないと考えてかまいません。
また税込み価格は税抜き価格の1.05倍で、端数は切り捨てとしてください。

MatchEvaluator を受け取る Replaceメソッドを使って実装しました。
呼び出しインターフェースは以下の通り。

public string Replace (
     string input,
     MatchEvaluator evaluator
)

このメソッドは、指定した入力文字列内で指定した正規表現に一致するすべての文字列を、 MatchEvaluator デリゲートによって返される文字列に置換します。
この手の文字列操作は、Regex使わないととても面倒ですね。

せっかくなので、税込み5%の価格表示のものを 税込み8%の価格に変更する ReplaceTax5to8 メソッドも 書いてみました。
地味な問題ですが、Regexの持つパワーを教えてくれる良問ですね。

using System;
using System.Text.RegularExpressions;

namespace Doukaku.Org {
    class Program {
        static void Main(string[] args) {
            string s = "test100円xyz438円、8130円";
            Console.WriteLine(ReplaceTax(s));
            Console.ReadLine();
        }

         // Regexを使えば、完結に書ける。使わないと、ものすごく面倒。
        public static string ReplaceTax(string s) {
            return Regex.Replace(
                        s,
                        @"\d+",
                        m => ((int)(int.Parse(m.Value) * 1.05)).ToString()
                   );
        }

        public static string ReplaceTax5to8(string s) {
            return Regex.Replace(
                        s,
                        @"\d+",
                        m =>  ((int)(Math.Ceiling((int.Parse(m.Value) / 1.05)) * 1.08)).ToString());
        }       
     }
}

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

2014年11月12日

C#でn日後を返す関数を返す関数を定義する

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

■問題 (出題者: にしお さん)
整数nを渡すと「日時のデータを受け取って、n日後の日時を返す関数」を返す関数を作ってください。 関数を返す関数が作れない場合は、関数の代わりになるようなオブジェクトでも構いません。
出題の意図としては「関数を返す関数」と「日時の差分の扱い方」を、それぞれ単体だと簡単すぎるので合わせ技にしてみた、というところです。

「n日後を返す関数」ではなくて、「n日後を返す関数」を返す関数 を定義せよ、という問題です。

C#では、デリゲートを返すメソッドを定義すれば良いということですね。
GetFuncNDaysLater というメソッドを定義し、その戻り値を Func<DateTime, DateTime> デリゲートとしました。
ラムダ式便利ですね。

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

namespace Doukaku.Org {
    class Program {
        static void Main(string[] args) {
            var FiveDaysLater = GetFuncNDaysLater(5);
            Console.WriteLine("{0:d}", FiveDaysLater(DateTime.Today));
            Console.WriteLine("{0:d}", FiveDaysLater(new DateTime(2014, 12, 27)));
            Console.WriteLine("{0:d}", FiveDaysLater(new DateTime(2038, 1, 30)));
            Console.ReadLine();

        }

        // 「日付を受け取り日付を返す関数」が戻り値なので、
        // 戻り値は、Func<DateTime, DateTime> ですね。
        // ラムダ式は、デリゲートに変換できるので、ラムダ式を返せばよい。
        static Func<DateTime, DateTime> GetFuncNDaysLater(int n) {
            return (DateTime dt) => { return dt.AddDays(n); };
        }

    }
}

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

2014年11月09日

C#で倍数になる13進数を求める

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

■問題 (出題者:にしお さん)
ここにある正の整数xがあります。xは2桁以上です。この数字の並びが13進法表記であるとみなすと、10進法表記であると見なした場合の倍数になります。この条件を満たす最も小さいxを求めるプログラムを書いてください。
例えばxが567の時、これを13進法表記と見なすと5 * 13 * 13 + 6 * 13 + 7 で 930 になります。930は567の倍数ではないので、567は条件を満たしません。 条件を満たす数を見つけ出すプログラムを書いてください。「条件を満たす数を出力するプログラム」ではありません。(print 567などは禁止ということ。)

実用的な問題じゃなくて、整数パズル問題ですね。
intを 13進数表記とみなして、10進数に変換する ToDecimalNumber メソッドを定義していますが、 これは、13進数表記以外にも対応しています。
なので、11進数とか、15進数なんかでも、答えが見つかります。
このToDecimalNumber さえ作ってしまえば、あとは簡単ですね。

最初は、標準のConvertクラスが使えるんじゃないかと思ったのですが、13をベース値にすることができませんでした
。 まあ、13進数なんてのは実際のアプリ開発では使われないので、サポートされていなくも仕方ないですね。

using System;

namespace Doukaku.Org {
    class Program {
        static void Main(string[] args) {
            var n = GetAnswer();
            Console.WriteLine(n);
            Console.WriteLine("{0}が{1}進数表記だとすると10進数で{2}、ちょうど2倍になります",
                n, BaseNum, ToDecimalNumber(n, BaseNum));
            Console.ReadLine();
        }

        private const int BaseNum = 13;

        static int GetAnswer() {
            for (int i = 10; i < int.MaxValue; i++) {
                if (IsOK(i)) {
                    return i;
                }
            }
            return -1;
        }

        static bool IsOK(int n) {
            return ToDecimalNumber(n, BaseNum) == n * 2;
        }

        // 13進数以外でもOK
        private static int ToDecimalNumber(int num, int basenum) {
            int ans = 0;
            int i = 1;
            while (num > 0) {
                int remainder = num % 10;
                ans += i * remainder;
                num /= 10;
                i *= basenum;
            }
            return ans;
        }
    }
}

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

2014年11月05日

C#で座標データの整列

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

■問題 (出題者: odz さん)
(x, y) の座標情報を以下の2種類の方法で整列する機能を実現してください。
* (x, y) の辞書順(まず x で昇順に整列して、xが同じデータに対して yで昇順に整列する)
* (0, 0) からの距離の昇順
データの表現方法はタプルなり構造体/オブジェクトなり各自で適当に選んで下さい。

データの表現には、タプルではなく、独自に定義した Pointクラスを使いました。
辞書順、距離順ともに、LINQ to Object を使えば簡単にできますね。

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

namespace Doukaku.Org {
    class Program {
        static void Main(string[] args) {
            // テストデータの作成
            List<Point> list = new List<Point>();
            Random rnd = new Random();
            for (int i = 0; i < 10; i++) {
                list.Add(new Point { X = rnd.Next(0, 10), Y = rnd.Next(0, 10) });
            }
            // テストデータを表示
            list.ForEach(Console.WriteLine);
            Console.WriteLine();

            Console.WriteLine("辞書順でソート");
            var r1 = SortByLexicalOrder(list);
            r1.ForEach(Console.WriteLine);
            Console.WriteLine();

            Console.WriteLine("距離順でソート");
            var r2 = SortByDistance(list);
            r2.ForEach(Console.WriteLine);

            Console.ReadLine();
        }

        //(x, y) の辞書順でソート
        private static IOrderedEnumerable<Point> SortByLexicalOrder(IEnumerable<Point> list) {
            return list.OrderBy(p => p.X).ThenBy(p => p.Y);
        }

        // (0, 0) からの距離の昇順でソート
        private static IOrderedEnumerable<Point> SortByDistance(IEnumerable<Point> list) {
            return list.OrderBy(p => p.X * p.X + p.Y * p.Y);
        }
    }

    class Point {
        public int X { get; set; }
        public int Y { get; set; }
        public override string ToString() {
            return string.Format("({0},{1})", X, Y);
        }
    }
}
  
Posted by gushwell at 22:30Comments(0)TrackBack(0)

2014年11月02日

C#で コメントの削除

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

■問題 (出題者:nobsun さん)
ソースコードからコメント部分を削除するプログラム decomment を書いてください. すくなくとも,decomment を記述したのと同じ言語で書かれているソースコードが 扱えるようにしてください.

DeComentクラスをStateマシン風に書いてみました。
ただし、Stateクラスを定義するのではなく、delegateで Stateを表現しています。
ラムダ式使えるので、なんかいい感じです。
ちゃんと、C#の文字列リテラルの中の // や、 /* はコメントと認識しなようになってます。
でも、//で始まるコメント行が、削除されずに空白行となるのが、いまいちかな。

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


namespace Doukaku.Org {
    public class DeComment {
        delegate void Scan(char c);
        private static string result = "";       
        private static Scan eat;

        private static Scan outComment = (c) => {
            if (c == '/')
                eat = preInComment;
            else {
                if (c == '"')
                    eat = InLiteralString;
                result += c;
            }
        };

        private static Scan preInComment = (c) => {
            if (c == '*')
                eat = inMultiLineComment;
            else if (c == '/')
                eat = inOneLineComment;
            else {
                eat = outComment;
                result += "/" + c;
            }
        };

        private static Scan inOneLineComment = (c) => {
            if (c == '\r' || c == '\n') {
                eat = outComment;
                result += c;
            }
        };

        private static Scan inMultiLineComment = (c) => {
            if (c == '*')
                eat = preOutMultiLineComment;
        };

        private static Scan preOutMultiLineComment = (c) => {
            if (c == '/')
                eat = outComment;
            else
                eat = inMultiLineComment;
        };

        private static Scan InLiteralString = (c) => {
            if (c == '"')
                eat = outComment;
            else if (c == '\\')
                eat = InLiteralStringEscape;
            result += c;
        };

        private static Scan InLiteralStringEscape = (c) => {
            eat = InLiteralString;
            result += c;
        };

        public string Execute(string line) {
            result = "";
            eat = outComment;
            foreach (var c in line) {
                eat(c);
            }
            return result;
        }

    }

}

使い方は、こんな感じ。
ファイルから読み込んだテキストを改行コード込みで、そのまま Decommentクラスの Executeメソッドに渡します。

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

namespace Doukaku.Org {
    class Program {
        static void Main(string[] args) {
            var cc = new DeComment();
            var text = File.ReadAllText("Sample.cs");
            var s = cc.Execute(text);
            Console.WriteLine(s);
            Console.ReadLine();
        }
    }

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