はてなダイアリー to HTML

はてなダイアリーの記事を根こそぎ取得してローカル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。

XboxInfoTwit - ver.2.2.0.0

今回の更新は、HTMLをXMLに変換するライブラリをTidy.NetからSGMLReaderに変更しました。数日前にSGMLReaderでLinq to Html最高なんてエントリーを上げていたので、早速実戦投入というわけです。内部コードが割と変わったため、ver2.1系列から2.2へとアップ。利用者的にはぶっちゃけどうでもいい話です。すみません。

ユーザーに関係ある変更点は、先日発売されたばかりのBioShock2で実績が取得出来てなかったので、それを直しました。私はBioShock2でしか確認していないのですが、「カルドセプト」や「のーふぇいと!」も実績が取得出来ないという報告が上がっていたので、今回の修正によって取得出来るようになった、かもしれません。分かりません。カルドセプトやのーふぇいと!を持っている方は実績取れたよー、と教えていただけると助かります。一応Twitter検索で追っかけてはいるんですけど、最近投稿量が多くて(認証者数は1400行きました、ありがとうございます)全然目を通せていなかったりして。

追記:「カルドセプト」、「のーふぇいと!」ともに実績取得出来ているようです。確認していただいた方、ありがとうございました。

C#でスクレイピング:HTMLパース(Linq to Html)のためのSGMLReader利用法

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 - ArrayListでしょう。とはいっても、JavaのArrayListはほとんどList(Javaのほうの)と同じようなものですが、そうなってくるとむしろメソッドがなくて不便!って思ってしまうのはC#脳。

そして何故かスレッドの話は迷走していて不思議。パフォーマンス? んー……。勝手な印象論ですみませんが、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を実装していないからです。インデクサで気楽にアクセスすることは出来ません。これは、大変素晴らしい決断だと思います。軽い操作だと勘違いさせるような重たい処理は最初から用意しない。(なお、どうしてもindexで取得したい場合はLinqのElementAtが使えます)。JavaのLinkedListと比較してC#のは貧弱すぎて困るぜ!とか思っていたならば、それは大間違いで、むしろJavaの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).Add で簡単にNotSupportedを吐きます。他にもReadOnlyにしたコレクションにインデクサで値をセットしようとすれば、これまた同様に例外。コレクションフレームワークにおいてインターフェイスの完全保証を実現しようとするのは大変難しい。とは、私じゃなくて Java コレクション API の設計に関する FAQでSunが妥協した、と言うぐらいなので、無理なのでしょう。

まとまってませんが、結論としては疑問に思ったらソース読むのが手っ取り早い、とかそんなところで。Javaの良いところはC#に比べてフレームワークのコードへのアクセスが簡単なところですね。C#は部分的にはコードは公開されていて大変タメになるのですが、色々と面倒くさいし、公開されてない範囲も少なくないし……。.NET Reflectorのお世話になりまくってイリーガルな気分を味わうのはもう嫌ぽ。嘘。リフレクタ大好きですがそれはそれ。

Comment (2)

hinto : (02/26 07:44)

ホント Java はクソだな

neuecc : (02/26 19:14)

端的に言えばそうなりますね(笑)
Collections.sortとかちっとも良いようには思えないし
(そもそも何でもかんでもutilに詰めこんでるのが最悪)
ListIteratorなんてソートのためだけに無理やり導入したんじゃないか
と突っ込みたくなる不自然さ。
どうも、そこかしこでピントを外してる感じがあって良くないです。

Trackback(0)
Write Comment

Re:Scheduled

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も入れた。良さそうなのは何でもいれますよー。というわけでもないのですけどね、ちょっと便利、程度だと躊躇います。入力補完に大量にメソッドが出るってのはあんまり嬉しいものでもないですから、なるべく厳選して。

デスクトップヒストリー

