2014/01/04

[プログラミング]通貨のレート計算

,
 3つめは"A.2 Currency Calculator"です。通貨のレート計算ですね。問題文を読めば分かりますが、仕様は
  • 引数に10進数と、2つのISO 4217準拠の通貨コードみたいなのを与えるので、もう一つの通貨でいくらになるか計算する
  • プログラムで扱えない通貨コードならエラーを吐く
  • 引数が無い場合は、てきとーな解説を吐く
です。

むずかしいところ - 通貨レートの取得

さて、これを実現するのには一つ問題があります。それは「変動する通貨レートを、どうやって取得するか」です。問題の出題者さんは「1$ = \100だよね!」みたいな感じで軽く出題したのかもしれませんが…

 幸いにして、ネット上で最新の通貨レートのデータを取得できるサイトがあります。

クジラ 外国為替 確認 API (為替 RSS) - http://api.aoikujira.com/kawase/

 ほんとはこのデータ元のXurrencyのAPIを使おうと思っていたのですが、Pricingページに"only 29,99 eruos per year"と書かれてたので諦めました。

 なんとかXML形式なら扱えるだろう、ということで、今回はこれを使うことにします。

コード

とりあえずコード貼っておきます。
using System;
using System.Linq;
using System.Xml.Linq;

namespace Rate1
{
    class Program
    {
        /// <summary>
        /// 通貨リストを作成する関数。といっても面倒なので、currencies.xmlを作る。
        /// </summary>
        static void MakeCurrencyList()
        {
            // Currency List(Compliant to ISO 4217) can be get from http://api.aoikujira.com/kawase/
            // (you should replace ", " to "\", \"")

            //var currencyList = new List<string> {"eur", "gbp", "aud", "brl", "cad", "chf", "cny", "dkk", "hkd", "inr", "jpy", "krw", "lkr", "myr", "nzd", "sgd", "twd", 
            //                                "zar", "thb", "sek", "nok", "mxn", "bgn", "czk", "huf", "ltl", "lvl", "pln", "ron", "isk", "hrk", "rub", "try", "php", 
            //                                "cop", "ars", "clp", "svc", "tnd", "pyg", "mad", "jmd", "sar", "qar", "hnl", "syp", "kwd", "bhd", "egp", "omr", "ngn", 
            //                                "pab", "pen", "ils", "uyu", "usd"};

            //var savexml = new XElement("Currencies");
            //savexml.Add(currencyList.Select(item => new XElement("Currency", item.ToUpper())));
            //savexml.Save(@"currencies.xml");
        }

        static void Main(string[] args)
        {
            // 使用できる通貨リストを、currencies.xmlから読み込む。
            var path = @"currencies.xml";
            var currencyxml = XElement.Load(path);
            var currencyList = from currency in currencyxml.Elements()
                             select currency.Value;
                
            // 引数のチェックをする
            if (3 > args.Length)
            {
                Console.WriteLine("args < 3");
                return;
            }

            // パラメータ(価格、元の通貨、相手先の通貨)
            var price = 0.0;
            try
            {
                price = double.Parse(args[0]);
            }
            catch (FormatException fe)
            {
                Console.WriteLine(fe.Message);
                return;
            }

            var srcCurrency = args[1].ToUpper();
            var dstCurrency = args[2].ToUpper();
            // 扱ってない通貨を指定すると、前の変換結果が返ってくる。
            // なので、仮にこういうエラーチェックしてる。
            if(!(currencyList.Contains(srcCurrency))){ 
                Console.WriteLine(srcCurrency + @"は扱えないです");
                return;
            }else if(!(currencyList.Contains(dstCurrency))){
                Console.WriteLine(dstCurrency + @"は扱えないです");
                return;
            }

            // レート表のxmlをDLする
            var url = @"http://api.aoikujira.com/kawase/xml/" + srcCurrency.ToLower();
            var elem = XElement.Load(url);

            // xmlのkawase/resultがokでなかったらエラー
            if (elem.Element("result").Value != "ok")
            {
                Console.WriteLine("XML result isn't ok");
            }
            else
            {
                // xmlからレートを取得する
                var rates = from p in elem.Elements()
                            where p.Name.LocalName == dstCurrency
                            select p.Value;

                foreach (var rate in rates)
                {
                    // 結果を表示する(結果は1つのはず…)
                    try
                    {
                        Console.WriteLine("{0} {1} == {2} {3}", price, srcCurrency, price * double.Parse(rate), dstCurrency);
                    }
                    catch (FormatException fe)
                    {
                        Console.WriteLine(fe.Message);
                        return;
                    }
                }
            }
        }
    }
}

