Archive - 2009.06

Linqで文字のバイト単位でグループ分け

var input = "e4bba5e4b88be381aee69687e5ad97e58897e381af5554462d38e38292e69687e5ad97e382a8e383b3e382b3e383bce38387e382a3e383b3e382b0e5bda2e5bc8fe381a8e38199e3828b3136e980b2e695b0e381aee38390e382a4e38388e58897e381a7e38182e3828be38082";
var count = 0; // 外部にカウント用フラグ変数を置く
var words = Regex.Matches(input, "..")
    .Cast<Match>()
    .Select(m => Convert.ToByte(m.Value, 16))
    .Select(b => Convert.ToString(b, 2).PadLeft(8, '0'))
    .ToLookup(s => (s.StartsWith("10")) ? count : ++count);
Console.WriteLine(words.Count);

前回(たった10時間前ですが)の続き。16進数バイト列で表現されたUTF-8の文章の文字数を数えろ、というお話でした。せっかくなのでビット見てうんたらかんたら、のほうでも。律義に2進数に変換しながら分類。例えばwords[0]は[11100100,10111011,10100101]になる。こんな感じで41個分グルーピングされています。.Keyで何文字目かが取得出来るところが地味に素敵でいて微妙に混乱を招く(配列の何番目、ではない)。 で、まあしかし、場外にフラグ変数を置くことで何でもこなせるけれど、それってアリなの?という疑問はある。Linq連鎖の場外に変数を置くなんてグローバル変数的な害悪です。かどうかは分からないけれど、イマイチだとは思う。

場外に置かないで分類するとしたら、どうすればよいかなー。うーん。思い浮かばない。

var text = words.Select(g => g.Select(s => Convert.ToByte(s, 2)).ToArray())
    .Select(bytes => Encoding.UTF8.GetString(bytes))
    .Aggregate(new StringBuilder(), (sb, s) => sb.Append(s))
    .ToString();
Console.WriteLine(text);

こっちはどうでもいいんですが、文章への復元はこんな感じで。面倒くさー。何個Select使えば気が済むんだ、みたいなみたいな。でもLinq使わないとそれこそもっと面倒くさい気はする。とりあえず=>かわいい。=>のかわいさは異常。=>がかわいいすぎて萌え死ぬ。

Cast

天下一プログラマーコンテスト、ほうほう、例題とな?

var input = "e4bba5e4b88be381aee69687e5ad97e58897e381af5554462d38e38292e69687e5ad97e382a8e383b3e382b3e383bce38387e382a3e383b3e382b0e5bda2e5bc8fe381a8e38199e3828b3136e980b2e695b0e381aee38390e382a4e38388e58897e381a7e38182e3828be38082";
var bytes = Regex.Matches(input, "..").Cast<Match>().Select(m => Convert.ToByte(m.Value, 16)).ToArray();
var result = Encoding.UTF8.GetString(bytes);
Console.WriteLine(result); // 以下の文字列はUTF-8を文字エンコーディング形式とする16進数のバイト列である。
Console.WriteLine(result.Length); // 41

