linq.js サンプルサンプル

前回投稿したlinq.js、誰の目にも触れず埋もれてしまうのではないかと思っていたのですが、ある程度は見てもらえたようでホッとしました。表作ったり動画作ったりと、ゲーム攻略サイトのノリで書き上げたかいがありました(そして、もうすっかりゲームサイトじゃなくなってしまった!)。ここ数日に新しく登録されたCodePlexのプロジェクトで見ても、Views/Downloads共に多いほうのようです。とはいってもDLは30前後なので、感覚的には全然少ないのですが。ただ、そもそもJavaScriptで野良ライブラリを使うシチュエーションなんてないだろ常識的に考えて……といったところなのでしょうがない。でも使って欲すぃ。前回は完全にチュートリアルだったので、今回はちょっと実用っぽいサンプルで行きます。

// abcdefghijklmnopqrstuvwxyzという文字列を作る
E.Range('a'.charCodeAt(0),26)
  .Select("String.fromCharCode($)")
  .ToString()
// 現在の年から10年前までを生成(2009,2008,2007...,2000)
E.RangeDown(new Date().getFullYear(),10)
// 0で初期化した配列、とか
// 空配列で初期化した配列、とかに地味に便利(いわゆるFill)
var array = E.Repeat(0, 100).ToArray();
var array = E.Repeat([], 100).ToArray();

RepeatはC#ではイマイチ使い道が見つからないんですが、JavaScriptでは激しく便利なよう。あ、この辺はLINQ Padで動かしながら作れます。a-zの生成は頭の中で作ってたモノはちょっと違ってて反応みながら即座に直してました。やっぱリアルタイムで反映されると便利。特にJavaScriptはIntelliSenseがないので、細かいミスが多くなってしまうので(charCodeFromだと思ってたんですよー)

そういえば、a-zの生成の26という数字は良くない。'z'.charCodeAt(0)を使いたいのですが、Rangeの二つ目はcountなので、(z-a+1)を書かなくてはいけなくて、少し長くなるかなあ。「第二引数の数字まで生成」も用意しておくべきでした。近いうちに追加しておきます。E.ToInfinity('a'.charCodeAt(0)).TakeWhile("$<='z'.charCodeAt(0)")で結果は同じになりますけど、無駄があるかなあ。TakeWhileは加工した後のものに使いたいのであって、生成のすぐ後に使うのは違和感がある。

E.Repeat(null)
  .Select("Math.floor(Math.random() * 26 + 'a'.charCodeAt(0))")
  .Select("String.fromCharCode($)")
  .Select("Math.round(Math.random()) ? $.toUpperCase() : $ ")
  .Take(100)

更に応用して、100個ランダムなa-z,A-Zのアルファベットを生成する、というもの。Selectを繋げていっての加工は楽しくて簡単。生成後はToArray()で配列にするのも、ToString()で文字列にするのも、好きに選べます。終了条件を今回はTake(100)にしましたが、TakeWhileで条件をつけるのが自然。たとえば画面を埋め尽くしたら終了とか。

var rand = new Random();
Func<bool> TrueOrFalse = () => (rand.Next(2) == 0);

Enumerable.Repeat(rand, 100)
  .Select(r => r.Next('A', 'Z' + 1))
  .Select(i => TrueOrFalse() ? (char)i : (char)(i + 'a' - 'A'));

文字を数字と見なしての生成はC#のLINQでも便利に使えます。C#だとこんな感じでしょうか。randomの扱いがちょっと微妙かな? Repeat内にnew Random()としたいところなのですが、それだと二個目のSelect時にはRandomが消滅しているので使えないので、しょうがなく。

Enumerable.Repeat(new Random(), 100)
  .Select(r => new { r, i = r.Next('A', 'Z' + 1) })
  .Select(t => (t.r.Next(2) == 0) ? (char)t.i : (char)(t.i + 'a' - 'A'));

最初のSelectの段階でランダムも保持しておけば解決。ただ、素直に外出しのほうがいいかなあ、という気もします。この辺、どっちにすべきか微妙に悩ましい。この程度の場合ならSelectを二段にせず、一段階目で全部処理してしまえば万事解決な気はします。個人的には多段階に繋いでこそのLINQであり浪漫だと思うのだけど……。

他に、例えばしつこくランダムソートを検証すると……

var result = E.Repeat(0, 10).ToArray(); // 結果格納用配列の初期化
for (var i = 0; i < 10000; i++){
	var array = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; // 配列はあえて手書き?
	array.sort(function() { return Math.round(Math.random()) ? 1 : -1 });
	result[array[0]]++;
}
// 画面に出力
E.From(result).ForEach(function(value, index){
	document.write(index + ':' + value);
	document.write("<br />");
});

/* 結果例
0:498
1:888
2:808
3:1345
4:1033
5:1119
6:897
7:1214
8:1170
9:1028
*/

初期化と書き出しにだけlinq.jsを使いました。比較関数にランダムはダメ・絶対。と、こんな感じに、地味にコソコソっと普通のJavaScriptコードに混ぜ込んでも便利に使えるんじゃないかなあ、と思います。私自身は、もうこれなしだとJavaScript書く気になれない、のですがそもそも全然JavaScriptで何も書いちゃあいないので何とも言えなかったり。

とりあえず、あと一回分はアップデートネタがあるので、今週末にでも追加します。

Profile

Yoshifumi Kawai

Cysharp, Inc
CEO/CTO

Microsoft MVP for Developer Technologies(C#)
April 2011
|
July 2024

Twitter:@neuecc GitHub:neuecc

Archive