Archive - Programming
はてなダイアリー to HTML
- C# - 10.03/09
はてなダイアリーの記事を根こそぎ取得してローカルHTMLに保存するアプリケーションです。過去ログを全部取得して昇順に並び替えます。カテゴリ指定も可。 本文抽出アルゴリズムだなんて高尚なことはせず、HTMLをそのまま切り出しているだけのはてなダイアリー完全特化なぶんだけ、デザインやsyntax-highlightなどもそのままで見ることができます。上の画像はNyaRuRuの日記(勝手に貼ってすみません)の.NETカテゴリーを抽出しているところ。私がC#やLinqを覚えられたのはNyaRuRuさんの日記のお陰といっても過言ではなく、しかも読み返す度に新しい発見があって本当に素晴らしい。ので、度々読み返しているのですが、はてな重い。重い。なら全部ぶっこぬけばいいぢゃない。というのが作った理由でして……。
あと、最近こそこそごそごそとC++も勉強中なので、 [C++] - Cry’s Diaryや [C++] - Faith and Brave - C++で遊ぼうを読むと、(大体は全く分からないのですが)勉強になります。なお、Permalinkは相対パスになってしまい使えないのですが、日付の部分は絶対パスなので、コメント見たくなったりPermalinkを取りたくなったら日付から辿れます。
こうしてHTMLを自炊(?)すると、電子ブックリーダー欲しくなりますね。それと、リーダーはやっぱブラウザが載ってないとダメよねー。PDF(と独自形式?)だけ見れても嬉しくぁない。そんなに本には興味ない。HTMLが見たいのです。Twitterのログが見たいのです。2chまとめサイトが見たいのです。海外の技術書は結構PDFで買える感じなのでそれはそれで気になるところですが――。
以下ソースコード。↑のzipにも同梱してありますが。コンパイルにはSGMLReaderが必要です。
static class Program { static IEnumerable<T> Unfold<T>(T seed, Func<T, T> func) { for (var value = seed; ; value = func(value)) { yield return value; } } const string HatenaUrl = "http://d.hatena.ne.jp"; static void Main() { Thread.GetDomain().UnhandledException += (sender, e) => { Console.WriteLine(e.ExceptionObject); Console.ReadLine(); }; Console.WriteLine("抽出対象のはてなIDを入力してください"); var id = Console.ReadLine(); Console.WriteLine("カテゴリを入力してください(全ての場合は空白)"); var word = Console.ReadLine(); Console.WriteLine("出力ファイル名を入力してください"); var fileName = Console.ReadLine(); // 抽出クエリ! var root = XElement.Load(new SgmlReader { Href = HatenaUrl + "/" + id + ((word == "") ? "" : "/searchdiary?word=*[" + Uri.EscapeDataString(word) + "]") }); var contents = Unfold(root, x => { var prev = x.Element("head").Elements("link") .FirstOrDefault(e => e.Attribute("rel") != null && e.Attribute("rel").Value == "prev"); if (prev == null) return null; retry: try { var url = HatenaUrl + prev.Attribute("href").Value; Console.WriteLine(url); // こういうの挟むのビミョーではある return XElement.Load(new SgmlReader { Href = url }); } catch (WebException) // タイムアウトするので { Console.WriteLine("Timeout at " + DateTime.Now.ToString() + " wait 15 seconds..."); Thread.Sleep(TimeSpan.FromSeconds(15)); // とりあえず15秒待つ goto retry; // 何となくGOTO使いたい人 } }) .TakeWhile(x => x != null) .SelectMany(x => x .Descendants("div") .Where(e => e.Attribute("class") != null && e.Attribute("class").Value == "day")) .TakeWhile(e => !Regex.IsMatch(e.Value, @"^「\*\[.+\]」に一致する記事はありませんでした。検索語を変えて再度検索してみてください。$")) // 間違ったカテゴリ入力した時対策 .Reverse(); // 古いのから順に見たいので // style抽出 var styles = root.Element("head").Elements("link") .Where(e => e.Attribute("rel").Value == "stylesheet") .Select(e => { e.SetAttributeValue("href", HatenaUrl + e.Attribute("href").Value); return e; }) // 副作用ダサい .Concat(root.Element("head").Elements("style")); // HTML組み立て! var html = new XStreamingElement("html", // まあ、Reverseでバッファに貯めるので焼け石に水ですけどね、XStreamingElement new XStreamingElement("head", styles), new XStreamingElement("body", // new XElement("div", new XAttribute("class", "hatena-body"), サイドバーとか邪魔なので無視 // new XElement("div", new XAttribute("class", "main"), new XStreamingElement("div", new XAttribute("id", "days"), contents))); // 保存 var path = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().FullName), fileName + ".html"); var xws = new XmlWriterSettings { Indent = true, CheckCharacters = false }; // 不正な文字のあるサイトを書き出すと落ちるので防止 using (var xw = XmlWriter.Create(path, xws)) { html.Save(xw); } } }
try-catchが出るとゴチャついて嫌。なのだけど、しょうがないか。それと Unfoldはどうしたものかねえ。極力、標準演算子のみで済ませたいんですが、今回はちょっと使わざるを得なかったと思っています。次のページのURLを得るには、取得したHTMLから解析しなければならない。下流で解析し取得した次のURLは、上流に渡さなきゃいけない。のですが、通常は下から上に渡せないのがLinqなのよね。そんな場合、外部変数を介して渡すか、Unfoldか、場合によってはScanなんかを使うかになるわけで、とにかく外部変数は避けたかったのでUnfoldを使いました。
HTMLへの書き出し部分では物珍しい XStreamingElementを使ってみました。今回はただ単に書き出すだけなので、通常のXElementのようにメモリ内にツリーを保持する必要はないし、相当大きいXmlを扱うため効率も気になってくるところ。そこで遅延ストリーム書き込みを可能にするXStreamingElementの出番です。詳しくは 方法: 大きな XML ドキュメントのストリーミング変換を実行する をどうぞ。とはいっても、このプログラムでは反転させるためReverseでバッファに全て溜め込んでいるので、まあ……。XStreamingElementって言いたいだけちゃうんか、みたいな。
デザインはdiv class=hatena-bodyとdiv class=mainを抜いているので(不必要なサイドバーの描画を除去するため)、この二つに依存するCSSが書かれているサイトの場合はデザインが崩れることがあります。ちなみにneuecc clipはこの二つどころか、その他にもwrapperを置いているというデタラメなCSS構造をしているため、デザインは保存出来ません。全くもって酷い。もっとスクレイピングに優しいHTMLを書かないとダメですな。
HTMLへの書き出し部分ははまりどころでした。最初Save(fileName)で保存していたんですが、特定のサイトの特定の部分で落ちてしまって困りました。具体的には 2008-07-23 - Faith and Brave - C++で遊ぼう で(例に出してすみません)、Protocol Bufferによる出力結果がInvalidXmlCharに引っかかってアウト、のようです。回避する方法は、XmlWriterSettingsのCheckCharactersをfalseに設定したXmlWriterを生成して書き出せばOK。
C#でスクレイピング:HTMLパース(Linq to Html)のためのSGMLReader利用法
- C# - 10.03/02
Linq to XmlがあるならLinq to Htmlもあればいいのに!と思った皆様こんばんは。まあ、DOMでしょ?ツリーでしょ?XHTMLならそのままXDocument.Loadで行けるよね?XDocument.Parseで行けるよね? ええ、ええ、行けますとも。XHTMLなら、ね、ValidなXHTMLならね。世の中のXHTML詐称の99.99%がそのまま解析出来るわけがなく普通に落ちてくれるので、XDocumentにそのまま流しこむことは出来ないわけです(もちろん、うちのサイトも詐称ですよ!ていうかこのサイトのHTMLは酷すぎるのでそのうち何とかしたい……)。
そこでHtmlを整形してXmlに変換するツールの出番なわけですが、まず名前が上がるのがTidy、の.NET移植であるTidy.NETで、これは論外。とにかく面倒くさい上に、パースしきれてなくてXDocumentに流すと平然と落ちたりする。おまけにXDocumentに入れるには文字列にしてから入れる必要があって二度手間感がある、などなど全くお薦めできません。XboxInfoTwitはTidy使ってますが、後悔してますよ……。
次にHtml Agility Packで、これは中々良いです。Linq to Xml風で大変使いやすい。のですが、あくまで風味であって、なんで本物のLinq to Xmlが目の前にあるのに、それっぽく模したものを覚えなきゃいけないの?二度手間で面倒くさいよ。
そこで、第三の選択としてSGMLReaderを使うという方法を提案します。SGML Reader自体は古くからあるのですが、日本語での情報はあまりないみたいだしLinq to Xmlと組み合わせたスクレイピング用途、に至っては皆無のようなので、ここで紹介しましょう。とりあえず例を。
// たったこれだけのメソッドを用意しておけば static XDocument ParseHtml(TextReader reader) { using (var sgmlReader = new SgmlReader { DocType = "HTML", CaseFolding = CaseFolding.ToLower }) { sgmlReader.InputStream = reader; // ↑の初期化子にくっつけても構いません return XDocument.Load(sgmlReader); } } static void Main(string[] args) { using (var stream = new WebClient().OpenRead("http://www.bing.com/search?cc=jp&q=linq")) using (var sr = new StreamReader(stream, Encoding.UTF8)) { var xml = ParseHtml(sr); // これだけでHtml to Xml完了。あとはLinq to Xmlで操作。 XNamespace ns = "http://www.w3.org/1999/xhtml"; foreach (var item in xml.Descendants(ns + "h3")) { Console.WriteLine(item.Value); // bingでlinqを検索した結果のタイトルを列挙 } } }
見たとおり、信じられないほど簡単です。SgmlReaderはXmlReaderを継承しているため、XDocument.Load(xmlReader)にそのまま流し込めます。また、SgmlReader自体もDocTypeとInputStreamを設定するだけという超簡単設計になっているため、楽にHtml to Xmlが実現。HtmlはXDocumentになってしまえさえすれば、あとは慣れ親しんだLinq to Xmlの操作で抽出していけます。
例では、BingでLinqを検索した結果の検索結果見出し部分を抽出しています。見出しはh3で囲まれているので、Descendants(ns + “h3″)。以上。超簡単。C#のスクレイピングの簡単さはRubyも超えたね!
残る問題は、日本語を扱う際はエンコーディング周りの設定が面倒くさい(間違ったエンコーディングだと文字化けする)、ということなのですが、そのWebClientのエンコーディング問題は、.NET Framework 4.0から修正された System.Net.WebClient は、HTTP ヘッダーから Encoding を自動的に認識してほしい | Microsoft Connect らしいです。素晴らしい!提案して頂いたbiacさんに感謝。
追記(より簡単に)
上の記事を書いてから気づいたのですが、HrefプロパティにURLを指定するだけで、中でStream類を作って自動的にHtmlだと判別してくれるようです。更には、エンコーディングもContentTypeを見て自動調整してくれます(詳しくはSgmlParser.csのOpenメソッドを参照)。よって、もっとずっと簡単に書けます。
static void Main(string[] args) { XDocument xml; using (var sgml = new SgmlReader() { Href = "http://www.xbox.com/ja-JP/games/calendar.aspx" }) { xml = XDocument.Load(sgml); // たった3行でHtml to Xml } // Xboxの発売スケジュールからタイトルと発売日を抜き出してみる var ns = xml.Root.Name.Namespace; var query = xml.Descendants(ns + "table") .Last() .Descendants(ns + "tr") .Skip(1) // テーブル一行目は項目説明なので飛ばす .Select(e => e.Elements(ns + "td").ToList()) .Select(es => new { Title = es.First().Value, ReleaseDate = es.Last().Value }); // 書き出し foreach (var item in query) { Console.WriteLine(item.Title + " - " + item.ReleaseDate); } }
usingの辺りが若干鬱陶しいので、最初の例のようにメソッドに切り出してもいいかもしれません(CaseFolding.ToLowerも付けたいし)。Loadし終わったらストリームはもう不要です。XDocumentはメモリ内に全部構築するタイプのもので、実質XmlDocument(DOMツリー)の代替となっています。抽出時のXml名前空間ですが、サイトによってついていたりついていなかったりするので、var ns = xml.Root.Name.Namespaceとしておくと、全てのサイトに対応出来ます。
抽出のテクニック
上の例は Xbox.com | Xbox ゲームソフト 発売スケジュールからタイトルと発売日を抽出するというものです。定期的にページを監視して、更新されたらTwitterに投稿するXbox発売予定BOTとか作れますね!Xmlで取れるAPIさえあれば……なんてことはなくなりました!これからは 全てのサイトが易々とスクレイピング可能な代物として浮き上がってきますな。
ただし、元がHTMLのものはAPIとして用意されているXMLと違って、抽出に優しくない構造をしています。 Descendants一発でOk、というわけにもいかないので若干の慣れは必要かもしれません。今回の例の抽出コードが何やってるかよくわからない、という人はHTMLソースと見比べてみてください。目的のTableに辿り着くにも、いくつかの方法があります。決め打ち成分が入ってしまうのはどうにもならないのですが、何を決め打ちにするのがスッキリ書けるのか、となると色々です。今回はTableがLastである、という点を使いましたが、他の方法を考えてみると
var case2 = xml.Descendants(ns + "div") .Where(e => e.Attribute("class") != null && e.Attribute("class").Value == "XbcWpFreeForm1") .SelectMany(e => e.Descendants(ns + "tr")); var case3 = xml.Descendants(ns + "table") .First(x => x.Ancestors().Any(e => e.Attribute("class") != null && e.Attribute("class").Value == "XbcWpFreeForm1")) .Descendants(ns + "tr");
目的のTableを囲むdivのclassが XbcWpFreeForm1であり、XbcWpFreeForm1が適用されているdivは一つしかない、ということに着目するとこうなります。case2は、divを全て列挙して探し出す方法。First(predicate)ではなくWhere.SelectManyにすることで、目的のTableが複数個ある場合でも対応出来ます。case3はTableに絞った上で、そのTableの上位階層(Ancestors)にXbcWpFreeForm1が含まれるかを探し当てる方法。Last、という決め打ちが難しい(場合により変動するケース)場合には有効でしょう。この、上位階層(Ancestors)や下位階層(Descendants)の要素に特異な要素はないか(Any)、と探す手法は、ターゲット自体に特徴がなく抽出し難い場合に活用出来ます。
そもそもDescendantsなんて富豪すぎて許せん、実行効率命!という人はElement(”body”).Element(”div”)….とトップから掘っていってもいいわけですが、さすがにElementの連続はダルいのでXPathを使うのもよいでしょう。Linq to XmlでのXPathの利用法は、以前neue cc - そしてXPathに戻るに書きました。XPathは、複雑なことを書こうとすると暗号めいた感じになるから余りすきじゃないですね。私は多少冗長なぐらいでもLinqで書くのが好きだなあ。
何故C#には(Javaの)Collections.sortに相当するものがないのか
Java は Collections.sort があるのに .NET は List 自身が Sort メソッドを持っているのはなぜ?
だそうです。あまり疑問でもないです。だってIListはSortないし。比較するならIList - ListでありList
そして何故かスレッドの話は迷走していて不思議。パフォーマンス? んー……。勝手な印象論ですみませんが、1さん(名前を出すのもアレなので1さん、ということにさせてください)はJavaのCollections.sortの性能を勘違いしているんじゃないかな? 配列のコピーを問題にしてるようだけど、Javaの方式はコピー、してますよ。もっとも最初の方の発言(効率は重要じゃない)と後ろの発言(コピーが嫌)が矛盾していますが。ともかく、実際のSunの実装を見てみましょう。
public static <T extends Comparable<? super T>> void sort(List<T> list) { Object[] a = list.toArray(); Arrays.sort(a); ListIterator<T> i = list.listIterator(); for (int j=0; j<a.length; j++) { i.next(); i.set((T)a[j]); } }
toArrayしてコピー。それをsortして、forで詰め替え。C#で同じような外部Sort関数を書くのならば以下のようになります。厳密には違いますが、それは最後に述べます。
static void Sort<T>(IList<T> list) { T[] array = new T[list.Count]; list.CopyTo(array, 0); Array.Sort(array); // List<T>のSortもArray.Sortを利用しています for (int i = 0; i < array.Length; i++) { list[i] = array[i]; } }
forで詰め替えている様は、マジマジと見ると微妙。実際、このコードには問題があります。クイズだと思って、どこが問題なのか考えてみてください、答えは最後に述べます。
List<T>のSortは、内部に配列を持っているため、CopyToとforでの詰め替えが不要です。パフォーマンスで言えば理想的な形になっているわけです。 ゲッタとセッタ経由でソートの入れ替えすればいいぢゃん、というのはそうですね、違います。配列のほうが速いし!というのもそうでしょうが、それ以前にListインターフェイスの実装は自由です。例えばLinkedListのゲッタを考えてみたらどうでしょう。getの度に前(もしくは後ろ)から走査があるため、パフォーマンスが悲惨な事になるのは容易に想像出来ます。ListはArrayListだけじゃないので、ゲッタ/セッタに頼るのは無理がある。汎用的に、Listインターフェイスならば何でも受け入れるという設計にする以上、コピーを作るのは不可避です。
というわけで、パフォーマンス云々を言うならば、内部を知っているListクラスがSortを持つのはベストな選択でしょう。そして利便性を考えてもベストな選択。ならばいったい何処に不満があるのでしょうか?
Javaのほうが優れているのは、「自前でIListを実装したクラス」を破壊的ソートするのに、クラスにSortメソッドを用意したくない。といったところでしょうか。Sortの実装自体は通常はArray.Sortを呼ぶだけなので簡単なので別に手間でもないのですけどね……。というか、この手の基本アルゴリズムを自前実装したのを使うのは悪です(勉強用に、なら当然すべきで悪なのは自前実装の妄信です)。
ようするところ、Sortメソッドを自前で用意したくないけど破壊的ソートが欲しい、ということになる。ふーむ、個人的にはなくてもいいかな。非破壊的なソートがLinqのOrderByを使うことで可能なので、破壊的のほうを欲しいとはあまり思わない。
ListIterator
では本題。何でC#には破壊的ソートをしてくれる外部関数がないの?というと、インターフェイスの都合上、不可能だから。が理由だと私は考えています(そもそも必要性薄いから、が最大の理由だと思いますがそれはそれとして)。Javaのsortと私の書いたC#のSortを見比べてください。大きな違いがあります。それは、forでソートした配列をリストに詰め直している部分。
i.set((T)a[j]); // Java list[i] = array[i]; // C#
C#の場合、listがLinkedListのようなものだった場合は悲惨なことになります。それに比べて、JavaではListIteratorを用いているため、配列と同じ処理効率で値をセット出来ます。C#には、このJavaのListIteratorに相当するものがないので、全てのIListに対して問題なく性能を発揮する破壊的ソート関数を作成することは不可能です。
「LinkedListのようなもの」という歯切れの悪い言い方をしたのは、.NETのLinkedList
ただ、IListのインデクサは取得にコストがかからないことを期待、してもいいとは思います。そんなのを一々気にしてたら何も作れない。それにちゃんと、.NETのLinkedListはIListじゃないしね。ね。というわけで上のほうで出したクイズは、問題があるかないかは何とも言えない微妙ラインです。んーと、つまりはfor(int i;i < hoge.length(); i++)の問題点はどこだー!みたいな話で、基本的にはhoge.length()なんてコストがかからないのを期待して問題ないし、コストがかかるんならそのクソクラスが悪い、みたいな。
ついでに個人的な意見ですがListインターフェイスにListIteratorはそこまで必要ではない。普通のIteratorと機能がかなり被る割には、使う機会はとても少ない。おまけに、ListIteratorのsetってoptionalで、実装されていることが保証されてない。この手の、実装しなくてもいいインターフェイスって撲滅した方がいいと思うんですけどねー。私は怖くて呼べません。
とはいえ、保証されないインターフェイスを完全に撲滅など出来はしません。例えばList(Java)/IList(C#)のAddは実行出来ることが保証されていない。JavaならArrays.asList(1, 2, 3).add(4)を、C#なら(array as IList
まとまってませんが、結論としては疑問に思ったらソース読むのが手っ取り早い、とかそんなところで。Javaの良いところはC#に比べてフレームワークのコードへのアクセスが簡単なところですね。C#は部分的にはコードは公開されていて大変タメになるのですが、色々と面倒くさいし、公開されてない範囲も少なくないし……。.NET Reflectorのお世話になりまくってイリーガルな気分を味わうのはもう嫌ぽ。嘘。リフレクタ大好きですがそれはそれ。
Re:Scheduled
- Java - 10.02/18
Windows7杯では、無事グランプリを逃して部門賞に落ち着いたりした昨今ですがこんばんわ。長らくBlog放置、ついでにゲームも放置中という有様なので、改めてスケジュールを考えなおさないとな、というところに来てたりします。今年はまずはlinq.jsのWSH対応をやるぞ!とか言ってた気がしますがちっともやってませんし。で、代わりに何をやっているかというと、何故かここ一ヶ月は延々とLinqのJava移植をやっていたりします。どうしてこうなった……。予定なんて立てるだけ無駄ってことですな。
そんなわけで、今週中、無理でも来週中にはリリースするつもり、です。もう実装は出来上がっているのですがテストを一切書いてないので、テスト書きとJavaDoc書きと、あと事前条件の例外送出(これも一切書いてない)が残っているので、やらないといけない。実装はやる気満々にノリノリで書けたんですが、残ったこれらをやるのはモチベーションが全然上がらない。ダルぃ。シュタゲやりたい(やれよ)
進捗は例によってneuecc on Twitterでgyazoで画像張りながら、Javaに文句つけながらやってます。スタンダードにwhere-selectとか(NetBeansの赤線は解決しました)、正規表現がスッキリ!とかは、まぁまぁ悪くないと思うのですが、左外部結合で記述がカオスなどは何とも言えないネタ感が。作者の私がメソッドのシグネチャを合わせられなくて苦労したぐらいなので、一体誰が使えるんだよ、という。Join系は鬼門です。あと、匿名型の代わりにTupleを使うのですが、型定義が地獄になるのもねえ。 定番の無限フィボナッチなんかも書いたけど、Tupleの型定義のクドささえなければ見れるのだけど、現状だと少し厳しい。
// static importと合わせるとLLっぽく見えて素敵、的な何か for (int i : range(5, 10)) { System.out.println(i); } // 10以上のもののうち重複なしで二倍にしてリストに格納する的な何か int[] array = {3, 13, 51, 2, 1, 51, 67, 32, 13, 9}; ArrayList<Integer> list = Enumerable.from(array) .where(Predicates.greaterEqual(10)) .distinct() .select(Functions.multiply(2)) .toList(); // [26,102,134,64]
Java7のクロージャと型推論に期待しつつ、今のうちに出来ることはやっておく、的な何か。期待しないで期待して待っててください。多分、思われているよりかはマトモに使える代物だと思います。移植なんて誰もが考えるし、LinqのJava実装なんて当然幾つかあるんですが、どれも全然徹底してない(中途半端にSQLには対応してる)ので、今一つなんですね(JavaScriptの時も同じこと思ったわけだけど……)。ひたすら愚直にLinq to Objectsだけを忠実移植したものは初になると思います。いやまあ、何で誰もやらないかっていうと誰得としか言いようがないから、ってのもありますが。私は、割と普通なLinq好きーなのでそんなのちっとも気にしない!
標準Linq以外のメソッド類はlinq.jsにあるのは大体入れた。linq.jsのRangeDownToはアホだったので削った。RangeTo(0,-10)でいいわけですよね、Toなのだから。DownToとかアホすぎた、といったような反省も若干活かしてます。それとRxのCatchとかFinallyも入れた。良さそうなのは何でもいれますよー。というわけでもないのですけどね、ちょっと便利、程度だと躊躇います。入力補完に大量にメソッドが出るってのはあんまり嬉しいものでもないですから、なるべく厳選して。
AnonymousComparer - ver.1.2.0.0
- C# - 10.02/04
AnonymousComparerを再度バージョンアップしました。ダウンロードは上記リンク先、CodePlexからどうぞ。バグがなければ、これで最後だと思います。いやもう内容的には出尽くしたかな、と。更新内容はIComparer<T>を作成可能にしました。また、OrderByでIComparer<T>を利用するものへ、拡張メソッドを追加しました。
// こんなシーケンスがあるとして、IComparer<T>を使用してその場で自由に比較を指定したい var seq = new[] { 1, 2, 3 }; // IComparer<T>を作る var comparer = AnonymousComparer.Create<int>((x, y) => y - x); seq.OrderBy(x => x, comparer); // OrderBy/ThenByに拡張メソッドが追加されているので、型推論が効いたまま書けます // List.Sort(Comparison)みたいなイメージですかね seq.OrderBy(x => x, (x, y) => y - x); // 3, 2, 1 seq.OrderByDescending(x => x, (x, y) => y - x); // 1, 2, 3
LinqにはDescendingが用意されているので、あまり使い道はなさそうですね。私もOrderByのICompare<T>オーバーロードを使いたいと思ったシチュエーションが今までにありませんし……。第一引数がkeySelectorなので、それで十分用を足せちゃうのですよね。それにしても、DescendingでIComparerを指定した場合の結果は紛らわしくていかんですな。
さて、更新内容はもう一つあって、むしろこっちのほうが重要なのですが、compareKeySelectorを利用したオーバーロード(Linq演算子への拡張メソッドは全部それです)で、シーケンスにnullが含まれている場合にnullで落ちるのを修正しました。今回からはヌルぽで落ちません。どういうこっちゃ、というと説明しづらいのでコードで。
class MyClass { public int MyProperty { get; set; } public override string ToString() { return "Prop = " + MyProperty; } } static void Main() { var array = new[] { new MyClass{MyProperty=1}, null, new MyClass{MyProperty=2}, null, new MyClass{MyProperty=1} }; var r1 = array.Count(); // 5 var r2 = array.Distinct().Count(); // 4 (nullが重複として消える) foreach (var item in array.Distinct(mc => mc.MyProperty)) { Console.WriteLine((item == null) ? "ヌルぽ" : item.ToString()); } // 出力結果は // Prop = 1 // ヌルぽ // Prop = 2 }
といった感じです。分かったような分からないような?
AnonymousComparer - ver.1.1.0.0
- C# - 10.01/26
AnonymousComparerをバージョンアップしました。ダウンロードは上記リンク先、CodePlexからどうぞ。更新内容はCreateのオーバーロードに追加して、IEqualityComparerの完全模写を可能にしました。今まではキー選択だけだったのですが、今回からはEqualsとGetHashCodeを個別に指定することが可能です。
var myClassComparer = AnonymousComparer.Create<MyClass>( (x, y) => x.MyProperty == y.MyProperty, // Equals obj => obj.MyProperty.GetHashCode()); // GetHashCode
こんな感じに指定します。全くもってそのままです。Equalsとかをラムダ式で指定するというだけです。型推論は効きませんので指定してやってください。なお、使い方自体は全然変わってませんので、普通の使い方は初回リリース時の記事を参照ください。
以下オマケ。
// ランダムでtrueかfalse返すComparer var rand = new Random(); var randomComparer = AnonymousComparer.Create<MyClass>( (_, __) => rand.Next(0, 2) == 0, _ => 0); // 間引くのに使えるぜ! var mabiita = list.Distinct(randomComparer).ToArray(); // とか思ったけど、最初の一つ目は必ず選択されるのよね(Distinctなので当然……) // といった、特殊な間引き方をしたい人はどうぞ(いません) // 普通にやるならWhereでランダムにフィルタリングするのを選びます
色々と用途が考えられるようで、そもそもEqualityComparer自体があまり使う状況ってないので、使い道が考えられない微妙な感じが素敵です。まあ、AnonymousComparer自体はふつーに使う分にはふつーに便利ですので、Linqのお供にどうぞ。もう一つ、今度は役に立つ例でも。
class MyClass { public int Prop1 { get; set; } public string Prop2 { get; set; } public string Prop3 { get; set; } } static void Main() { var array = new[] { new MyClass{Prop1=30, Prop2="hoge", Prop3="huga"}, new MyClass{Prop1=100, Prop2="foo", Prop3="bar"}, new MyClass{Prop1=100, Prop2="hoge", Prop3="mos"}, new MyClass{Prop1=30, Prop2="hoge", Prop3="mos"} }; // Prop1とProp2が一致するのだけ省きたい!といった複数キー指定は匿名型を作る array.Distinct(mc => new { mc.Prop1, mc.Prop2 }); }
複数キーで比較したい時は、匿名型を作るのが手っ取り早いです。こういった用途に匿名型を使うというテクニックは、Linqの他のところでも結構出てくるので覚えておくと便利です。富豪的?気にしない気にしない。
C#(.NET Framework)の文字列連結について
- C# - 10.01/07
一般に文字列を+=で連結するのは遅いと言われています。事実そのとおりで、多量の連結を+=で行うと死ぬほど時間がかかります。例えば、以下のようなコードで示されることが多いでしょう。
// ベンチマーク用関数(10万回実行) Func<Action, TimeSpan> bench = action => { var sw = Stopwatch.StartNew(); for (int i = 0; i < 100000; i++) { action(); } return sw.Elapsed; }; // StringBuilderの計測(最後のToStringを入れてませんが、あまり変わらないのでスルー) var sb = new StringBuilder(); var sbTime = bench(() => sb.Append("hoge")); // stringの+=での計測 var s = ""; var stringTime = bench(() => s += "hoge"); Console.WriteLine(sbTime); // 0.004sec Console.WriteLine(stringTime); // 14.97sec
0.004secと15secでは話になりません(正確には、StringBuilderでは最後に文字列に変換するToStringを入れるべきですが、それでも1secは超えなくて差は歴然なので省略します)。ならば、文字列を連結する場合は、どのような時でもパフォーマンスのためにStringBuilderを使うべきでしょうか? 答えは違います。
// ILではひとつにまとまる // IL_0001: ldstr "abcde" var s = "a" + "b" + "c" + "d" + "e";
定数の連結はコンパイル時にひとまとめにされるので、StringBuilderを使うのは愚かな選択となります。この辺はILDASMで見ればわかるし、Reflectorでもひとまとめになって展開されているのが確認できます。では定数ではなく動的に値を返すものは?
static string Get() { return DateTime.Now.ToString(); } static void Main(string[] args) { // IL_0031: call string [mscorlib]System.String::Concat(string[]) var s = Get() + Get() + Get() + Get() + Get(); }
ILを見ると、s += Get(); s += Get(); みたいな展開のされかたにはならず、String.Concat(string[])が呼ばれることになります。よって、速度を心配してStringBuilderを使う必要は全くありません。測定してみましょう。(ちなみにs+=Get()だとString.Concat(string,string)が大量に呼ばれることになるのが遅い理由)
// benchとGetは上で使ったのと同じものを流用 var sbTime = bench(() => { var sb = new StringBuilder(); sb.Append(Get()) .Append(Get()) .Append(Get()) .Append(Get()) .Append(Get()); var s = sb.ToString(); }); var stringTime = bench(() => { var s = Get() + Get() + Get() + Get() + Get(); }); Console.WriteLine(sbTime); // 0.65sec Console.WriteLine(stringTime); // 0.64sec
速度はほとんど変わりません。StringBuilderとString.Concatでは処理の中身は結構違いますが、速度変わらないのならどっちでもいいよね。なら、記述しやすいほうを選ぶのが良いでしょう。妄信的にパフォーマンスのためにStringBuilder!とか思っている人は、少し考え直してみてください。そんなの当たり前だろ常識的に考えて、と思っていた時期が私にもありました……。世の中は存外StringBuilder神話に溢れているかもですよ? いやほんと。あとC#なら逐語的リテラル文字列もお忘れなく。
Haskell用IDE 「Leksah」の紹介と導入方法
- Haskell - 10.01/04
本格的にプログラミングを学び始めたのがC# with Visual Studioな私としては、充実した、とまではいかなくてもそれなりに動くIDEがないとシンドい。新しい言語を学ぶときは、まずIDE探しから始めるのだよ、はっはっは。と、全く自慢にならないゆとりっぷりを晒してしまうわけですが、事実辛いものは辛い。そしてHaskell。日本語による書籍も4冊出ていて、学習しやすくなったものの実行環境導入の敷居の高さは変わらず。GHCi(インタプリタ)でコマンド打ち込みながらやれって? いやいや、ムリムリ。
初心者にこそ強力なIDEが必要なのだよー、入力補完や背後でのコンパイルによるエラー報告、色分けにオートインデント、デバッガ。これらが素早いトライアンドエラーを可能にし、学習速度を高める。まずはメモ帳で十分、なんていうのは誤り。学習するなら最初からIDE。ということはneue cc - 最もタメになる「初心者用言語」はVisualStudio(言語?)が、それをHaskellにも持ってこようとしています。大体がしてEmacsってIDEっしょ、もはや。さて、しかしWindowsでEmacsってちょっと……。
Leksah
そこで、Leksahの登場です。HaskellによるHaskellのための開発環境。Leksah(逆から読むと……)はHaskell自身で書かれたHaskell用IDEで、WindowsでもMac OSでも動作します。バージョンは0.6と、まだまだ不安定気味なところも見え隠れしますが(不意に落ちても泣かない)十二分に使えます。インストール・設定も簡単なので、非常にお薦め。Windows用のIDEだと、他にEclipseプラグインのEclipseFPやVisual Studio 2005拡張のVisual Haskellがありますが、試したところどちらもイマイチでした。今のところLeksahしか選択択はないように思います。
Haskell Platform
IDEを入れる前にコンパイラを入れましょう。ということはGHCですね?と思ってしまいますがちょっと違います。Haskell Platformからセットアッププログラムをダウンロードしましょう。オールインワンで全てやってくれます。インストールが終了したら、Leksahのインストールと実行。初回実行時には何やらディレクトリ位置を指定してください的なダイアログが立ち上がりますが、それは無視しても構わなかった、はず、です。
Hello, Worldまで
IDEのお約束として、最初の設定は少し面倒くさいです。が、それさえ乗り越えれば簡単生活が待っているので、ちゃちゃっと設定を済ませましょう。まずメニューからPackage->New Packageを選択して新しいPackageの作成。これはVisualStudioで言うところのソリューションですかね。するとPackageのコンフィグ画面が立ち上がっているので、まずはPackage IdentifierのNameとVersionを適当に記載します。
次にDependenciesを選択して、Selectからbaseを選び、Addボタンを押す。これはVisualStudioで言うところの参照設定です。System.dllを読み込むように、baseを読み込むよう指定したわけです。
次にExecutablesを選択して、Executable NameとFile with main functionにMain.hsと記述してAddボタン。これはVisualStudioで言うところのスタートアッププロジェクトですね。今はまだMain.hsはないので、後で作ります。
最後に1 Buildでファイルを置く予定のディレクトリを指定したら設定は完了。Saveボタンを押してからClose。
次に右ウィンドウModulesタブを開き、ラジオボタンLocalを選択して右クリックからAdd Module。入力欄にMainと入力すればMain.hsが作成されます。コメントが色々書かれたものが読み込まれているので、とりあえず全部削除。一行目にmodule Main where。あとは好きなように書いて、最後にmain = do以下に実行文を書けば出来上がり。
module Main where -- 名前空間みたいなもの double x = x * 2 -- とりあえず関数など作ってみる -- mainは必須。ようはstatic void Mainですな main = do print "Hello World" print $ double 100 -- $でカッコを省く print(double 100)と同じ
Ctrl+Bでビルド。Ctrl+Alt+Rで実行結果が見れます。 あとは、好きなように書き換えて実行、実行、実行。インタプリタで頑張るよりも学習効率良いですよ、きっと。なお、デバッグはデバッグのアイコンを押してデバッグモードに入って、Show Debuggerでデバッガウィンドウを出して、あとは適当に弄る(よくわかってない)。
Leksahの特徴
インパクトがあるのが、エディタ上で文字が記号に置換されること。上の画像は一切手を加えていないエディタのスクリーンショットなのですよ。非常に異国情緒に溢れていて、いいですね。λがλですよ。ホットコーナーの舞台裏でのプログラミングHaskellのレビューでも記号について触れられていますが、本の通りの綺麗な記号でディスプレイに表示し、編集出来ます。
ただし、使用するフォントが制限されます。プログラミング用フォントでは、私はConsolasがお気に入りなのですが、Consolasでは一部の記号が化けてしまうため、今はDejaVu Sans Monoを使っています。ただ、これだと今度は日本語が化けてしまったり。
変換される記号はインストールディレクトリの\data\Default.candyで確認出来ます。スペースも補完入力されるのが気にくわないぜ、と思ったら書き換えてやりましょう。また、Default以外にも、.candyファイルを作成してエディタのコンフィグで読み込むcandyファイルを指定すれば、好きな文字を好きなルールで変換可能です。ショートカットキーも同様にHoge.keymapを作成してエディタで直に編集して、コンフィグで指定します。ちなみに私はRunがCtrl+Alt+Rなのは指が厳しいので、とりあえずF5にしておきました。
なお、もし通常表記したい場合は、Config->To Candyを選択することで簡単にオンオフできます。
何か書くと常にバックグラウンドでコンパイラが動いて、エラーを表示してくれます。静的言語の強み!勿論、ダブルクリックで該当行にジャンプできます。
入力補完も効きます。また、ModulesのPackageを見れば、標準ライブラリ一覧と、その型を見ることが出来ます。素早く関数を知る・試すことができるのは学習速度に影響しますからね。非常に便利。これでDescriptionも表示されれば完璧なのですが、今は型のみ。
本
という感じに、プログラミングHaskellをぽてぽてと読んでいます。Haskell自体が刺激的な言語ということもあって、非常に楽しい。良い本です。章末問題の解答は公式サイトでpdfが配られています。そして、最近ではReactive Extensionsですっかりお馴染みに動画を見ることが多いErik Meijerによる各章解説動画もありますね。おお、なんという致せり尽くせり。素晴らしい。
AssemblyInfoの取得
- C# - 09.12/31
public sealed class AssemblyInfo { public string FileName { get; private set; } public string Version { get; private set; } public string FileVersion { get; private set; } public string Title { get; private set; } public string Description { get; private set; } public string Configuration { get; private set; } public string Company { get; private set; } public string Product { get; private set; } public string Copyright { get; private set; } public string Trademark { get; private set; } public string Culture { get; private set; } public AssemblyInfo(Assembly assembly) { FileName = assembly.GetName().Name; Version = assembly.GetName().Version.ToString(); FileVersion = GetAttributeName<AssemblyFileVersionAttribute>(assembly, a => a.Version); Title = GetAttributeName<AssemblyTitleAttribute>(assembly, a => a.Title); Description = GetAttributeName<AssemblyDescriptionAttribute>(assembly, a => a.Description); Configuration = GetAttributeName<AssemblyConfigurationAttribute>(assembly, a => a.Configuration); Company = GetAttributeName<AssemblyCompanyAttribute>(assembly, a => a.Company); Product = GetAttributeName<AssemblyProductAttribute>(assembly, a => a.Product); Copyright = GetAttributeName<AssemblyCopyrightAttribute>(assembly, a => a.Copyright); Trademark = GetAttributeName<AssemblyTrademarkAttribute>(assembly, a => a.Trademark); Culture = GetAttributeName<AssemblyCultureAttribute>(assembly, a => a.Culture); } private string GetAttributeName<T>(Assembly assembly, Func<T, string> selector) where T : Attribute { var attr = assembly.GetCustomAttributes(typeof(T), true).Cast<T>().FirstOrDefault(); return (attr == null) ? "" : selector(attr); } } // 利用例 class Program { static void Main(string[] args) { var assembly = Assembly.GetEntryAssembly(); var info = new AssemblyInfo(assembly); // こんな感じに。 Console.WriteLine(info.Title); } }
通常AssemblyInfo.csに記載する、タイトルとか説明とかバージョンの取得って、用いたいシーンも少なくないわりに存外面倒くさい。そんなわけで補助クラスを作ってみました。コンストラクタにAssemblyを投げ込むと、文字列にして返してくれます。
これと同じことをやるコードを一年ぐらい前に書いたのですが、今見たらどうしょうもなく酷かった……。(あまりにも酷いので見せられません!)。なので、今基準で書き直してみました。いかに型を書かないで済ませるか、いかに行数を少なく見た目をすっきりさせられるか。Func<T, string>という発想が一年前は出来なかったんだなあ。CastやFirstOrDefaultも知らなかったやも。
そんなわけで今年もありがとうございました。無事、閉鎖せずに一年を乗り越えられました。ただ、ある意味閉鎖してますけれどね、ゲサイト的な意味では。今年を振り返るとプログラミング、プログラミング、プログラミングでした。一年前とは見比べるまでもなく成長出来たと思います。ブログにコードを晒すこと、小さくてもいいのでソフトウェアを作って公開すること、というのが確実に貢献してくれました。よく言われる、コード晒せば他の人の添削が期待できるよ!ってのはそこまで期待できないと思うのですが(勿論、ありがたい指摘も幾つかありました、感謝です)、それよりも他の人が見る、という意識をもってコードに取り組むのが効いた気がします。カッコつけて書こうと、何度も練り直すのが結果的には良かったかな、と。
もう少し振り返れば、なんといってもC#、というかLinq。Linqの魅力に取りつかれて、そのままフルスロットルで加速した一年でした。C#や.NET Frameworkに詳しいか? と言われるとまだまだモニョるのですが、Linq to Objectsなら詳しい、と言えるだけの自信はつきました。これは、linq.jsとしてJavaScriptに移植したのが大きいです。動作が完全に一致するよう何度もチェックしたり、Monoのソース読んだり、リフレクタでSystem.Core.dll読んだりしたので、内部をきちんとイメージ出来るようになったので。
来年は、んー、とりあえずlinq.jsのWSH拡張の早期リリースを目指したいです。linq.jsは、個人的には非常に便利だと思っているのですが、ウェブ用のライブラリとしてアピールするのはどう考えても「無理」。パフォーマンス無視で、リスト処理がこんなに簡単に書けるんです、どうでしょう?ってんじゃあ請求力もないって話です。そもそもウェブ用JavaScriptで多用するDOM操作関連は未実装部分(linq.js Xml Extensions)多いし。とはいえ、せっかく作ったわけなので、真面目に布教させたいと思っています。今のところJavaScriptライブラリの隙間、WindowsScriptHost用やテキストエディタのマクロ用としての応用例を探っている、というか実装中。特に、WSH用に使うと物凄く便利なことが分かったので、とっとと実装を終えて、使ってみて欲しいところです。
実装中というか、途中で放置しちゃってるのがアレですが。ちょろっと記事書いたのが8月。今まで、それから全く進んでおりません。XboxInfoTwitのリリースに追われたり、Rxで遊んだりで放置ルートに入ってしまったのですねえ。やる気はめっちゃあるので、来年はまず一番に、linq.jsのWSH拡張のリリースを目指します。本気で本当に。
プログラミング以外だと、今年で一番影響が大きかったのがTwitterかなあ→neuecc on Twitter。今までチャットやネトゲなど、コミュニケーション系のウェブサービスを全く受けつけなかったコミュ不全の私が、唯一利用出来たサービスだという。一人で書き飛ばしていればよくて、無理に繋がらなくていいのが非常に楽。……。まあ、私はもう少し@飛ばしてもいいと思います。むしろ飛ばせ。これも来年の目標、ですかね。
C#のWebRequestとWebClientでCookie認証をする方法(と、mixiボイスへの投稿)
- C# - 09.12/17
WebからHTMLをダウンロードするにはWebClientが便利です。が、そのまんまだとCookie認証で躓きます。せっかく便利にダウンロード出来るのに、認証を超えられないんじゃ意味が無いよ!というわけかで幾つかのやり方を紹介したいと思います。海外だと沢山情報が出回っているのですが、日本だとWebClientはクッキーがとれないが検索上位に出てくるので、WebClientの利用を諦めて面倒くさいWebRequestを使う羽目になっている人が多いんじゃないかしらん。WebRequestなら@ITの記事、@IT:.NET TIPS クッキーを使ってWebページを取得するには?が引っかかりますからね。
とりあえず、@ITのmixiへの認証を例題に、まずはWebRequestでのやり方を見てみます。
// WebRequestによるCookie認証 // POSTしてCookieContainerに書き込む var data = Encoding.ASCII.GetBytes(string.Join("&", new[] { "next_url=/home.pl", "email=めるあど", "password=ぱすわど" })); var cookieContainer = new CookieContainer(); var req = (HttpWebRequest)WebRequest.Create("https://mixi.jp/login.pl"); req.CookieContainer = cookieContainer; req.Method = "POST"; req.ContentType = "application/x-www-form-urlencoded"; req.ContentLength = data.Length; using (var stream = req.GetRequestStream()) { stream.Write(data, 0, data.Length); } var res = req.GetResponse(); // ここでCookieContainerに書き込まれる // 以下、そのCookieを使えばアクセスし放題 var reqLog = (HttpWebRequest)WebRequest.Create("https://mixi.jp/show_log.pl"); reqLog.CookieContainer = cookieContainer; // CookieContainerセット var resLog = reqLog.GetResponse(); using (var stream = resLog.GetResponseStream()) using (var sr = new StreamReader(stream, Encoding.GetEncoding("euc-jp"))) { Console.WriteLine(sr.ReadToEnd()); // アクセスできてるのを確認 }
CookieContainerを設定すれば、Cookieのサーバーからの取得も送信も全部自動でやってくれる、というのがポイント。そこは楽です。楽なのですが、WebRequest自体が使いづらい。何をやるにも、いちいちStreamがどうだのこうだのなんてウンザリです。ていうか何だこのvarの多さ、変数乱れ打ち! そうなるとついつい、よーしパパ、ラッパー作っちゃうぞー、とか言ってしまいますが、もう見てらんない。.NET FrameworkにはWebClientというMS謹製のラッパーがあるわけなので、それを使いましょう。認証?Cookie?自前で取ればいいんですよ、ヘッダーから。
// WebClientならポストは超簡単! var wc = new WebClient { Encoding = Encoding.GetEncoding("euc-jp") }; wc.UploadValues("https://mixi.jp/login.pl", new NameValueCollection { {"next_url", "/home.pl"}, {"email", "めるあど"}, {"password", "ぱすわど"} }); // じゃあCookieはどうするの?というと、ResponseHeaderから自前で抽出します var setCookie = wc.ResponseHeaders[HttpResponseHeader.SetCookie]; var cookies = Regex.Split(setCookie, "(?<!expires=.{3}),") .Select(s => s.Split(';').First().Split('=')) .Select(xs => new { Name = xs.First(), Value = string.Join("=", xs.Skip(1).ToArray()) }) .Select(a => a.Name + "=" + a.Value) .ToArray(); var cookie = string.Join(";", cookies); // 以降は取得したCookieをHeaderに設定しておけばOk wc.Headers[HttpRequestHeader.Cookie] = cookie; var result = wc.DownloadString("https://mixi.jp/show_log.pl"); Console.WriteLine(result); // アクセスできてるのを確認
そう、WebClientでも、ResponseHeaderからSetCookieは取れるのです。なので、ここからCookieにバラしてやれば、あとはHeaderに設定するだけなので簡単です。一見WebRequest並に行数がかかっているのですが、大変なのはCookie分解部分だけです。分解がちょっと面倒なのは否めませんが……。基本的にカンマ区切りとなっていますが、有効期限の設定されているものが含まれていると「expires=Fri, 16-Dec-2011」のようにカンマが入ってしまい、単純なSplit(’,')では失敗します。なので正規表現の否定戻り読みでexpires=***,の場合は除外しています。あとは、バラしてクッツケテ、を繰り返して生成。そういえばSelect三連打ですが、これはもちろん複数行にすることでSelect一つで済ますこともできます。でも、そこはそれぞれ役割を切って3つに分けるのが、私の美意識、でしょうか。効率を考えれば匿名型なんて作らない方がいいぐらいなのですけどね、効率じゃない良さってのがあるんです。Linqには。
やり方はまだあります。WebClientは本当にただのWebRequestのラッパーで、中では普通にWebRequestを呼んで処理しています。よって、継承してoverrideしてGetWebRequestの辺りを書き換えて、CookieContainerを使うようにすれば非常に簡単です。
class CustomWebClient:WebClient { private CookieContainer cookieContainer = new CookieContainer(); // WebClientはWebRequestのラッパーにすぎないので、 // GetWebRequestのところの動作をちょっと横取りして書き換える protected override WebRequest GetWebRequest(Uri address) { var request = base.GetWebRequest(address); if (request is HttpWebRequest) { (request as HttpWebRequest).CookieContainer = cookieContainer; } return request; } } // WebClientを継承してちょっと書き換えてやれば一番簡単 var cwc = new CustomWebClient { Encoding = Encoding.GetEncoding("euc-jp") }; cwc.UploadValues("https://mixi.jp/login.pl", new NameValueCollection { {"next_url", "/home.pl"}, {"email", "める"}, {"password", "ぱす"} }); var result = cwc.DownloadString("https://mixi.jp/show_log.pl"); Console.WriteLine(result); // アクセスできてるのを確認
私的にはこれがお薦め。どうせWebRequestはそのまんまじゃ使い辛いので、多かれ少なかれラッパー作るでしょう。出来の悪いラッパーを作る/使うぐらいなら、WebClientの気の利かない部分だけ書き換えた方が良い。 ちなみにCookieの他にもWebClientの気の利かないところとしては、自動でリダイレクトするところが辛い、場合がある。普段はリダイレクトでいいんですが、リダイレクトされると困るシチュエーションもあります、たまに。そんな問題も、CookieContainerと同じくGetWebRequestの部分で、request.AllowAutoRedirectを設定すれば回避出来ます。
Web上のものをゴニョゴニョ処理するのに「Rubyなどのスクリプト言語の良さが目立つ。」というのは、ライブラリの問題にすぎない、ってことですな。XML処理には今やLinq to XMLがあるし、HTMLの取得にしてもちょっと工夫するだけで回避できるのでC#だから書きにくい、なんてことは無いと思っています。いやまあMechanize便利やん、とかありますがありますが。しかしC#には最終兵器、WebBrowserがあるので何とでもなる。HTML解析ならHtml Agility Packを使えば、物凄く簡単に出来ます。
最後に、Twitterの自分の投稿最新20件をmixiボイスに投げ込む、というコードを例として出してみます。CustomWebClientクラスは上に乗っけた奴を使っています。
static void Main(string[] args) { var encoding = Encoding.GetEncoding("euc-jp"); // ログイン var cwc = new CustomWebClient { Encoding = encoding }; cwc.UploadValues("https://mixi.jp/login.pl", new NameValueCollection { {"next_url", "/home.pl"}, {"email", "めーる"}, {"password", "ぱすわど"} }); // 投稿に必要なpost_keyをhtmlから取り出す var echo = cwc.DownloadString("https://mixi.jp/recent_echo.pl"); var postKey = Regex.Match(echo, "id=\"post_key\" value=\"(.+?)\"").Groups[1].Value; // 例なので簡易化するため認証無しのTwitterステータスを取得します // HttpUtilityの利用にはSystem.Webの参照設定が別途必要 var id = "自分の(じゃなくてもいいけど)TwitterID"; var texts = XDocument.Load("http://twitter.com/statuses/user_timeline/" + id + ".xml") .Descendants("status") .Select(x => HttpUtility.HtmlDecode(x.Element("text").Value)) .Reverse(); foreach (var text in texts) { // mixiボイスに投稿(UTF-8以外の日本語の投稿はUploadValuesが使えない(泣) cwc.Headers[HttpRequestHeader.ContentType] = "application/x-www-form-urlencoded"; cwc.UploadString("http://mixi.jp/add_echo.pl", string.Join("&", new[] { "body=" + HttpUtility.UrlEncode(text, encoding), "post_key=" + postKey, "redirect=recent_echo" })); } }
差分を記録するようにしたり、@付きを除外したりするようにすれば、そこそこ使えるんじゃないかしらん。利用はご自由にどうぞ。
Linqでコマンドラインオプション解析
- C# - 09.12/13
最近、Linqでの副作用について考えこむことが多くなりました。きっかけはSelectメソッド内で、外部のListに対してAddしたあげくreturn null -> ToArrayとかいうForEach代わりに使うかのような超勘違いしたコードを見せられたことなのですが(自信満々にどうだ!って感じで出されたのでモニョるしかなかったという苦い記憶ががが) と、そんな私の愚痴はどうでもよくて、副作用。基本的には邪悪ですよね。個人的に嫌なのは、せっかくスコープが狭く、ラムダ式だけを見つめれば良い状態になっているのに、副作用が入ると広い範囲を意識しなければならないこと。この変数名はどこからきたの? インスタンスの状態はどうなるの? 考えごとが増えるのは嫌なものです。ミスも増えるでしょう。エラーの温床となってしまいます。
とはいえ、使いどころによっては強力な効果を発揮するのも事実。例えば、以前書いたIEnumerableの文字列連結なんて、カウント用変数を一つ用意するだけで、Aggregateでサクッと書けてしまいます。というわけで、無駄な多用は厳禁だけど、使いどころをちゃんとおさえて書きましょう、というイイコな結論を出しつつ本題というか例題。
コマンドラインオプション解析。シーケンスを前方から解析して、次のキーが現れるまでは以前のキーで分類する。という分かるような分からないようなお話です。コマンドラインオプションだけでなく、たとえば決まった形式のテキストファイルを解析するとかでよくありそうなパターンだと思います。これがXMLなら簡単に解析出来るのに、クッ…… みたいな。
さて、グループ分けとなると、じゃあLinqで出来るよね? GroupByかなんかを使えばいいっしょー。と思い浮かぶわけなのですが、素の状態だと上手くいきません。GroupByを使うためのキーを列挙内部だけで保持することは出来ないからです。じゃあどうするか、というと、そうそう、副作用です副作用。はいはいクロージャクロージャ。というわけで列挙中にサクサクッとキーを書き換えてしまいましょう。
// こんな風に来るコマンドラインオプションを解析しよう var args = new[] { "-i", "input.txt", "-hoge", "-huga", "-o", "output.txt" }; // グループ分けといったらLinqだよね? // ディクショナリに分解したい、dict["-i"]で"input.txt"が取れる、というように // コマンドラインオプションをHashSetに格納する var options = new HashSet<string> { "-i", "-hoge", "-huga", "-o" }; string key = null; var result = args .GroupBy(s => options.Contains(s) ? key = s : key) // 副作用! .ToDictionary(g => g.Key, g => g.Skip(1).FirstOrDefault()); // 1番目はキーなのでSkip
とまあ、こうなります。副作用便利!
そういえばコマンドラインオプションの解析は.NET Framework標準では用意されていないのですよねえ。外部ライブラリのものは、当然なのですがあらゆるものに対処するため、どれもこれもヘヴィーすぎです。別にそんな複雑なのいらないよー、-oを解析出来ればそれだけでいいんだよー、的な小さいシチュエーションなら、この程度でも問題ない、はずです、きっと。
例としてコマンドラインオプションの解析を出したのは、id:coma2nさんのNDesk.Options(Mono) - コマンドラインパーサー - Programmable Lifeという記事を見てのことです。このNDesk.Optionsは凄いですね! まだ触ってないので実際の使いかっては分かりませんが、ラムダ式の使い方に驚きました。非常に上手いやり方だと思います。シンプルで。明快で。覚えやすく書きやすく。私もこういう発想が出来るようになりたいなあ。
書評 : More Effective C#
- C# - 09.12/06
結論は「Linq to Objectsの本」です。全編に渡って例題がLinqの再実装となっていて驚きました。「作って学ぶLinq」のほうが題として正しいぐらい。冗談じゃなく本当に、7割ぐらいが実質Linq周りです。実質、と言ったのは本書中では特に明言されていないからですが、見ればすぐにこれLinq to Objects……と突っ込みたくなること請け合いの例が沢山収録されています。
以前からLinq to Objectsに絞った解説書が出るべきだ、と思っていました。Linq to Objectsはこれでいて結構深いのです。どうもLinqというとLinq to SQLとか、データベース周りの喧伝の印象が強いようで、Linq to Objectsの実態が正しく伝わっていない気がします。今時リスト処理に高階関数使うなんてどのLightweight Languageでも常識よねー、というお話でもあります。Rubyのメソッドチェイン+ブロックなんて見た目だけで言えばLinqと丸っきり一緒ですし。昨今のモダンな言語の最も優れた部分を、最も優れた形で掲示しているのがLinqです。(優れた形、というのに異論はあると思いますが突っこまんで下さい)
そんなわけで褒め称えたいところだし、内容は結構良いと思っているのですが難点が一つ。対象範囲がC#3.0までのわりに、書き方が微妙に2.0っぽいこと。これはよろしくない。Linqに関しても再実装であることが本書中に明言がなく、書き方が2.0なので、「2.0でLinqをやるには」になっています。別に原理を知れればいいわけで、何も本書中の書き方を真似る必要はないのですが、それだと人に薦めづらいのですね。Linq知らない人に、これ見て学ぶといいよ、と素直に手渡したいのだけど手渡しにくい感が悔しいです。変にC#2.0と3.0を行ったり来たりするようなフワフワした構成じゃなく、Linqであることを明言した上で、その解説に徹してくれればよかったなあ、なんて思うんですね。
本の意義というか効用は、Linqや高階関数を多用してしまっても、この本が免罪符になるというのが一番大きいですね! C#3.0というのはLinqを使いこなし、更にはLinq風に設計構築していくのがEffectiveなのです、と大手を振って言える、かも。でもまあ、実際Linq風に扱うのが基盤になっているのは確かなので、変に凝るよりはLinq to Xxxみたいになっているほうが嬉しいです<ライブラリのような根幹部分での設計
Moreが先に出ていて、無印の発刊はこれから先です。無印がMoreの後に出るのは、本国では無印はC# 4.0対応の第二版が出るのでそれを待つのかなー、と思ったのですがそういうわけでもないようで。というわけで恐らくC#1.0まで対応のものだと思うので残念のような、そうでもないような。私はC#3.0から入ったにわかC#使いなので、1.0の書き方を見れるというのも新鮮で面白いんじゃないかなー、なんて思ってます。
Reactive Extensions for .NET (Rx) メソッド探訪第六回:exception handling
- C# Rx Framework - 09.11/29
.NET Reactive FrameworkからReactive Extensions for .NET (Rx)に名称が変わったようなので、タイトルも変更。長いね。というわけで久しぶりなのですが、今回はざっとexception handling operators、つまり「Catch, Finally, Retry, OnErrorResumeNext」を見てみることにします。それとRun(ForEachなので説明不要ですが)。Rxって何?という人はHello, Reactive Extensionsをまず参照下さい。
Rxの花形はイベント合流系のメソッドにあると思うので、ひたすら脇役ばかりを紹介してちっとも本流に入ろうとしないのはどうかと思うのですけど、EnumerableExのCatchを見て、あー、こりゃ便利だ、ヤバい、便利だ、用途すぐ浮かんでしまった、というわけでしてCatchを紹介します。まずは、その浮かんだ例であるTwitterのタイムライン取得をどうぞ。例はIEnumerableに対してのものですが、IObservableに対してのものも同じです。
class Twitter { public string Text { get; set; } public DateTime CreatedAt { get; set; } } static IEnumerable<Twitter> EnumerateUserTimeline(string userName) { // {0}はユーザー名、{1}はページ番号 公開ユーザーのものを取得なら認証不要 var format = "http://twitter.com/statuses/user_timeline/{0}.xml?page={1}"; foreach (var page in Enumerable.Range(1, 1000)) { var query = XDocument.Load(string.Format(format, userName, page)) .Descendants("status") .Select(e => new Twitter { Text = e.Element("text").Value, CreatedAt = DateTime.ParseExact(e.Element("created_at").Value, "ddd MMM dd HH:mm:ss zzzz yyyy", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal) }); foreach (var item in query) yield return item; } } static void Main(string[] args) { // 2009/11/23から今日までの投稿を古い順に並べるというもの var test = EnumerateUserTimeline("neuecc") .TakeWhile(t => t.CreatedAt >= new DateTime(2009, 11, 23)) .OrderBy(t => t.CreatedAt) .ToArray(); // これで基本的には問題ないわけですが、TwitterにはAPI制限があるので // ちゃんと全部取得出来るわけではなく、API制限発動 => 死亡になる可能性がある // 死んでもいいんだけど、せっかく取った死ぬ前のデータはがめておきたいよねえ // というわけで、そこで出番なのがRxのCatch! var test2 = EnumerateUserTimeline("neuecc") .TakeWhile(t => t.CreatedAt >= new DateTime(2009, 11, 23)) .Catch((Exception e) => Enumerable.Empty<Twitter>()) .OrderBy(t => t.CreatedAt) .ToArray(); // 例外が発生したら握りつぶして、代わりにEnumerable.Emptyを返します // なので、例外発生前のデータは全て取得出来ています、素晴らしい! }
といった感じです。つまりCatchは、そのまんまCatchです。Linqで全部書くのも良いんだけど、例外処理が出来なくてなあ、という不満がこれで解消されます。残りのFinally, Retry, OnErrorResumeNextですが、全部Catchの派生みたいなものです。とりあえず簡単な例を。
static IEnumerable<int> Iterate1To5() { yield return 1; yield return 2; throw new DivideByZeroException(); // 嘘例外でも投げておく yield return 4; yield return 5; } static void Main(string[] args) { // 1,2 Iterate1To5().Catch((Exception e) => Enumerable.Empty<int>()).Run(Console.WriteLine); // 1,2,100,200 Iterate1To5().Catch((Exception e) => new[] { 100, 200 }).Run(Console.WriteLine); // 1,2 -> 例外発生(ArgumentNullExceptionはDivideByZeroExceptionじゃないのでCatchしない) Iterate1To5().Catch((ArgumentNullException e) => new[] { 100, 200 }).Run(Console.WriteLine); // 1,2,100,200。つまりCatchの簡略版 Iterate1To5().OnErrorResumeNext(new[] { 100, 200 }).Run(Console.WriteLine); // 1,2,Finally。これでtry-catch-finallyが出来あがる Iterate1To5() .Catch((Exception e) => Enumerable.Empty<int>()) .Finally(() => Console.WriteLine("Finally")) .Run(Console.WriteLine); // 1,2 -> 1,2 -> 例外発生。例外を検知したら最初から列挙し直しての再試行 // EnumerableExのRetryはバグっぽくてObservableとは違う動きをする // 明らかにオカシイのでそのうち修正されるでしょう Iterate1To5().ToObservable().Retry(2).Subscribe(Console.WriteLine); }
最後に、中身をちゃんと知るには自分で実装するに限る、ということでIEnumerableでの拡張メソッドで再現してみました。Catchは本当に便利なので、わざわざRx使うのも、と思う場合は以下のコードを是非コピペして使ってくださいな。
// ループをぶん回すだけ、というもの(linq.jsではForce()が同様の働き) public static void Run<TSource>(this IEnumerable<TSource> source) { source.Run(_ => { }); } // ようするにForEach public static void Run<TSource>(this IEnumerable<TSource> source, Action<TSource> action) { foreach (var item in source) action(item); } // try-catch句の中でyield returnが使えないので回りっくどいことに public static IEnumerable<TSource> Catch<TSource, TException>(this IEnumerable<TSource> source, Func<TException, IEnumerable<TSource>> handler) where TException : Exception { using (var enumerator = source.GetEnumerator()) { while (true) { TException exception = null; var hasNext = false; try { hasNext = enumerator.MoveNext(); } catch (Exception e) { exception = e as TException; if (exception == null) throw; } if (exception != null) { foreach (var item in handler(exception)) yield return item; } if (hasNext) yield return enumerator.Current; else yield break; } } } // Rxにはこういう、handlerがActionのオーバーロードが欲しいです // わざわざ空のシーケンス投げるのは面倒くさいし、匿名型に対応できないじゃないか! public static IEnumerable<TSource> Catch<TSource, TException>(this IEnumerable<TSource> source, Action<TException> handler) where TException : Exception { return source.Catch((TException e) => { handler(e); return Enumerable.Empty<TSource>(); }); } // OnErrorResumeNextはCatchの簡略版みたいなもんですね、別に必要ないような public static IEnumerable<TSource> OnErrorResumeNext<TSource>(this IEnumerable<TSource> source, IEnumerable<TSource> next) { return source.Catch((Exception e) => next); } // ToList().ForEach()とRun()ではactionの出るタイミングが変わることに注意 public static IEnumerable<TSource> Finally<TSource>(this IEnumerable<TSource> source, Action action) { try { foreach (var item in source) yield return item; } finally { action(); } } // 本当は無限でやるべきなんでしょうが、int.MaxValueで。 public static IEnumerable<TSource> Retry<TSource>(this IEnumerable<TSource> source) { return source.Retry(int.MaxValue); } // EnumerableExのRetryがバグ臭いのでObservable.Retryの挙動を採用しました public static IEnumerable<TSource> Retry<TSource>(this IEnumerable<TSource> source, int retryCount) { var count = 0; Exception exception = null; while (count < retryCount) { exception = null; foreach (var item in source.Catch((Exception e) => exception = e)) { yield return item; } if (exception == null) yield break; count++; } throw exception; }
どれもCatchの派生のようなものです、CatchイイよCatch。これは使いまくりたくなる。それにしてもtry-catchの中でyield returnが使えないのを、はじめて知りました。こんなことやろうとしたことがなかったので。あと、EnumerableEx.Retryはひっじょーにバグ臭いです。ちなみにEnumerableEx.Mergeもバグ臭い。全体的にEnumerableExはバグ臭さ全開です。明らかに(Observableから)適当に移植した感漂ってます。ヤバい。
TwitterのTL過去ログをHTMLにするツール
- C# TwitterToHTML - 09.11/27
Twitterの他の人のポストは全部読みたいと思っています。数千もフォローしてるアルファーツイッタッターでは無理でしょうけど、せいぜい百ちょいぐらいなら全然いけるわけです。と、思っていたのですが、たかだか200を超えたところで、ん、無理……?と思える感じになってきてしまいました。ツール的限界で。Webから過去ログを見ようとすると、限界点に到達してしまって未読があるのに過去ログが見れない状態になってしまって。ていうか、そもそもWebでログを見るというのはダルい。まあ、ないですよね。私がTwitterで使っているツールはEchofonで、これは過去ログ見るのに適さないし全然昔の見れないし、というわけでどうしたものかなー、と思っていたんですが、作ればいいわけですよね、過去ログ閲覧専用Twitterクライアント。
と、考えてはみたものの、そもそもわざわざツール作るまでもなく、ログをHTMLで吐けばいいんじゃね?と気付いた。YesYesYes。流し読みなら、むしろへっぽこ専用ツールよりもブラウザのほうが見やすいし。家でガッとHTML取得しといてモバイルに転送して電車でゆったり見る、とか出来るし。というわけで、可能な限り過去ログを掘ってHTMLに吐きだすプログラムを書きました。可能な限り、といってもAPI制限の都合上で最大800件まで、のようです。うーん、これじゃあ半日ぐらい前、程度ですよねえ。18-24時とかだと一瞬で吹っ飛びそうかも。3000件ぐらいまでは欲しいとこなのですが……。なお、API消費はたった4か5なので安心です。一回につき200件取れるので。
デザインはCSSで行えます。例えばimgのwidthとheightを0pxにすればアイコン表示を消せます。これで学校や会社で見る時にアニメアイコンが並んで恥ずかしい思いをしなくて済む! あとまあ、デフォルトのCSSはショボい(私がCSSの知識ないので……。float良く分からん、高さ揃わない、50pxで決め打ち!とか)ので、適当に改良して使ってください。
あと、コード(C# 3.0)も同梱してあるので適当に見て突っ込んでくださいな。HTML組み立て部分はLINQ to XMLです。
var urlPattern = new Regex("(s?https?://[-_.!~*'()a-zA-Z0-9;/?:@&=+$,%#]+)"); var xhtml = new XElement("html", new XElement("head", new XElement("link", new XAttribute("rel", "stylesheet"), new XAttribute("href", "style.css"))), new XElement("body", new XElement("ul", EnumerateHomeTimeline(username, password).Select((t, i) => new XElement("li", new XAttribute("class", (i % 2 == 0 ? "even" : "odd")), new XElement("div",new XAttribute("class","name"), t.ScreenName), new XElement("div",new XAttribute("class","date"),t.CreatedAt.ToString("G")), new XElement("div", new XAttribute("class","image"), new XElement("img",new XAttribute("src", t.ProfileImageUrl))), new XElement("div", new XAttribute("class","text"), urlPattern.Split(t.Text).Select(s=> { var href = urlPattern.Match(s); return (!href.Success) ? (XNode)new XText(s) : new XElement("a",new XAttribute("href",href.Value),href.Value); })))))));
えーと…… 汚い、ですね!それでも、このLINQ to XMLの関数型構築がなければどれだけ悲惨なことになっていたか!やはりLINQ to XMLは素晴らしい。さて、しかし困ったのがリンクのaタグ付け。文字列で扱っていれば普通に置換すれば済む話なのですが、XTextにそれを放り込むとタグはエスケープされます。最初驚いたのですが、考えてみると当然ですね、XMLとして不正なものは許されないので。しょうがないのでSplitしてXMLとして組み立ててやりました。
json/xmlを拾ってきてHTMLに整形するだけなのだから、JavaScriptで書いてうぇぶあぷり、的なものにしたほうが利便性とか何とかかんとかが良好なんじゃございませんこと?とか思わなくもなかったのですが、C#、楽なので、ほんと。良い言語なんですって。
Hello, Reactive Extensions
- C# Rx Framework - 09.11/24
Reactive FrameworkがReactive Extensions for .NET (Rx)として、DevLabsで公開されました。紫のうなぎアイコンが可愛い。これは(消滅してしまった)Microsoft Voltaと同様のものなのですが、開発チームが同じだからだそうです。DevLabsには他にAxumやSTM.NETなど、興味深いプロジェクトがいっぱいありますが、日本語による情報がほとんど手に入らないので手を出しづらいところがあります。RxもDevLabsに登場したことでグッと情報が増えましたが、英語ソースによるものばかりなので、私の脳みそ的には相当シンドイことになっています。英語辛いよぅ。
とはいえ、小細工しなくても.NET3.5 SP1上で動かせるのは素敵なので是非試しましょう! Silverlight Toolkitにこっそり収録版からも、かなりパワーアップしています。メソッド大増量、そしてToolkit版でバグい挙動していたのが本当にバグなのか私のやり方が悪いのか悩んでいた部分がサクッと修正されいてホッとしたり。
インストールディレクトリに置いてあるchmのヘルプを見ることも出来るし、IntelliSenseも動きますので、前に比べると触りやすくなりました。ただまあ、例ぐらいは入れてよって感じで、簡素極まりない一行説明から理解するのは、やっぱり難しい。
System.Interactive.dll
Rxには興味ないよ、という人にも大変役立ちなのがこのdll。デフォで参照設定に加えることは確定的に明らか。中のクラスはSystem.Linq.EnumerableExのみで、ようするに拡張メソッド集です。EnumerableExという名の通り、Linq.Enumerableに対する追加版となっています。基本的にはIObservableに用意されていたメソッドをIEnumerable用に持ってきたという感じです。Repeat(value)による無限リピートやReturn(value)による単体シーケンス作成、Generate(所謂Unfold)など、欲しかった生成メソッドが沢山用意されています。
更に当然のようにIEnumerableに対する拡張メソッドもたっぷり。Zip(.NET 4に搭載)やMemoize、それにDo(副作用専用のActionメソッド)、Run(ようはForEachです、そう、ForEachですよ!)が非常にうれしい。他、挙げればキリがない用途不明の拡張メソッドがテンコ盛りなので要研究ですね。
例えばフィボナッチ数列。
EnumerableEx.Generate( new { v1 = 0, v2 = 1 }, // initialState _ => true, // condition a => a.v1, // resultSelector a => new { v1 = a.v2, v2 = a.v1 + a.v2 } // iterate ) .Take(30) .Run(i => Console.WriteLine(i)); // conditionが無限ならこうも書ける(第二引数の戻り値がIEnumerable、だけど平たくされる?) EnumerableEx.Generate( new { v1 = 0, v2 = 1 }, // initial a => EnumerableEx.Return(a.v1), // resultSelector a => new { v1 = a.v2, v2 = a.v1 + a.v2 }); // iterate
んね、素敵。
System.CoreEx.dll/System.Threading.dll
CoreEx.dllにはAction, FuncのT16まで版(.NET 4に搭載)やUnit(voidを表す型)が主なところ。他にIEventやNotificationがありますが、これらは主にRxで使うためのものですね。Threading.dllはLazy, Task, Parallelといった、.NET 4に搭載されるメソッドの先取りといった感じのものががが。真剣に追っかけると大変なのでスルー。
System.Reactive.dll
以前のに比べるとメソッドが増えているのは当然なのですが、目立つところではSystem.Joins.Patternクラスの追加が目新しいです。何に使うのかはまだ知りません。ふむ。自分でお題を探すのも大変なので、Forumで出てる内容を幾つか紹介します。
// 100,1,2,3,....,10 Observable.StartWith(Observable.Range(1, 10), 100); // 順番が奇妙なのは拡張メソッドとして使えるようにしたため Observable.Range(1, 10).StartWith(100);
ConsはStartWithに改名されました。ついでに順番がちょっと変わりました。先頭に付け足すのに、第二引数というのが違和感全開なのは否めない。これは、拡張メソッドとして利用できるようにしたためでしょうね。メソッドチェインを崩さずに、先頭に値を足すことが出来るようになりました。ObservableだけではなくEnumerableにもあります。
public static IObservable<IEvent<MouseEventArgs>> GetMouseDown(this Control control) { return Observable.FromEvent<MouseButtonEventHandler, MouseEventArgs>( h => (sender, e) => h(sender, e), h => control.MouseDown += h, h => control.MouseDown -= h); }
h => (sender, e) => h(sender, e)っていうのが混乱しますな。第一引数のhはEventHandler<MouseEventArgs>です。ここでhをMouseButtonEventHandlerに変換します。この辺も、ActionやFuncと同じく、EventHandler<TEventArgs>だけあればいいのに、その他のゴチャゴチャしたデリゲートは消滅してしまえばいいのに、とか思わなくもないのですがしょうがない。「sender,e => h(sender,e)」は引数がobject,MouseEventArgsで戻り値がvoidのよくあるイベント用のデリゲートです。素の状態でこのラムダ式を書くと型が決まらないので動作しませんが、FromEventの型宣言時にMouseButtonEventHandlerだと明示しているので、変換出来ます。ここで変換されるので、第二、第三引数のhはMouseButtonEventHandlerになります。
メソッド探訪の第一回で警告が出る、とか書いてしまったのですが、こういう風に記述すれば警告も出ず、文字列メソッド名を使わずに利用できたようです。言われてみればなるほど、って感じなのですが、気付けなかったなあ……。
ambはLISPのambを由来として、ambiguous(不明瞭)の略。だそうです。Rxでは、例えば……
var first = Observable.Range(1, 3).Delay(300); var second = Observable.Range(4, 3).Delay(100); var third = Observable.Range(7, 3).Delay(200); Observable.Amb(first, second, third).Subscribe(s => Console.WriteLine(s)); Console.ReadLine();
Delayは発火を指定ミリ秒だけ遅らせるメソッドです。では、何が表示されるでしょうか。答えは、”4,5,6″です。んじゃあthirdをDelay(0)にしたら? “7,8,9″が表示されます。なるほど、分かってきた。つまり最初に到達したものを採用する、というわけです。この例ではわざとらしくDelayを足したシーケンスを投げてみましたが、例えば幾つかのイベントを並べて、最初にイベントが発火したものを。みたいな用途が考えられなくもない。
EnumerableEx.Amb( new[] { 1, 2, 3 } .Select(i => i + i) .Select(i => i + i), new[] { 7, 8, 9 } .Select(i => i + i)) .Run(i => Console.WriteLine(i));
IObservableは分かるとして、何故かIEnumerableにもAmbがあります。上の例は何が表示されるでしょうか?答えは、8割は14,16,18です。残り2割は4,8,12です。チェインを沢山繋いだ方が原則的には「時間がかかる」ため、チェイン数の少ない方が採用される場合が多い。ただし、内部ではThreadを立てているので、必ずしもそうなるわけじゃない。というわけで、結果は非常に不確定で不明瞭で、使い道は完全に謎。