コードは大きく分けてMakeCurrencyList()とMain()の2つです。前者は、「プログラムが対応している通貨レートを保存しておくため、対応通貨を書いたXMLを作成する」関数です。それもネットから取得してくればいーじゃんと思うかもですが、

  • そもそも上のAPIは、Invalidな通貨を指定すると、前に取得が成功したデータが返ってくる(キャッシュ?)(/kawase/iiiとかにすると、その前に指定した/kawase/jpyとかが来た) → よって、返ってきたデータで通貨対応してるか判断できない
  • 1つの通貨を指定すると、対応した他の(54種の)通貨全てとのレートが出るけど、これを用いて対応通貨を取得するとしても、最初の1つは指定しないといけない
  • そもそもXurrencyも上のサイトも、対応通貨を別表で配布したりはしてない(HPにはテキストで書いてある、けどHTMLをDOMしたりするのも大変そう)
  • じゃあ元からデータで持っておこう
という話でした。"JPY"で他通貨取得の方がいいのかなあ…

 XML作成ですが、面倒だからxmlns名前空間とかは特に指定してないのですが、多分したほうがいいのですね…

var savexml = new XElement("Currencies");
savexml.Add(currencyList.Select(item => new XElement("Currency", item.ToUpper())));
savexml.Save(@"currencies.xml");
ここでは、XElementを用いて、
<Currencies>
<Currency>EUR</Currency>
<Currency>GBP</Currency>
</Currencies>
のようなXMLファイルを作成しています。最初にルートのCurrencies要素を作って、その子としてcurrencyListの各要素を追加しています。

// パラメータ(価格、元の通貨、相手先の通貨)
var price = 0.0;
try
{
    price = double.Parse(args[0]);
}
catch (FormatException fe)
{
    Console.WriteLine(fe.Message);
    return;
}
ここはargs[0]( = 元の通貨での額)をdoubleにしてます。Parse()出来なかった時のためにtry~catchしてますが、「tryスコープ中で変数を宣言すると、tryのスコープが終わった瞬間に見えなくなる」という厄介な問題があるので、最初にわざわざ書いてます。もしかしたらTryParse()の方がいいのかもしれない…

コーディング規則

オンリーワンなコーディング規則、多分あると思うんですけど、天下のMicrosoftさんが「こう書け!」って言ってるみたいですので、今回はこれになるべく沿うように書きました。

C# のコーディング規則 (C# プログラミング ガイド) - http://msdn.microsoft.com/ja-jp/library/ff926074.aspx

でもコメントの//後に半角開けるのって気持ち悪い…
Read more →

2014/01/03

[プログラミング]ファイル読み込み

,
 100 Little Programming Exercises – go-left Softwareの"A.5 Count Words and Lines"です。と言ってもファイルの文字数と行数を数えるだけですが… 改行コードの猥雑さ(LF、CR、CRLF)を叩き込む、文字列処理の練習ということなんでしょうか?でも面倒だったので、Fileクラス使って済ませました。

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

namespace CountWordsandLines1
{
    class Program
    {
        static void Main(string[] args)
        {
            if (args.Length == 0)
            {
                Console.WriteLine("Specify a file name");
            }
            else
            {
                var filename = args[0];
                var encoding = Encoding.UTF8;
                // ファイルを読みこむ
                //if(File.Exists(filename)) //ファイルが存在するかは、File.Exists()か読み込み時例外のどっち?
                try
                {
                    var lines = File.ReadLines(filename, encoding);
                    Console.WriteLine("File name : {0} contains {1} words in {2} lines", 
                        filename, lines.Select(line => line.Length).Sum(), lines.Count());
                }
                catch (FileNotFoundException e)
                {
                    Console.WriteLine(e.StackTrace + "\n" + e.Message);
                }
                catch (System.Security.SecurityException e)
                {
                    Console.WriteLine(e.StackTrace + "\n" + e.Message);
                }
            }
        }
    }
}

 File.ReadLines(string, Encoding)はどうやら.NET 4.0追加の新しい関数らしいです。私は最初ストリームを使うものだと思ってました。で、この関数は面白いことに、返り値がIEnumerable<string>です。