以前に応募した、Windows 7杯 自作PCの祭典 2009ですが、部門賞「勝手にオレが一番!部門」で無事受賞することが出来ました。部門賞の5台の中から、一般投票でグランプリを決めるので、是非とも私に投票してやってくださいー。あと2/13に秋葉原のリナカフェで当日一般投票&結果発表があるそうなので、そちらも是非どうぞ。あ、私の応募記事ですが→neue cc - デスクトップシアター for Windows 7杯になります。発表会場ではパネルに写真を飾るそうなので、もう少し良い写真を撮っておけば良かったなあ。昔は壁紙を揃えたりカッコつけた照明にしたりしてたんですが、今回は応募締め切りギリギリで必死に写真撮って記事書いて、ってやってたので余裕がありませんでした。若干後悔。

私的には、定格パフォーマンス部門の人のLevel 10が自作PC本体の魅力としてはカッコイイ!ですね。写真の構図が憎い。 あと、バカPC部門の冷蔵庫PCはナイスですな。リザーブタンク=ペットボトルは最高のアイディアだと思います。それにしても、かなり大掛かりに加工してあって、凄いなあ。まさに自作PC、という凄さでは一番ですね。他は、んー、特別賞のマルチタッチ賞が該当なしなのはしょうがないかな。Windows7杯なので、Win7の追加機能であるマルチタッチを!というのは分からないでもないけれど、自作PCでマルチタッチを生かすっていうのは難しいよ。一体何をどうすればいいんだ、という。

写真で振り返る机上写真の歴史

写真が残ってなかったりするのがあるのが悔しいなあ。とか思いつつも、残ってるものを探してきました。ローカルHDDに見つからなくても、Web上に残ってたりして助かりました。やっててよかったWebサイト。といっても、やはり残ってないのも多いのね。残念。

2002年です。VAIOなので自作PCじゃないよ! これは何と言うか、懐かしいね。非常に懐かしい。CRTモニタはともかく、MDデッキとかMIDI音源(YAMAHAのMU2000)とかポータブルCDプレイヤーとか、今じゃあすっかり廃れたものが映っていて泣ける。マウスはIntelliMouse Explorerの、たぶん初代かな。

飛んで2006年です。4年も飛んじゃうのが痛い、こうなるまでにも過程があったんですが……。2002年の写真を見て思いっきり懐かしめたので、もっと残ってると良かったのだけど。既にXbox360だものねえ(画面のものはPGR3)。時代飛びすぎ。この頃はCRT信者でモニタを22インチCRETにしてトランスコーダーで写していましたね。液晶なんて残像!残像!あんなもんクソだ使えねー。CRT解像度最強だし。とか言ってた気がしますが、なかったことにしましょう。ちなみに2chスピーカーはこの頃のほうが良いの使ってました。PIEGAのアルミニュウム筐体のスピーカーでして、音もデザインもとても気に入ってました。サラウンド環境は7.1ch。マウスはMX1000かな、これは。多ボタンマウス信者の始まりである。

2007年の8月。いつのまにやら全モニターが液晶になってる。やっぱ液晶だとスタイリッシュな感じになりますなー。大きさは全部WUXGA(1920×1200)。サラウンドは、センタースピーカーなしの6.1chという若干変則的な形を取っていました。デスクトップシアターだとセンタースピーカーの置き場に困るんですよねー。この問題を解決させられなくてずーっと悩んでいました。ま、この頃はセンタースピーカーは左右スピーカーの中央で聞けるのならなくても問題ない。一人で椅子に座って聞くデスクトップシアターではファントム再生(左右のスピーカーでセンタースピーカーの音を仮想的に作る)でも問題ない。むしろ、そのほうが音の統一感が出ていい。とか言っていましたが、なかったことにしましょう。マウスは恐らくMX-R。キーボードはHappyHackingKeyboardですなー。デザインは最高に好き。でも、今は日本語配列のRealForceになりました。

2007年の11月。これはデスクトップシアターの薦めという記事で書いた時のもの。スピーカーを小型スピーカーに変更したことで、9.1ch環境になりました。スペース的にも大型スピーカーを廃したことで余裕が出て正解だったと思っています。センタースピーカーも、(モニタ土台のほうの)デスク下に配置することで解決。PIEGAのスピーカーとの決別と、それによる2ch再生力の低下に悩んだんですが、それは今ではSTAXで聴くからいいもん、という方向で解決(?)しました。