題意に全然沿わずフレームワーク丸投げ。それにしても、古いクラス(MatchCollectionとか)はIEnumerable<T>じゃないのでCast<T>が必要で面倒くさい。Castは、その段階で「型を意識」しなければならないのが思考の流れを阻害する。「Select(m=>m.」と打つと、ああ、送られてくるのはMatchなのかあ、って分かる。それ以上には考えさせないで欲しい。型推論万歳。型推論は、JavaScriptを始めとしたゆるふわ加減の心地良さと、型キッチリでコンパイルしてエラーが出て弾かれて万歳の気持ちよさがちょうどよく混ざり合ってて、イイなーって思う。だからというわけじゃないけど、なんでもvarで済ませてます、私は。var hoge = 3とか平気で書く。

そういえば、AchiralはMatchとMatchCollectionに、Selectのみ拡張メソッドで足してあるので、Castを書く必要なくスムーズに繋げられる。この面倒なMatchCollectionをどうしたものか、という解答として丁度良い妥協バランスで、なるほどなるほどでした。Achiralは読んでて本当にためになります。

追記:続き→neue cc - Linqで文字のバイト単位でグループ分け

IEnumerableに文字列連結

C#と諸々 10分でコーディングから。ネタ元、のネタ元の問題文の解読しづらさは異常。例も酷かった。というのはともかく、Linqならグルーピングのお話だよねー

class Cards
{
    public string[] Deal(int numPlayers, string deck)
    {
        var count = deck.Length / numPlayers;
 
        var result = deck
            .Select((c, i) => new { c, i })
            .GroupBy(t => (t.i % numPlayers), t => t.c)
            .Select(g => new String(g.Select(c => c).ToArray()))
            .Select(s => (s.Length > count) ? s.Substring(0, count) : s)
            .ToArray();
 
        return (result.Length < numPlayers)
            ? Enumerable.Repeat("", numPlayers).ToArray()
            : result;
    }
}

あれ、スッキリどころか意外と汚い……。んー、最初はGroupByのところがToLookupでそのままreturnしてすっきり終了。だったのですが、「求められるのはIGroupingじゃなくて文字列」なのでLookupじゃなくGroupByにして後段のSelectで結果を文字列化。「余ったカードは配られず捨てられる」ので更に後段のSelectで廃棄処分。「カードが足りなかった場合は人数分の空文字列配列を返す」のでreturnを別に分けてRepeatでそれを生成。後付けでごにゃごにゃ足していった結果、汚くなってしまった、残念。

まあでも、何が嫌かってnew Stringの部分ですね。中身がcharじゃなくて文字列の時はstring.Joinを使いますが、どちらもToArrayがウザくなってイヤー。IEnumerableにToStringは文字列で連結して欲しい。いや、Aggregateで代替出来るのは知ってますが定型処理のくせに必要なタイプ数多くてnew Stringとあんま変わらないし、セパレータも付けたいとなるといよいよ面倒くさい。というわけで拡張メソッドの出番。

public static class Ext
{
    public static string ToJoinedString<T>(this IEnumerable<T> source)
    {
        return source.ToJoinedString("");
    }
 
    public static string ToJoinedString<T>(this IEnumerable<T> source, string separator)
    {
        var index = 0;
        return source.Aggregate(new StringBuilder(),
                (sb, o) => (index++ == 0) ? sb.Append(o) : sb.AppendFormat("{0}{1}", separator, o))
            .ToString();
    }
}

残念ながら拡張メソッドは元からあるメソッド名を上書きできないのでToStringではなくToJoinedString。関数名が微妙? そうですね……。 セパレータを使いたい場合にAggregateでサクッと書けなくて昔悩んだのですが、カウント用の変数を場外に用意すればいいんだ!と気付いたのでサクッと書けるようになりました。Linqだけで1行で済ませたい場合はSelectでindex作ってやればいいですねー。

var result = Enumerable.Repeat("hoge", 10)
    .Select((s, i) => new { s, i })
    .Aggregate(new StringBuilder(), (sb, t) => (t.i == 0)
        ? sb.Append(t.s)
        : sb.AppendFormat("{0}{1}", "|", t.s));

悪夢だ。実行効率的にもアレゲ。いくらAggregateで何でも出来るといっても、この辺は標準搭載して欲しいものですねー、Sumがあるなら文字列連結があってくれてもいいじゃない、みたいな。

UstreamでFlash Media Encoderを使って高画質配信するためのまとめ

昔々、このサイトがゲーム系サイトだった(気持ち的には今も!)頃に、少しUstreamを使ってゲーム配信していたことがあったのです。その頃は画質散々、フレームレート散々で到底実用にならず、次第に縁も遠くなってしまいました。そんなこんなで、暫くお休みしていた間に、何やら外部エンコーダーを使って高画質配信が出来るようになっていたので、手法や設定などのメモをまとめてみました。ゲームだけに限らず、デスクトップ配信や勉強会配信にも適用できると思いますので参考にどうぞ。

とりあえず実際の配信結果を一つ。私はエンコード関係を確かめるときはGeometry Warsを使っています。エフェクトが激しくてエンコーダー泣かせなので。綺麗な画質、とは言えないものの十分見れる画質(そうか?)とフレームレートが確保出来ています。エンコーダーに優しいソースならもっと綺麗になります。たとえばエンコーダー泣かせの対極である静止画主体のもの、ちょうど3Dギャルゲーの体験版が配信された日だったので、それも撮ってみました-> TimeLeap。1:30ぐらいからモデルが出てきますが、結構綺麗に撮れています。動きが入るとすぐに乱れてしまいますが全体的には許容範囲に収まっています。

あ、そうそう、そんなわけで配信再開しています。Blogトップの上のほうに張り付けてあるので、気が向いたら見てくれると嬉しいです。また、配信開始時には原則Twitterのほうでも告知していますのでそちらからでも。流行に100歩遅れて乗って、実況音声も入れることにしました。あまり話せる人間じゃないのでたまにボソッと呟く程度ですが。そもそも私の声が、ちょっとアレだし。聞いててイライラする質の声だと思います。

Flash Media Encoderの設定

まずはエンコーダー。AdobeのサイトからFlash Media Encoderをダウンロードします。2009年6月現在の最新版はFlash Media Live Encoder 3。ダウンロードにはAdobeへの登録が必要なので適当に入力しておきます。それと、製品サイト/本体ともに日本語版はありません。が、別に設定するようなところも大してないので英語でも何ら問題はないでしょう。

次にUstreamの設定画面(Your Shows->Advanced)から、Flash Media Encoder XML Fileをダウンロードします。これにUstream配信用の設定が全て記載されているので、FMEのメニューFile->Open Fileでダウンロードした設定ファイル読み込む。すると、画面右側に配信用のURL等が反映されます。これだけでもう配信可能。「FME側のStartボタンを押してからUstream側で配信コンソールを開いて」配信開始するだけ。FMEを利用している場合は、配信コンソールでは何の設定もする必要はない(というか出来ない)ので、Record StartやStartを押すだけです。配信コンソールを先に開いていたり、途中でコネクションが切れたりした場合は、コネクションが再度繋がるとUstreamコンソールのInfoタブの横にReconnectタブが現れるので、そこでYes, switch to the FME Streamを選択すればFMEによる配信になります。

FME側のStartを押すとエンコードが開始されます。エンコードは開始されますが、Ustreamでの配信はUstream側のコンソールでSTART BROADCASTを押さない限りは始まらないので、遠慮なくローカルのテストのためにStart押していきましょう。で、エンコード開始するとEncoding Logタブに移りますが、このタブの右側、Staticsが非常に重要。AverageのOutput、Dropsとfpsに注目して、エンコード設定を煮詰めていくことになります。Dropsはドロップ数でフレームが欠けた数を示す。フレームが欠けたときはカクカクした動画が配信されている、ということになります。fpsのほうは平均FPSで、ドロップ数の影響で全体的にどれだけフレームレートが低下したかを示すことになります。

結果的にほとんどデフォルトなのですが、エンコード設定。。FormatはH.264ではなくVP6。これは、Ustreamとの相性が現在H.264だと悪いのと、低ビットレートではVP6の画質も悪くないので。フレームレートは30fps。低ビットレートで60fpsは画質劣化が酷いので、ここは普通に30fpsが良いかと思います。600kbpsぐらい映像に回せるなら60fpsも悪くないとは思いますが……。

映像サイズは480×360。映像サイズは自由に選べるので640×480だろうが320×240だろうが好きにしていいのですが、ここはUstreamの表示パネルのサイズである480に合わせるのが無難。ニコニコに直接綺麗に転載したい、とか思うならニコニコサイズ(512×384)も面白そう。Ustreamは表示サイズ16:9も選べるので、映像ソースがワイドなら480:270にする、のはお薦めしません。というのも右上にUstreamの透かしロゴ、左下に広告のオンオフボタンが表示されるので、4:3にすればそれらが映像ソースに被るのを避けることが出来ます。

ビットレートは映像400、音声96の合計500。ビットレートの上限はとても悩ましい。Ustream的には制限はないので、配信者の回線、というよりも受信者の回線の性能を考えて私はこのぐらいに抑えています。もう少し欲張ってもいいかもしれません。音声はサンプリングレートを22050Hzにすればビットレート80以下も選べるので、64あたりにして、その分を映像に回すというのも良いですね。

VP6のエンコーダー設定はQuality以外は弄る必要がなく、そしてQualityなのですが、これは現状Good Quality以下から選ぶことになりそう。画質優先なら当然Best Qualityだろ、と言いたいところなのですがLower Framerateと書かれているとおり、本当にフレームレートが低下します。これは録画して、上述のStaticsでチェックすれば簡単に分かります。とにかくドロップ数が酷く、私の環境(Core i7 920)でGeometry Warsを撮ると平均15fpsになってしまい、画質どころの話じゃありませんでした。平均15fpsといっても連続して15fpsではなく、始終バラバラにフレームが欠けてカクカクになった結果の15fpsだから始末が悪い。BestとGoodの間、Greatはどうかというと、ドロップ数はかなり少なく平均28fpsぐらいで、まあまあ良好と言えるのですが、「少しとはいえカクカクする」 vs 「そこそこの画質向上」を秤にかけた結果、私はフレームレートの安定のほうを選びました。この辺は、好みとマシンスペックと実際に配信する動画の性質によって変わるので、何度かオプション変えてエンコードして、Staticsを確認してみてください。マシン性能によってはAvg QualityやLower Qualityにすべきかもしれません。

SCFH DSF

ここからは取り込みもとの映像ソースの話。デスクトップ配信/ゲーム動画配信をするなら、ManyCamだの色々ツールはありますが、現在はSCFH DSF一択になります。高機能で軽くて設定が楽なので、他のを選ぶ意味がありません。インストール方法は、解凍したSCFH DSFをこれ以降動かすことのないフォルダに配置してinstall.batを実行。あとは、Flash Media Encoder(以下FME)を起動した後にSCFH DSFを起動し、FMEのプロセスを選択してOK。これでFME側のInputプレビューにSCFH DSFの画面が表示されているはずです。

設定は、まずメニューからLanguage->Externalを選んで日本語にする。そして、Drag Hereでキャプチャソフトのプレビュー画面(私はキャプボにIntensity、ソフトにKusunokiTV HDを使っています)を選ぶ。縮小方法は基本はSoftware(Bilinear)。CPUがよほど貧弱でなければ、これを選ぶべき。ただし、カクカクしてまで綺麗な縮小画質にこだわるのは無意味なので(カクカクのほうが、よほど動画としての画質が低い)、右下のパフォーマンスのfps表示を見て、酷く低いならもっと軽量の方法を選ぶと良いでしょう。オプションはお好みで。オーバーサンプリングはパフォーマンスのfpsに余裕がありそうなら、チェック入れとくと素敵な気分になれる。

SCFH DSFはレイアウト機能によって、多数の取り込みソースを自由に合成することが出来ます。Xbox360の映像ソースは16:9なので、上下に領域が余る。この余った上下の領域には、黒帯+メッセージを表示することにします。画面解像度は480×360で、映像は480×270。つまり480×45の領域が使えることになる。今回は付箋紙21(2000)を用いて黒帯+メッセージ表示域を作成してみました。付箋紙21SEではないのは、直接編集機能を利用するためです。

背景色を黒(別にピンクでも画像でも何でも構いませんが)、付箋の縁は「なし」、それとオプションでダブルクリック時の挙動を「直接編集」に変えておくと編集が楽になります(なお、直接編集時に編集から抜けるショートカットはCtrl+F4)。最後に480×45へのサイズ調整ですが、手作業で合わせるのは不可能なので、ツールを使います。ここはAutoHotkey万能説に則り、こんなスクリプトを使うことにします(#IfWinActive系のはこれより後続の設定の全てに影響を与えるので、スクリプト設定ファイルの後方に追加しましょう)。

#IfWinActive, ahk_class THUSENSHI
F1::WinMove, A,,,,480,45

付箋紙に対してのみ、F1キーを480×45にリサイズするショートカットに設定、という意味になります。というわけでF1を押してリサイズ後は、右クリック->サイズ・位置固定にチェックを入れておくとサイズが変わらないことが保証されて安心度アップ。AutoHotKeyの使い方に関しては過去の記事、AutoHotKeyによるマウス/キーボード徹底カスタマイズで詳しく紹介しています。スクリプトに関してはAutoHotKeyでウィンドウのタイル配置でも少し書いていますので良ければどうぞ。スクリプト書こう!とか気合い入れてキーリマップしよう!とか考えると若干面倒くさいのですが、上のスクリプトを適用させるぐらいならAutoHotKeyをダウンロードしてAutoHotkey.iniに↑のコードをコピペするだけなので、簡単にすぐできます。

付箋紙ができあがったら、あとは、FCSH側でウィンドウ追加してDragHereして上部と下部に設置すれば出来あがり。

音声調整

音量が大きすぎて割れたり、逆に小さすぎるのは困りものなのですが、FMEの音量モニタリングはイマイチ分かりにくい。音量は事前に別のツールである程度調整すると良いでしょう。ここはSoundEngine Freeを使います。大きな効果音が鳴るシーンで波形が割れないように、実況するならばマイク音声も込みでBGMと声のバランスを合わせるためにも、何度も何度も録音しては調整して録音しては調整して、を繰り返すわけですが、録音しての繰り返しは面倒くさい。WaveSpectraを使えばリアルタイムに観測できますが、リアルタイム過ぎて逆にチェックしづらいので、私は何度も録音しての繰り返しになりました。

波形見て、0dbを頻繁に超えているようなら割れまくりということなので、音量下げるべし。それでも、ゲームによって音量違ったりするし、マイクが絡むと面倒くさいしで、一回設定決めれば二度と弄らないでいい、というようなものにはならないので難しいですねえ。

実況音声

利用しているサウンドカードはマイク入力がないので、マザーボードのオンボードMic入力を使おうと思ったのですが、いざ試してみるとノイズが酷過ぎるしマイクブーストは調整がピーキー過ぎるしで、使いものにならなかった。オンボードで苦労するぐらいなら、ササッと諦めて外部アンプや外部ミキサー買う方がきっと幸せになれます。

マイクは、私はヘッドセットが苦手なのでスタンド型のものを。サウンドカードのライン入力にマイク音声を入れるためミキサー、ついでに、もうどうにでもなれー、とばかりにマイクアンプもセットで購入。こうして、オーディオテクニカ配信三種の神器が完成してしまった。馬鹿にならない出費に……。とはいえ、オンボードのマイク入力品質、というかラインインも含めてアナログ系の品質はかなり酷いので、ササッと諦めた方が良いです、と思いたい。マイクアンプもミキサーも、あると物凄く楽になります。必死にオンボードのマイクブーストの設定と格闘、とかアホらしいぐらいにクリアに録れるし音量調整も簡単。というわけでカウトイイトオモウヨ。

実際の環境

こんな感じで配信しています。マイクが少し見づらいかな、一応正面に立ててあります。モニタは真ん中にゲーム画面、右にエンコード関連、左に配信コンソール(チャットや配信数監視)とブラウザ(ついったー見る)。PCモニタをゲーム画面代わりに使うと、こういうところが楽でイイ。そして、ディスプレイ枚数は正義。グラフィックボードの追加が必要なトリプルはともかく、今時のPCなら(たとえノートPCであっても)デュアルは欲しいねえ。なお、この環境は30インチWQXGA(2560×1600)と23インチQWXGA(2048×1152)が二枚です。

モニタ安いので、接続端子が空いているならばデュアルにすると、とても幸せになれます。どうせサブだからと17や19インチ買うよりも、思い切ってサブも横1920のものを選ぶと、横が長いので色々配置出来て良い。サブなので24とか大き目のものは辛いので(単純に、あまり横に伸び過ぎると目の移動量が多すぎて疲れる。理想は勿論三枚同じディスプレイにすることですがメインは大きいモニタ、サブは小さめに、というのも私はアリだと思っています。三枚全てが30インチとか現実的に無理だし)21インチぐらいのがいいんじゃないかなー、最近だとフルHDサイズとして結構出てますよね。まあ、勿論、メインモニタがWUXGAならWUXGAで、インチサイズ、出来れば製品自体も揃えた方が良いのは間違いないのですけど、安いモニタを3枚よりはメインだけ奮発して高いモニタを、としたほうが、私は良いと思ってます。結局視点の8割はメインモニタに集中するので……。

AutoHotKeyでウィンドウのタイル配置

以前にもAutoHotKeyカスタマイズネタを書きましたが、私はウィンドウ移動系を多用しています。トリプルモニタの間を瞬時にジャンプさせたり、サイズをフィットさせたり。Windows7からWindowsボタン + ←でディスプレイの左半分にリサイズとかがあるので、幾分か被ってしまっているところもありますが、細かい部分では自分で調整したもののほうが、やはり使いやすいです。今回はその中から一つ、同一アプリケーションのウィンドウを水平に並べる関数を紹介します。水平に並べるって、昔懐かしMDIアプリにはありましたが、タブ全盛の昨今だと結構忘れ去られ……。

OSをWindows7に変更したところ、便利なQTTabBarが動かない。困ったねえ。ファイラー使えば?って話なのですが、エクスプローラーに統合されてる便利さは何にも代え難いものがある。と、いうわけで、なんかもうファイラーに戻る気しないしエクスプローラーのままでいいよぅ。でも、せめて多用するファイル移動をスムーズに行いたい→ウィンドウ複数毎開いたた時にズらすのが面倒くさい→こんなの全自動で並べるべき、人力で毎回やるなんてアホ→AutoHotKeyでスクリプト書きましょう。

;アクティブなアプリケーションと同一種類のウィンドウを水平垂直に並べる(最大4枚まで)
;アクティブウィンドウの左上座標が含まれるモニターに並べる
TileMove()
{
    WinGet, activeWindowID, ID, A
    WinGetPos, x, y, w, h, ahk_id %activeWindowID%
    SysGet, monitorCount, MonitorCount
    Loop, %monitorCount%
    {
        SysGet, m, MonitorWorkArea, %a_index%
        if (mLeft <= x && x <= mRight && mTop <= y && y <= mBottom)
        {
            WinGetClass, activeWindowClass, ahk_id %activeWindowID%
            WinGet, id, list, ahk_class %activeWindowClass%
            Loop, %id%
            {
                w := (mRight - mLeft) / 2
                h := (id > 2) ? (mBottom - mTop) / 2 : mBottom - mTop
                x := (Mod(a_index, 2) == 1) ? mLeft : mLeft + w
                y := (a_index <= 2) ? mTop : mTop + h
 
                StringTrimRight, this_id, id%a_index%, 0
                WinActivate, ahk_id %this_id%
                WinWaitActive, ahk_id %this_id%
                WinMove, ahk_id %this_id%,,%x%, %y%, %w%, %h%
            }
            break
        }
    }
}

AutoHotKeyは三項演算子ないのかよ、クソ!とか思ってたのですが実は普通にあったことが判明。知らんかった。今回ようやく気付いた。で、これは関数の形にしているので、設定ファイルの上のほうにでも張り付けてやって、割り当ては好きなキーを選んでください。私は無変換+Tabに振っています。その場合は「vk1Dsc07B & Tab::TileMove()」になります。

実行するとこんな風に整列します。二枚の場合は縦領域を全部使って平行並べになる。ワンタッチでサクッと出来て、かなり便利だと思います。あ、そういえば全然関係ないのですが、Win7からフォルダの新規作成にCtrl + Shift + nというショートカットキーが振られています。これは嬉しい。今まではQTTabBarのプラグインCreateNewItemButtonを使ってCtrl+Kを振っていたのですが、これでQTTabBarがなくてもやっていけるようになった、かどうかはまだまだ微妙だけど、何とか我慢できる。

つまるところ、Win7はかなりイイ。タスクバー回りも大きく変更が加えられていて、最初は慣れなくて以前の方式に戻そうかと思ったのですが、慣れてくると良く出来てるなーと感心しました。いやー、発売日には憧れの(?)秋葉で並ぶ、をやりたいぐらいに良いですねえ。ドライバも、インストール時に跳ねられるものもVista互換モードで動かすと大抵入るので発売日から入れようと、RCから入れようと、不安なところはない。

XboxInfoTwit ver 1.2.0.0 フルスクレイピングに変更

Xbox.comのサイトがリニューアルされましたね!フレンドのフレンド表示機能のお陰で、ついに念願かなって自分自身のステータス取得に成功。野良APIを使わずに全てスクレイピングで実装することができました。よって、今回からは100%取りこぼしなく、ステータスの変更も瞬時に反映されます。他に変更点としては日付が投稿時の時間になりました(Xbox.com上からは時間を取れるものがないので) あと、書式に実績の数を追加しました。”%TitleTotalAchievementCount%:タイトルの最大実績数”, “%TitleAchievementCount%:取得した実績数” です。この変のよくわからないうえに長ったらしい名前はとても後悔しています……。

バグがあったら教えてください。どう見てもテスト不足なのでサクッと落ちるかもしれません。(言ってるそばから不具合発見しました。おまけに、zipファイルに格納するファイルが足りなかったので新規にダウンロードした場合は動かなかったっぽい。酷すぎる。というわけでver 1.2.0.1になりました。)

linq.js ver 1.3.0.2 - CascadeDepthFirst/BreadthFirst

linq.jsにCascadeDepthFirst, CascadeBreadthFirstを追加しました。ちょっと分かり辛いのですが非常に強力です。深さ優先/幅優先で多段SelectManyをかけていくようなイメージ。説明しづらいので例をどうぞ。

<div id="tree">Root
    <div>Spine
        <div>Neck
            <div>Head</div></div>
        <div>RClavicle
            <div>RUpperArm
                <div>RLowerArm
                    <div>RHand</div></div></div></div>
        <div>LClavicle
            <div>LUpperArm
                <div>LLowerArm
                    <div>Hand</div></div></div></div></div>
    <div>RHip
        <div>RUpperLeg
            <div>RLowerLeg
                <div>RFoot</div></div></div></div>
    <div>LHip
        <div>LUpperLeg
            <div>LLowerLeg
                <div>LFoot</div></div></div></div></div>

こんなHTML、というかツリーに色々と操作することにします。例えばinnerTextのように、深さ不明のchildNodesを全て掘ってTextNodeだけを取り出して値を連結するとしたら、通常は再帰を使いますよね。でも、CascadeDepthFirstを使えば

var root = document.getElementById("tree");
var innerText = E.Make(root)
    .CascadeDepthFirst("$.childNodes")
    .Where("$.nodeType == 3")
    .Select("$.nodeValue")
    .ToString();

こんな感じに書けます。rootから深さ優先探索で子ノードを全取得、そのうちnodeTypeが3のもの(TextNode)のnodeValueを文字列連結。宣言的に、再帰よりも分かりやすく書けます。何より後段で豊富なLinqメソッドを使って値を操作していけるのが利点になります。今回から新しく追加されたMake(hoge)というのはRepeat(hoge,1)と等しい。Fromを用いるとオブジェクトはKeyValuePairに、文字列は一文字ずつのシーケンスに化けてしまうので、今回からこのメソッドを用意しました。

実のところMakeもCascadeDepthFirstもC# 3.0 Supplemental Library: Achiral - NyaRuRuの日記のパクりです(AchiralではMakeはMake.Sequence)。ぶっちゃけ今までも勝手に散々パクッているので(ごめんなさい、ありがとうございます。ライセンス的にアレっぽいので、どこかに書いておかないと……) むしろ何で今更追加なのかというと、あまりにも露骨すぎて劣化コピーを入れるのは失礼に思っていて。結局入れてしまったけれど。そんなわけで応用的なものを一つ。引き続き↑のDOMツリーを使って……

var root = document.getElementById("tree");
E.From(root.childNodes)
    .Select(function(child) { return { child: child, parent: root} }) // 外部の値を取りこむ時は文字列ラムダ式は使えません
    .CascadeDepthFirst(function(pair) // 中を入れ子で親の値を参照したいので文字列ラムダ式は不可
    {
        return E.From(pair.child.childNodes)
            .Select(function(child) { return { child: child, parent: pair.child} });
    }, "v,n=>{value:v,nestLevel:n}") // CascadeDepth/BreadthFirstの第二引数はネストレベルを利用可能
    .Where("$.value.child.nodeType == 1") // ELEMENT_NODEだけを取得するためフィルタ
    .WriteLine(function(t)
    {
        return t.nestLevel + ':'
            + E.From(t.value.child.childNodes).First().nodeValue + ' . '
            + E.From(t.value.parent.childNodes).First().nodeValue;
    });

例もパクりです。与えられた木から,子→親への対応を作る,を C# で - NyaRuRuの日記を、HTMLだったらツリーはDOMだよね、という感じで書いてみました。第二引数にはネストレベルを取得できるリザルトセレクターが使えます(省略も当然可能)。んー、DOMだとTextNodeが混ざるせいで、例として不適切に処理が混沌としてしまいました。あ、WriteLineの部分は子要素のFirstがTextNodeであると決め打ちしてます。正確にやりたいなら冒頭のinnerTextの例のようにWhereでTextNodeのみに絞った上で、ToStringで連結したほうが良いですね。

CascadeDepthFirstをCascadeBreadthFirstに変えると(両者は探索方式が違うだけで引数や戻り値は一緒)、こんな結果になります。

動作の差異がよく分かるんじゃないかなーと思います。

continue/break

ForEachでcontinue/breakを使えるようにしました。

E.Range(1, 100).ForEach(function(i)
{
    if (i % 2 == 0) return true; // continue
    if (i == 7) return false;    // break
    alert(i); // 1,3,5
});

灯台下暗しというか、こういうのって自分が必要になるまで気付かないというか。Linqって基本的に前段階のWhereで絞るからこの辺のものって必要になる機会が少ないんですよね。いや、言い訳ですけど。で、まあ、jQueryと同じでtrueをreturnすればcontinue、falseをreturnすればbreakになります。実装は超単純。

while (enumerator.MoveNext())
{
    if (action(enumerator.Current, index++) === false) break;
}

ActionなのにFuncになってしまった!しまった!と、思わなくもないけど、この辺が後付けでグダグダになるのはしょうがないので気にしないことにしよふ。じゃなくて、基本はActionなのでヨシとしておこう。むしろこの辺はJavaScriptがユルフワなのでC#ルールに(用語を)当てはめようとして無理が出ているだけ。そういえばで、個人的にはreturn;でcontinue、return true;でbreakにしたいなあ、とか思ったり思わなかったりなのだけど、その辺は標準に合わせた方が混乱しなくていいよねー、と思うことにしました。

Suggest(モドき)

どうも、新PC組んだりして(Core i7だしメモリ12GだしIntelのSSD2枚刺しでこれで数年は買い換えないでも前線で闘っていける!と思いたいのだけど、そこら中で微妙にコスト削減で若干残念になっていたり、ExtremeじゃなくてMainstreamなところとか) まあ、それはそれ。で、Windows7 RC1を入れて若干の苦戦を強いられていて環境整えるのに時間かかりそう-、というわけでショボいネタを一つ。

// これが補完候補、ただの文字列配列
var list = ["tokyo", "kanagawa", "tochigi", "saitama", "sapporo", "nagano"];
 
// イベントハンドラは引数を二つ取る、一つは送り元、もう一つはイベント
X.ID("inputBox").AttachEvent("keyup", function(sender, event)
{
    var suggestArea = X.ID("linqsuggest");
    suggestArea.RemoveNodes(); // 子ノード全削除
    if (event.keyCode == 13) // eventはイベント
    {
        sender.value = ""; // 空にするだけ……
        return;
    }
    var text = sender.value; // senderは送信元DOM要素、今回はTextBox
 
    // この4行が本編です、ええ
    suggestArea.Add(X.Elem("tbody",
        E.From(list)
            .Where(function(s) { return s.indexOf(text) != -1; })
            .Select(function(s) { return X.Elem("tr", X.Elem("td", s)) })
    ));
});

入力補完モドきです。見にくいですけど、ソースコードの上にある入力ボックスにgaでkanagawaとnaganoが表示される、とかそんな感じ。エンターキーによる入力には対応してません、ただたんに絞り込んで表示するだけのデモです。んーと、suggestArea.Addからの、たった4行でリストの絞り込みして->DOM作成が出来るね、簡単だね、素晴らしいね、関数型構築とLINQの合わせ技は強力ですよね、だからみんなも使ってね、ということが言いたいそうです。世の中、WhereしてSelectするだけで9割方カバー出来る。気がする。気のせい。

ちゃんと真面目に作っていきたい気もするのですが、この強引リニアサーチのまま拡張しても野暮ったいだけで意味ないなー、と思うので気が乗らない。でも、大量のデータを扱うわけじゃなければ、この程度の手抜き実装でも普通に何とかなるものですよねー、とは思わなくもない。無理に洗練されたものを作ろうとせず、領域を見極めて労力を最小限にしたいと私は思ふ。いや、手抜きじゃなくて。手抜きですけど。

今後のlinq.xml.jsは微妙に機能足りてないというか、クロスブラウザ回りがグダグダなのと(Attributeにstyle指定がIEだとコケるとか!) DOMと混ぜ合わせてると気になるところが幾らかあるので、ボチボチと作っていきます。素のlinq.jsには深さ優先探索/幅優先探索を入れようとしているのですが、PC入れ替え中なのでもうしばらく後。

一体誰にアピールしてるの?な寂しさを感じつつも、それを言ったら前回の記事なんてどこからどうみても露骨なまでに受け狙いに走ったわりには大不発で寒すぎたので、それを考えたらマシです、いや、よくわからないけれど。でも簡単アピールは何か違う気がする……。うーん。まあ、誰が使わなくても自分は使うわけなので適当に行きましょふ。

最もタメになる「初心者用言語」はVisualStudio(言語?)

本題、に入る前にlinq.jsの更新内容を。今回からVisualStudio上でJavaScriptを書く際にIntelliSense(コード補完)のサポートが効くようになりました。これでもうリファレンスと睨めっこしなくてもすみます。IntelliSenseが出てくれば使えて、出てこなければ使えない。引数の型までは補完してくれませんが、メソッドチェーンに関しては100%信頼出来ます。利用するにはVisualStudio2008 SP1と対応パッチをあてる必要があります。私はProfessionalで確認していますが、Express Editionでも行けると思います。

もう一つ、linq.xml.jsのほうは、今回からXML/DOMの関数型構築が可能になりました(関数型構築の詳細は前回記事を参照ください)。それに伴い実例として、同梱してあるTwitterビューアサンプルも関数型構築に、ついでに@usernameにアンカーを張るように変更しました。

DOM構築時にLinqクエリを埋め込めると何が便利なの?のサンプル。全体像(obj.textって何だよ、とか)は同梱のものを見てください。obj.textは「@hoge hugahuga @foo hogehoge」な感じ(ようはTwitterの投稿の本文)で、文字列置換なら@usernameをa hrefで囲むだけだけど、DOMでやるにはどうしよう→全部マッチさせちゃえばいいぢゃない、それならSelectで射影すると楽ですね、という流れ。E.Matchesはキャプチャ付きの正規表現グローバルマッチが気楽に使えて非常に便利。

VisualStudioの薦め

以前に最もタメになる「初心者用言語」は、という話題で盛り上がっていたけれど、その時JavaScriptを挙げた人の多くが開発環境を入れる必要なく、テキストエディタがあればそれだけで始められるから、といったものが多かったけど、正直どうかなー、と思っていました。1年以上前のネタに今頃乗っかるのって感じですが今頃乗っかてみます。

初心者は、使えるメソッドが分からないから一々リファレンスを引くのに手間取るし(私は今だって文字列のsubstrとsubstringで悩む、特にJavaScriptはこの手の罠が多いし)、しょうもないタイプミス(大文字小文字とか)に悩まされてプログラムが動かない。演算子やカッコにスペースを空ければいいのかどうかも悩むし、一々手で直すのは手間だけど直さなければ汚いし……。

言語の学習って、特に最初はとにかく書くこと、それが最も覚えやすいと思うのです。リファレンスを引く時間も、手動でスペースを空ける時間も、言語の学習とは全く関係ない余計な時間です。限りなくゼロに近づけた方が良い。リファレンスを引く時間がゼロならば、その分だけ書く作業に時間を回せます。だから、テキストエディタじゃなくIDEを使おう。入力補完は一々リファレンスを引く必要もなく使えるメソッドをリストアップしてくれるし、ヘルプを見る必要もなくメソッドの解説が書かれている。補完があるからタイプミスも起こらないし、「長くてもいいから良い名前を付ける」習慣もつく。インデントだのスペースだのカッコの段落だのは、全てドキュメントフォーマッタ(VSではCtrl+E,D)で機械に任せるのが、完璧で綺麗で、何より手間がかからない。

そんなことより前に、そもそもforで変数がどう移り変わっているのかもよくわからないし。デバッガ、あったほうがいいでしょう、絶対に。あるのとないのとでは学習速度の桁が違ってくる。Firebugがあるって?いや、Firebugは確認用には物凄く便利だけど、それの上で開発とか辛くない?私は無理でした。ついでに言えば、Firebugインストールする手間もVisualStudioをインストールする手間も一緒。

Microsoft Visual StudioはWebインストールを選べば何の手間もいらずダウンロードされ、そのままインストールに入り30分程度で終わる(ダイアログの「はい」を押して待つすだけです、Eclipseと違って何入れればいいか、プラグインがどうのこうの、で悩む必要なくそのものがAll in One)、VSの使い方を知るのに30分。周囲に使える人がいれば、勘所を聞くだけで済むので10分。別に何も最初から全てを知る必要はなく、ダミーのASP.NETプロジェクト作って、F9でブレークポイント設定してF5でデバッグ実行してウォッチウィンドウで変数を監視する。それだけ分かれば十二分です(そこを分かるまでが辛い、というのはあります、分かっている人が横にいれば1分で済む話なのに、こんな初歩的な情報はあまりネットに転がってないから分かりようがない。需要がありそうなら書きますが……)

jQueryにも使える

jQuery大人気! やっぱjQueryが使えないとね!と、で、VisualStudioはjQueryに、jQueryオフィシャルが対応しています。どういうことか、というとjQueryのダウンロードページを見てください。Release NoetsとMinifiedとUncompressedと、Documentation: Visual Studio。そう、この公式に配布されているjquery-vsdoc.jsを使う(jquery.jsと同じディレクトリに置くだけです)と、VS上で丁寧に書かれた解説付きの入力補完が効くようになります。

jQueryの特徴といったらメソッドチェーン。ドット打つとの候補が出てくる出てくる、という感覚はとても楽しい。その楽しさを更に加速させるのがIntelliSense。メソッド名何だっけ、引数は何を取るんだっけ、と悩むこともない。ドット打って選ぶだけ。処理の本質部分だけに集中できる。

そういえばオブジェクトっぽい話が分かるかもしれないJavaScript講座が大人気ですけれど、便乗して言うとjQuery使うなら、まずVisual Studio、があれば、もうあとはドット打つだけなので何も考えないでもいいぐらい。んーと、本題はオブジェクトっぽい話、ですけれど、アレですね、何のかんのでオライリーのJavaScript 第5版を読むのが一番分かりやすかった。ケチッてWeb上のチュートリアルをブクマして読んで、なんてやるよりも、この場合は黙って金出して本読むのが時間の節約にも、得られる知識の濃さ的にもよろしいかと思われます。

amazonついでにLINQ本も張っておきます。LINQ本は以前に「LINQテクノロジ入門」という薄い本が出ているのですが、ほとんどLinq to Sqlのことしか書いていないので、少し高いのですが本で学ぶのならこちらの、プログラミングLINQのほうが良いです。初級-中級的にはLinq in Actionのほうがお薦めなのですが翻訳されてないしされる気配もなさそうですね、残念。そうそう、linq.jsも宜しくお願いします。 C#との高い互換性と、リファレンスにおいてあるLINQ Padはリアルタイムに動作を確認可能、なので学習用途ならかなりイイと思います。パフォーマンスは?実装がヘコい?是非とも突っ込みを!

入力補完の嬉しさ

まんじゅうの例を借りると、prototypeにメソッド突っ込んでオブジェクトほにゃららで何が嬉しいのか、というと、入力補完が効く。使えるメソッドがドット打つだけで出てくるし手入力する必要ない。jQueryのようにヘルプは出てきませんが、補完はちゃんと効くのです。同様に何のメソッドがあるのかさっぱり分からないDOM要素に対しても動くし、紛らわしさ全開な文字列に対しても効きます、便利便利。こうして眺めてると、知らないメソッドがあったりして勉強にもなる。

ところで突然C#の話に移って同様に、ポリモーフィズムで何が嬉しいの→入力補完が効く。ジェネリクスの何が嬉しいの→入力補完が効く。動的言語じゃなくて静型な型付け言語(C#とかJavaとか)の何が嬉しいの?→入力補完が超完璧に効く。型推論の何が嬉しいの?→入力補完が超完璧に効く上に更に型を入力する手間も最小限に抑えられる。

// これをそれぞれ二乗する
int[] array = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
// C# 2.0ではこんなクソみたいなコードが
int[] c2 = Array.ConvertAll<int, int>(array, delegate(int i) { return i * i; });
// C# 3.0ではこんなに短く! Rubyっぽく見えませんか?
var c3 = array.Select(i => i * i);

C#2.0だと、本質的な記述部分(i x i)以外の量の方が死ぬほど多くなってしまい、これじゃ嫌われて然りであり動的言語に行きたくもなる。何個intを書かせるんだよボケ、intなんて自明なんだよクソがって感じです。けれど、C#3.0からの型推論+ラムダ式があればとてもすっきりした上に、ちゃんと補完が効く(i x iを記述している最中もiがintであることを示してくれる)ので物凄く書きやすい。JavaScriptもいいけどC#も良いです。無名関数を愛するJavaScripterならC#のラムダ式はもっと愛せると思います(ウザいカッコとreturnが必要ないので)

C#は特にWindows用のデスクトップアプリケーションを作るなら、作りやすさで言えばベスト。.NET Frameworkは嫌だ?やっぱC++でしょって?うーん、ゲームを作るんでもないならC++の性能は必要ないし、作りやすさでいったらC#>超えられない壁>C++ですよ。C#は開発者に優しいゆるふわ言語。C++はF1マシン、とっても扱いにくいけど凄く速い。C#はプリウス。実用性と最先端ハイテクのバランスが絶妙。そもそも公道をF1カーで走ることが無茶苦茶なのです、公道はプリウスで十分どころか、むしろベストというもの。何にでも、適した領域というものがある。

しかも今C#を覚えると、もれなくSilverlightも付いてきます(Flashの開発言語がActionScriptであるように、Silverlightの主な開発言語はC#)。Flashに勝てるわけないだろヴァーカって?何にせよとりあえずツバだけでもつけておくと生きやすいかなーって思いません?Flash業界なんてレッドオーシャンもいいとこ、今から飛び込むならSilverlight。Microsoftは本気なのであっさりと消滅することはないでしょう。本気の時のMicrosoftはとってもしぶとくしつこい。

でもまあ、そんな云々はどうでもよくて、普通にLINQ楽しいですよ!が一番に言いたいことです。ええ、LINQ楽しい。LINQがあるからC#万歳だし、LINQがなくても作ったのでJavaScriptも万歳。linq.jsは初回リリース時にチュートリアルめいたものが書いてあるので、よければどうぞ、というかお願いしますお願いします。

Search/Archive

Category

Profile


Yoshifumi Kawai
Microsoft MVP for Visual Studio and Development Technologies(C#)

April 2011
|
July 2018

Twitter:@neuecc
GitHub:neuecc
ils@neue.cc