.NET Framework 4 の基本クラス ライブラリの新機能 - http://msdn.microsoft.com/ja-jp/magazine/ee428166.aspx

 なので今回はこれを使って、LINQ拡張メソッドで文字数を数えてます。.Select(line => line.Length)で行ごとの文字数を数え、それを.Sum()で合計してます。
 行数は、1行毎にIEnumerable<string>に突っ込まれているので、この要素数になります。
 一応幾つかファイルを読み込んでみた結果では、合ってるみたいです。

ぎもん

  • ファイルを読み込むときに、やはりファイルが存在するかは確かめると思うんだけど、これはFile.Exists()を使って事前に確かめるべきなのか?File.ReadLines()してみてFileNotFoundExceptionをcatchすることで把握するべきなのか?
  • Encodingの問題 - 未知のファイルは多分事前にエンコーディングはわからないだろうけど、どうするんだろう。
  • 容量の大きすぎるファイル問題 - GB級ファイルの問題とか、メモリをどうするかとか
Read more →

[プログラミング]引数逆順

,
 お正月休みですね。といっても、バイトもなく、実家暮らしで暇なので、プログラミングの練習問題を探してはC#をごにょごにょしてます。

100 Little Programming Exercises – go-left Software - https://go-left.com/blog/programming/100-little-programming-exercises/

これのReverse The Inputです。引数は自動的に、スペースで区切られた単語ごとに配列に入れられるので、その配列を逆順にすればいいだけですが、最後の要素"END"は逆順にしません。

とりあえず書いたのは以下のコード。
static void Main(string[] args)
{
    var list = args.ToList();
    if (list.Last() != "END")
    {
        Console.WriteLine("args.Last() != \"END\"");
    }
    else
    {
        //http://melma.com/backnumber_120830_4798410/
        //IEnumerable<T>.Reverse()は範囲の指定ができない。
        //List<T>.Reverse()は範囲指定が出来るが、返り値がvoid。
        list.RemoveAt(list.Count() - 1);
        list.Reverse<string>().ToList().ForEach(arg => Console.WriteLine(arg));
    }
}

 例外処理はしておらず、LINQ拡張メソッドではなくList<T>の関数を呼んでるので、行数が多めだと思われます。
 本来は多分配列の逆順を実装する練習なんでしょうけど、各要素を後ろから挿入するくらいしか思いつかなかった…

 最初は最後の要素以外のReverse()(List<T>.Reverse(Int32, Int32))しようと思ったんですけど、RemoveしてIEnumerable<T>.Reverse()のほうがいい気がした。
 IEnumerableだかにForEachが無いから、ListのForEachを使うためだけにToList()してる(どうやら副作用回避のため実装してないけれど、皆同様の拡張メソッド作ってしまうみたいだね)。

LINQ拡張メソッドのTake()を使う

結局、自分が困っているのは「どうやって最後の要素以外を逆順するか?」ということで、上の例だとRemoveAt()を用いてList<string>から要素を削除して、それからReverse()して対処してるわけです。
 
 んでも拡張メソッド一覧見てたら、「配列の一部だけを抜き出す」というTake()を見つけたので、じゃあそっちのほうが一行で済むよね?ということでこうしました。
static void Main(string[] args)
{
    var endKey = "END";
    if (args.Last() != endKey)
    {
        Console.WriteLine("args.Last() != " + endKey);
    }
    else
    {
        //http://melma.com/backnumber_120830_4798410/
        //IEnumerable<T>.Reverse()は範囲の指定ができない。
        //List<T>.Reverse()は範囲指定が出来るが、返り値がvoid。
        args.ToList()
            .Take(args.Length - 1)
            .Reverse<string>()
            .ToList().ForEach(arg => Console.WriteLine(arg));
    }
}
このコードの問題は、args.ToList()した後に、argsのListを幾つ抜き出す(Take()する)かの指定に、Listする前のargsの要素数を参考にしているところ…本当だったらList<T>.Countを使うべきなんだろうけど、どうしたものか…
 これは病院から帰ってきて思いついたもの。