2008年の11月。こりゃまたえらく簡素になったねー、というわけですが、これは引越しして一人暮らしを始めた時の写真です。モニタとかデュアルでいいっすよ、とか思って色々と整理したはずなんですが、やっぱりデュアルでは不満になったので結局、買い揃えていくことになったという。なんだかなー。ともかく、新生活のフレッシュさを感じたり感じなかったりして、ちょっと思い出深い。涙ちょちょぎれます。で、まあ、私は家にいる時は、このデスク左にちょっと映ってるベッドで寝てるか、椅子に座ってるかの二択しかありません。NEETになりたいなあ。職場なんて行きたくないでござる。

2009年の2月。これはneue cc - 解像度6000オーバーという記事で書いたもので、メインモニタがついに30インチになりました記念。メインモニタとあわせて左右のモニタも新調しました。ちなみに、せっかくの縦回転ですが、今の環境だと縦が塞がっているので出来ないんですよねえ……。トレードオフといえばそうなのですけど、この頃がちょっと懐かしい。

2010年の1月。これがneue cc - デスクトップシアター for Windows 7杯の写真で、今の環境です。9.1chなのですが、AVアンプを変えたので全方位を横一列に取り囲んでの9.1chじゃなくて、フロント上部に2台設置しての9.1chという環境に変わりました。このハイトスピーカーの置き方は最近出たドルビープロロジックIIzという規格によるもので、徐々に増えて行くんじゃないかと思っています。まあ、あとは再びクアドラプルモディスプレイ環境に戻ったり、ノートPCがあるから5画面だよ、といった感じであったり、今までの集大成的なものになっています。

といったわけで、駆け足で振り返ってみました。やっぱ2002-2006の間の写真がないのが痛い。絶対後悔するので、写真はちゃんと残しておきましょう、まる。ブログにアップしておけば、サルベージも容易でいいね!ていうか、実際2002年の写真以外はローカルに残ってなくて全部ウェブから引っ張ってきたのですが……。やっててよかったWebサイト。あと、写真がちっとも上達しないのが酷い。むむむ。

AnonymousComparer - ver.1.2.0.0

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
}

といった感じです。分かったような分からないような?

ver.2.1.0.1

Jewel Questで「未知のエラー」が発生する件を修正しました。言い訳がましいですが、これXbox.comのバグですよ! Netflixの時もなんじゃこりゃ、と思ったんですが、今回は”Insert translated text here”です。明らかにオイオイオイオイしっかりやれよ、って感じにアレなメッセージが浮かび上がってます。

こんなのがステータス画面のソースを開くと確認出来ます。いやまあ、だから何だって話ではあるのですけど。イレギュラーなことやってるのはコッチですからね……。

さて、ところで今回の不具合は1月上旬に報告を貰ったのに対処したのが1月ギリギリってどういうことよ、すみません本当にゴメンナサイ。不具合情報の報告は大変ありがたいのですけど、ちゃんとそのありがたさに応えなきゃダメですね、私。特に今回は確認も修正も全く難しくないところなので、しっかりしろよ、というお話でして。今後はしっかり対応していきます。

ただ、既知の不具合である、一部の人がログイン段階でコケるという件は全く手付かずです。いやー、自分のとこに環境ないとさっぱり分からん。あ、あとカルドセプトでステータスが反映されない件も放置中です、すみません。気が向いたら、というかソフト入手したらそのうち……。

Comment (6)

イシ : (02/16 19:13)

あれ?

neuecc : (02/16 19:16)

2.0.0.0のほうに投稿してありましたねw

カトー : (03/02 22:39)

初めまして。
2.1.0.1をDLさせていただいて、起動しようとしたのですがエラーで出来ず
(MSに報告しますか?的なログが出るアレでした)
では.NET Framework 3.5 SP1か!とDLしたのですが、
インストールの際、「.NET Framework 3.5 SP1を元の状態に修復しています」と出ます。
このまま実行してしまってもよろしいのでしょうか?不安になって書き込みしてしまいました

neuecc : (03/02 23:45)

それは.NET Framework 3.5 SP1ですねー。
うーん、そういう状況になったことがないので分からないのですが
修復しても削除してもOSが壊れることはないので
(Microsoftの仕事は一応信頼出来ると思いますよ)
修復してダメなら削除して入れ直しでもいいでしょうし、ともあれ修復で実行して大丈夫だと思いますよー。