ぎもん

自分はLINQ星人ではないけれど、「最後の要素が○○ならプログラムを終わらせる」みたいなのを、もうちょっとスマートに書けたりしないかな。
Read more →

2014/01/02

[プログラミング]暗号の国のアリスと循環アルファベット++

,
 とてもタイトルに困ったんですが…とりあえず大した内容ではないです
 
 『暗号の国のアリス』(著:結城浩)の中でシーザー式暗号というものが取り上げられています。これは、暗号化したい平文のアルファベットをそれぞれ、アルファベット順にn文字前後にずらすといったものです。例えば、"abc"なら3文字ずらしで"def"といった具合です。
 で、この暗号はBrute Force Attackで破ることが出来るのですが、練習問題を解くプログラムを書いてて「どうやって実装すればいいんだろう???」って思ったときのメモです。

実装法1 - テーブルを使う

 どうしようか迷い、最初はテーブルを使うものにしました。つまり、string配列→char配列にし、それぞれの文字のn文字先を、予め用意したテーブルから持ってきます。この場合のキモは、n文字先がテーブルのどの要素に成るかの計算です(と言っても簡単なクイズレベルですけど)。難点としては、コードが長くなるのと、for文の中にforeachが入り二重ループになりそうなことです。

参考: C# でのアルファベットの文字の配列を生成 - http://ja.softuses.com/73951

 この時、大文字(0x41~0x5A)と小文字(0x61~0x7A)を分けるのが面倒だったので、最初の文字列を.ToLower()し、小文字に揃えました(今回はこれでオッケー)。

実装法2 - 拡張メソッドを使う

 で、さっきのページでC#の拡張メソッドを見て、「なんだもっと簡単に書けるじゃん」ということで、結局次のようになりました。
static void Main(string[] args)
{
    var str = "PELCGBTENCUL";  //対象文字列
    foreach(var i in Enumerable.Range(0, 26).ToArray())
    {
        Console.WriteLine("{0} : {1}", i, 
        new string(str.ToLower().Select(c => (char)('a' + ((i + c - 'a') % 26))).ToArray()));
    }
}
 すると、以下のようになり、13文字後ろにずらす(or??文字前にずらす)と、元の平文と思われる"cryptography"が分かります。
総あたり(といっても26回)した時の例
拡張メソッドはお勉強中なのですが、実践できる機会があってよかったです。
 コードとしては、
  • str.ToLower()で、まず小文字に統一します。
  • .Select()で、変数str中の各要素(char)に対して、そのi文字先(ただし、'z' + iなどはASCIIの小文字の範囲を超えるので、i文字先(i + c)から、先頭の文字を引き(- 'a')、更に26で割ることでどのアルファベットか割り出し(% 26)、オフセット0x61のために'a'を足す。という面倒なことをしています。
  • .ToArray()でchar[]にしています。
  • new string(...)は、char[]→stringにするときの常套手段みたいです。でもnewした割りにはその変数すぐ消えるし、なんかちょっと気持ち悪いですね…
拡張メソッドのように、関数の戻り値から、また数珠つなぎのように関数を呼び出し、1行に収めるのはなかなか楽しいですね。

ぎもん

この本では、この後の使い捨て暗号(ワンタイムパッド)が解読不可な理由として、「全ての平文の候補が登場するため、どれが答えかわからない」と説明されています。これには、全てが同じ平文とかが登場するから、と例で示されています。でも、シーザー式暗号のn(0 < n < 26)文字復元をしても、結局どれが平文か分からないような気もする…?(もしかしたら平文は単語ではないのかもしれない)
 結局、元の平文が人間味のある∧既知の単語∧平文候補が1つを覗いて辞書に載ってる単語じゃないとだめなのでは…
 こればかりは、使い捨てパッドが解読不可能である数学的証明を知らないと、真実に辿り着けなさそうですね…

 それと、暗号はやはり戦争に於いても利用されたようですが(ナチスドイツのエニグマetc)、「どうやって鍵を秘密裏に運ぶか」ということに苦心したんですね。前線の兵に秘密鍵を渡す方法というのも気になってきました。
Read more →