カトー : (03/03 01:47)

やってみました。
無事に起動しております。使わせていただきますね。

neuecc : (03/03 23:51)

おお、よかった。

Trackback(0)
Write Comment

AnonymousComparer - ver.1.1.0.0

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の他のところでも結構出てくるので覚えておくと便利です。富豪的?気にしない気にしない。

日常雑話

扁桃炎で水曜日にダウンしてから(扁桃炎って40度も熱出るんだねー、インフルエンザかと思っちゃったよ)本当にダウンしっぱなしで、ここ数日は寝て薬飲んで寝るだけの最低最悪でした。今も全然治ってないので最悪です。そんな時でも人は働かなきゃ行けないんですね、社畜! NEETになりたいよう。さて、そんなわけなのですが、そろそろやることがいっぱいつまってきてしまっているので整理します。だらだら生きてるとだらだらネット見てTwitterやって過ごすだけになってしまうのです。それは私です。

  • XboxInfoTwitでJewel Questをプレイ中の人がいると「未知のエラー」で死ぬ件を修正する

これは原因まで掴めているし、そもそもこの件については、わざわざ教えてもらったというのに半月スルーしていたという最低な有様なのでとっとと直しなさい、という話なので直します。体調が悪い時にやるとポカミスやりそうなので、体調治ったら真っ先にアップデートかけますので。

  • OS再インストールする

物凄くグチャグチャなので、ずーっと再インストールしようと思っていたのですが延ばし延ばしにしすぎました。どうでもいい話なのですけど、とっととする、とここに書けばいい加減にやる気を出す気がしてきました。気のせいです。

  • Rxの記事書く

ネタは結構いっぱいあるのですが、Forumが活況すぎてそっち追うのにいっぱいいっぱい。あとRx自体が実に難しくて、私の能力的に結構いっぱいいっぱいです。あと英語のせいで脳みそが知恵熱でオーバーヒートしてます。私のTOEICのスコア舐めるなよ(受けた事ないので知らないけど、多分想像を絶するほど低い)。扁桃炎になったのもそれのせい(違)。とりあえず、近いうちにIObservableの連鎖についてきっちり書きたいと思ってます。

  • Ajax MinifierにGUIつけたの作る

Ajax Minifierとかみんな憶えてないでしょ。便利なMS製のJavaScript圧縮/解析ソフトです。CUIのみなのでGUIを提供したいと思ったのです。半分作ってあって、一応動くのはあります。ただ全然GUIになってません。Ajax Minifierが告知されてすぐに作り出して、そしてすぐに放置したというダメ人間。完成させたいです。GoogleがClosure Toolsとか出しちゃったしもういっかー、とか思ってません。Microsoft大好きっ子なのでAjax Minifier使うぜ。ていうかネーミングが悪いよね。Ajaxあんま関係ないし。

  • linq.js WSH拡張作る

作る。作る。本気本気。もう1月も終わりそうなのに何も手をつけてないけど本気。

  • プログラミングHaskell読む

F#の本がamazonから届いてしまう前に読む(笑) ちなみに積み本はいっぱいあって、どうしたものかなー、って感じですねえ。レガシーコード改善ガイドとリファクタリングとEffective C#と初めてのRubyと、まあ、そんなのを積んでます。そんな量でもないか。まあとにかく、積み本は良くないのでちゃんと読めって話ですな。

  • シュタインズゲートをプレイする
  • アサシンクリード2クリアする

シュタゲ未プレイなのが許されるのは(ry それにしても最近積みゲー多すぎですね。ゲームへの欲望ってのは本当に確実になくなってますねえ。そんなにC#楽しいか!って話なのですが。C#楽しいよLinq可愛いよハァハァ。それがいいのか悪いのかはなんとも言えないですけど、ただでさえ視野狭窄な私なので、ゲームからインスピレーションを受けないと感性が完全に死ぬので切らしたくはないですねぇ。今後はバイオショック2とガクブル島(OBLIVION)とスプセルコンヴィクションかな、手を出すのは。XBLAのDarwinia+は出たら買うと思う。まさかNAIJはないでしょう……。オフィシャルで動画見れます→Darwinia+ Promotional Trailer Streaming。良い感じ。

いうわけでどうでもいいTODO?の列挙でした。もう少し先の方を見ると、一応コレ作りたいとかアレ作りたいとかいう計画もあるんですが、少しも手をつけてないうちは妄想でしかないので、現実的な目の前の課題をちゃんと片付けてから夢見なさい、ですね。薬飲んでも続く微熱と下痢が治ってくれさえすれば――。TODOを消化出来る、なんてことはないんですが、ここ数日の人生死んだ感がヤヴァかったので、ちょっとは心入れ替えてやるんじゃないかと思われます。

人生死んだ感が発生すると、何か美味しいものが食べたくなって、食べログで近場の店を漁るなどしてしまうのだけど、一人じゃ入れないな、しょぼーん。となるなど。そんな時は「孤独のグルメ」を胸に……。いや、ランチと夜は違うじゃないですか。考えてみると孤独のグルメはお昼が多かったような。とりあえず今は美味しいビーフシチューが食べたい気分なので夜に一人で言っても平気な感じにコースじゃなくてビーフシチューだけ食べて帰れるような店(そんな都合のいい店などない)を探すという無駄に時間費やしてます。寝ろ。

デスクトップシアター for Windows 7杯

Windows 7杯 自作PCの祭典 2009応募の記事です。

応募対象は「勝手にオレが1番! 部門」で、コンセプトは「デスクトップシアター」。机周りと一台のPCに全ての機能を集約させることで、椅子から一歩も動くことなくゲーム、映画、インターネット、プログラミングなどあらゆることを快適に行うという引きこもり推奨システムです。実際、私は寝るとき以外は常に椅子の上にいます。

個室やワンルームの狭い空間を有効活用する、という点でもPCに全てを集約させるというのは合理的判断だと思います。その狭い部屋にテレビを入れる必要はあるのか?ディスプレイで全部まかなえばいいじゃない、ノートPCなんて捨てろ! 自作PCを組め!

最近では1920×1080のマルチメディア用途を狙った(ただのコスト削減流用という話でもある)16:9 HD液晶も数多いので、別に珍しいスタイルではないのですが、デスクトップシアターはただたんに表示させるだけ、ただたんに集約させただけではありません。スペースがないから妥協して集約させたのではなく、集約させたが故のメリットをハードウェア・ソフトウェア両面から徹底的に追求しました。

なお、画像はクリックすると原寸写真に遷移します。

構成パーツ

CPU : Intel Core i7 920
マザーボード : Asus P6T
メモリ : Corsair TR3X6G1600C8 2GB x 6
ビデオカード(メイン) : ELSA GLADIAC GTX 260 896MB(NVIDIA GeForce GTX 260)
ビデオカード(サブ) : LEADTEC WinFast PX8400 GS TDH Silent(NVIDIA GeForce 8400 GS)
SSD(メイン) : Intel SSDSA2MH08 X25-M 80GB x2 (RAID 0)
HDD(サブ) : SEAGATE ST31500341AS (1.5TB SATA300 7200) x2 (RAID 0)
光学ドライブ : Pioneer BD-ROM BDC-202
CPUクーラー : サイズ MUGEN∞2 無限2 SCMG-2100
ケース : CoolerMaster Sileo 500
電源 : CoolerMaster Silent Pro M600
センターモニタ : NEC LCD3090WQXi(BK)
サイドモニタ : SAMSUNG SyncMaster 2343BW x2
トップモニタ : DELL 2405FPW
HDMIキャプチャボード : Blackmagic Design: Intensity
AVアンプ : ONKYO TX-NA1007(B)
スピーカー : ECLIPSE TD307II x9
サブウーファー : ECLIPSE 316SW
Game Console:Xbox360 Elite

使用OS : Windows 7 Ultimate(64bit版)

PCケース内部

モノがある関係上、こんな角度からしか撮れなくてすみません。パーツよくわかりませんよね、これじゃあ。PC本体はあまり特筆すべき組み方でもなく、いたって普通です。メインドライブのSSDx2のRAID 0は体感ですら明らかに高速で非常に快適。プチフリ?そんなのありませんよ。みんなSSDにすればいいのさ。容量不足(160GB)は基本的にはそこまで深刻でもないのですが、HD動画キャプチャを行うので(主にXbox360の、1時間で数百GB行く)、大容量かつ高速なドライブが必要(未圧縮でキャプチャするため速度がないとコマ落ちします)。そのため、サブとして1.5TBのHDDをx2 RAID 0で用意。静音PCを狙ってHDDはスマートドライブに格納、また密閉度の高いケースを利用しました。CPU, Caseファンも平常時は低速回転させています。

かなり窒息度が高く、熱源も少なくないため温度モニタリングの状態は常に怪しげで不安度高し(平常時CPU温度55度前後)。そして絶望的な配線センス。次にPCを作るときは配線を魅せるようなのが作りたいかも。

PCケース外観

見た目はヘンテツもないわけで、実際のところ部屋の光の当たらない隅に置いてあるので、黒ければそれで良かったりします。なので、外観デザインは目立たないこと、ただそれだけを求めました。ついでに天板のサイズがぴったりだったので、ヘッドフォンアンプのSRM-600limitedを載せています。更にその上にオーディオインターフェイスとしてRME Fireface UCを設置。これは、明らかにオーバースペックでした。色々と反省。買う前は使いこなす構想があったのですが、今じゃあSTAXとAVアンプに音を送るだけの「聴き専」野郎ですよ! 許すまじ。時間に余裕ができたら、追々弄っていきたいです。

解説文

正面画像は一番上で使ったので、周囲を写して。4画面で9.1chサラウンドでXbox360しながらも同時に攻略サイトを見ながらTwitterに投稿しつつ動画キャプチャのモニタリングもしながらついでにUStreamで動画配信(以前にUstreamでFlash Media Encoderを使って高画質配信するためのまとめという記事を書いています)も出きれぅ(動画配信の状況は冒頭写真がそれです)。

やりすぎ。しかし圧倒的に便利。これこそ、まさに集約させたが故のメリットです。どれだけ過剰なやりすぎ要求にも答えてくれる、それが自作PC。

マルチメディア再生も当然、全てPCで行います。PCで動画を見ると解像度の差がTVで見るよりも、モロに出ますからね、持っててよかったBlu-rayドライブ。Xbox360好きーな私ですが、別にBlu-rayは否定しませんよ?(別にPS3も否定しませんが……買わないというだけで)。

DVDはTotal Media TheatreのSlimHDで再生しています。NVIDIA CUDAを利用した超解像による拡大処理で、Blu-rayに迫……りは全然しないのですが、まあ、何もしないスケーリング処理よりかは遥かにマシです。

音声はBD, DVD共に、PCではデコード処理は行わず生データのまま光出力でAVアンプに回してのサラウンド再生。ちなみに普通の2ch音楽の再生も、AVアンプでサラウンドエフェクトかけて聴いてたりします。音場がグッと広がって良いものです。普段はサラウンドで、しっかり聴きたいときはヘッドフォンで。そういう切り分けをすると、音楽がより楽しめます。

ベンチマーク

以上のようにコンテストの趣旨を履き違えたかのごとく、自作PC本体よりも周辺環境の拡充に力を注いでいるため、ベンチマーク結果はどうでもいいと思ってたりします。ただの飾りです。偉い人にはそれがわからんのです。

Windows 7を使って良かったと感じた点

いやー、いいOSですよ。画面綺麗だしメモリ大量に積めるし。メモリは7ではなく64bit OSの利点なわけですが。Win7は64bitのみの提供でも良かったんじゃないかしらん。ショートカットキーの充実(Winキー+矢印によるウィンドウサイズ変更とか)は最高に便利ですね!と、褒めたいのですが、この辺はAutoHotKeyによる自作スクリプトで解決していて使っていなかったり……。まあ、わざわざAutoHotKeyを導入しなくても使えるというのは良いことです。Windows標準搭載により、こういうことが出来ることの便利さが周知されるというのも素敵。この、偏狭のブログで幾ら布教させようとしても閑古鳥が泣くだけで虚しさがつどりますが、Microsoftが布教してくれたなら、それで満足です。さすがMicrosoft、(以下略)にしびれるあこがれるぅ。

あ、モニタの多さによるウィンドウ移動の大変さはAutoHotKeyの自作スクリプトで解決させました。 -> AutoHotKeyによるマウスカスタマイズとマルチディスプレイのためのスクリプト

デスクトップシアターはWindows OSとハードウェア(自作PC)とソフトウェアが全部噛みあってこそ成り立つもの。私はOSにもハードウェアにも貢献出来ませんが、一個人として、ソフトウェア側からの拡充に努めたいと思っています。AutoHotKeyスクリプトもそうだし、Xbox360の隣で常にPCが起動しているならPC側で、ネット経由でXbox360の状況をモニタリングすればいいぢゃない -> XboxInfoTwit とかもそう。

何にせよ、アホみたいに大量にソフトを起動してもメモリ余裕、OS大安定、動作軽快なわけでして、Windows 7の凄さ、良さというのを実感するところです。そういえば、Windows7といったらマルチタッチ対応も挙げられます。デスクトップシアターに有効活用できないかなあ、どうやったら上手く組み込めるかなあ、というのを考えています。まだまだ拡張の構想はあるので、来年は更にパワーアップしたもので応募してみたいですね!

更新履歴

2010/01/22 CPU-Zによるベンチマークの画像を張り忘れていました(画像自体のアップロードは行っていたのですがHTMLに張り忘れていた)。申し訳ありませんでした。

C#(.NET Framework)の文字列連結について

一般に文字列を+=で連結するのは遅いと言われています。事実そのとおりで、多量の連結を+=で行うと死ぬほど時間がかかります。例えば、以下のようなコードで示されることが多いでしょう。

// ベンチマーク用関数(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#なら逐語的リテラル文字列もお忘れなく。

Comment (2)

菊池 : (01/08 13:50)

んー、短い文字列の連結では String.Concat / StringBuilder.Appendの差は殆ど出ないという結果しか出ていないかと。(sb.Append(Get()).Append(Get())のパターンでは直前でnewしているから短い文字列しか扱っていない)
「差が殆ど出ないなら多少無駄でも StringBuilder.Append 使っとけ」を否定するには根拠薄い気がします。

neuecc : (01/09 02:25)

いやあ、趣旨が+を使ってもStringBuilderと差は出ない、だったのです。
そして結論が「+のほうがスッキリするよね?」なので、StringBuilderを否定した割に、
根拠が丸っきり主観的、な上にパフォーマンス面の話は完全スルー(というか考えてなかった)
なのが片手落ちでした……。

書いた時の心情が、何やるにもStringBuilderだらけのコードを見て
if(isHoge)
{
return true;
}
else
{
return false;
}
を見て
return isHoge
でいいぢゃん、といった感じだったので、思わずStringBuilderに敵意が。

Trackback(0)
Write Comment

Haskell用IDE 「Leksah」の紹介と導入方法

本格的にプログラミングを学び始めたのが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の取得

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。今までチャットやネトゲなど、コミュニケーション系のウェブサービスを全く受けつけなかったコミュ不全の私が、唯一利用出来たサービスだという。一人で書き飛ばしていればよくて、無理に繋がらなくていいのが非常に楽。……。まあ、私はもう少し@飛ばしてもいいと思います。むしろ飛ばせ。これも来年の目標、ですかね。

デジカメ写真の半リアルタイム確認システムの構築

写真撮る→PCに即座に転送される→ビューアーで転送された画像が自動的に開かれる→つまり撮影画像のプレビューがPCの画質で出来る、わーい。ふむ、何のこっちゃ。というわけで実際に使っている風景を動画をで撮ってみました。ケータイ画質で、風邪薬のビンを取るという適当なものですが、ただの説明なのでご勘弁を。画像転送に少し時間かかってるのでリアルタイムとは言いませんが、我慢できる範囲には収まっていると思います(もう少し速く、とは思いますが)

私は写真撮影に関して完全に素人で、ホワイトバランスがデタラメだったり露出があっていなかったり、そもそもピンボケだったりと失敗写真を繰り返しています。デジカメの液晶ではちゃんと撮れているように見えたのに!そんなわけで、撮ったものをちゃんとした画面で即座にモニタリングして、設定を煮詰め直すなり撮り直すなりが出来ればいいな、と思っていました。それが出来たら、特に室内での小物や料理撮影には便利だな、と。もちろん、Rawで撮って後から調整すれば良いという話もありますが、さすがにRawは手間がかかりすぎます。もっとカジュアルに付き合いたい。

Eye-Fi

Eye-Fiをご存じですか? Eye-Fi Japanに解説がありますが、無線LANを内蔵したSDカードで、写真を撮るとカードを抜くことなくその場で対応するオンラインサービス(はてなFotolifeやflickrなど)に送信してくれる、というものです。別にそんなに写真公開することなんてないし、そもそも外行かないしでイラナイよなー、なんて考えていたのですが、よーく解説を見ると、PCに送ることも出来るではないか。ということは、撮る→即座にPCで確認出来る。が実現出来る。こ、こんな当然のように思えることに今まで気づいていなかったなんて、悔しい。

ファイル監視ソフト

画像を自動でPCに転送出来る、となると、あとは転送された画像を自動で認識して画像ビューアーを起動するだけです。ちょっとソフトを探してみたのですが、良いものが見当たらない。まず、数分間隔でチェックするものは却下。何故なら、画像が転送されたら即座に起動してくれなければ意味がないから。他にもゴテゴテと機能が多すぎたりと、こういう単純な用途にフィットする監視ソフトが見当たらなかった。んー、ないなら自分で作ればいいぢゃない。というわけで、自分で作りました。シンプルなファイル監視ソフトを。

起動するとタスクトレイに常駐して、設定したフォルダを監視し、「新しいファイル」が作られると指定したアプリケーションにファイルパスを渡しながら自動的に起動します。例えば、指定フォルダをEye-Fiで画像が保存されるフォルダを指定し、実行するアプリケーションに画像ビューア(IrfanViewだったりPicasaだったり)を指定すると、写真を撮る度に画像が自動的に開かれる。ようするに冒頭の動画のような感じになる。というわけです。

アップロード

ついでに、Webサービスへのアップロードですが、私の場合は以前作成した半自動はてなフォトライフアップローダーを使ってワンクリックでアップロードしています。実行すると、事前に指定したフォルダの最新画像一枚だけをアップロードするプログラム(今回、少し更新してサブディレクトリもオプションで含められるようにしました)。なので、指定フォルダとしてEye-Fiが転送する画像フォルダを指定しておけば、Eye-Fiから転送されてビューアで開かれる画像を見て、採用!と思ったらクリックするだけ。それでアップロード完了。とてもお手軽です。

Eye-Fi Pro

そんなわけで、無線LANがインターネットにつながっている必要はなく、PCと一対一に通信出来ればそれでいい。のですが、残念ながら現在日本で販売されているEye-Fiカードは一対一では転送出来ません。海外で既に発売されているEye-Fi Proというモデルではアドホックモード(無線LANを搭載したデバイス、ノートPCとかと直接転送可能になる)が使えます。これがあれば、撮影→ノートPCの画面でプレビューが山でも川でも居酒屋でも、どこでも出来るのに!というわけで、早く発売されて欲しいです、Eye-Fi Pro。

GXR

Eye-Fiは大抵のカメラで使えるようなのですが、たまーに使えないカメラもあるようです。私が以前使っていたカメラは、残念なことにそのたまーに使えないカメラ、に該当してしまったようです。しょうがないので、Eye-Fiのためにカメラを買い換えました。ついでのついでなので合体機構がそそるGXRを購入。GXRは素晴らしいよ!何といってもEye-Fiが使えます(笑) 画質良し、操作感良し、そして何よりも(一眼画質のカメラとしては)軽い。よって、こういったお手軽プレビュー&アップロードには大変適している、気がします。一昨日来たばかりなうえに、私は写真ド素人なので詳しい評価は出来ませんが、かなり気に入ってます。

Prev |

Search/Archive

Category