回数サンドイッチ
- 2009-03-07
class Program
{
static void Main()
{
// 何でもいい配列
var array = Enumerable.Repeat("a", 10);
// foreachで素直っぽくindexを付ける
foreach (var item in array.Select((value, index) => new { value, index }))
{
Console.WriteLine("{0}:{1}", item.index, item.value);
}
// 拡張メソッドにしちゃう
foreach (var item in array.WithIndex())
{
Console.WriteLine("{0}:{1}", item.Key, item.Value);
}
// foreachで使う場合は普通に外側にcount用の変数置いた方が……
// indexの取れない拡張メソッド(ToLookupとか)へのチェイン時には便利に使えるかも
var splittedArray = array.WithIndex().ToLookup(kvp => kvp.Key < 5, kvp => kvp.Value);
}
}
public static class ExtMethods
{
public static IEnumerable<KeyValuePair<int, T>> WithIndex<T>(this IEnumerable<T> source)
{
int index = 0;
foreach (var item in source)
{
yield return new KeyValuePair<int, T>(index++, item);
}
}
}
最近は、どこにでもあるゲーム雑感サイト→どこにでもあるプログラミング雑感サイト、に無理矢理シフトしようとして浮ついた無理無理感が漂っていますが、別に特にシフトしたいわけでもなく、せっかくなので3月は集中的に書いてみようかな、と思っただけですが空気的に不評な雰囲気を感じちゃってたりしなかったりは、とりあえず無視黙殺で進めようかと思うこの頃ですがいかがお過ごしでしょうか。
前々回、前回からまだ続いて、Selectでindexを取る話。[C#]何度目の動きを見て、便利便利と思ったので簡単に使えるように拡張メソッドに放り込んでみた。KeyValuePairなんて名前じゃなくてIndexValuePairが良いんですが(笑) あるものは、そのまんま使うということで。
それ自体は別にforeachの外側にカウント用の変数置けばいいぢゃーん、という気がしなくもなくて困ったので、言い訳として、幾つかのメソッドがインデックス取れないから中間に挟む用として便利!と思うことにした。SelectとかWhereとか、大抵のものはFunc<T,int,TResult>も用意されているんだけどね。ToLookupとか、一部のものには無いんだね。
C# LINQで左外部自己結合
- 2009-03-06
static void Main(string[] args)
{
// 連続して同じ値が来る箇所だけを省いて取得する
// この場合だと1,2,4,3,4,0が取れることを目指す
int[] array = { 1, 2, 4, 4, 3, 3, 4, 0, 0 };
var arrayWithIndex = array.Select((value, index) => new { value, index });
var result =
from orig in arrayWithIndex
join alias in arrayWithIndex on orig.index equals alias.index - 1 into _
from alias in _.DefaultIfEmpty()
where alias == null || orig.value != alias.value
select orig.value;
}
前回に引き続いてindex生成してゴニョゴニョするネタを考えたい。というわけで、SQL的な自己結合。これで前後の値との比較が可能になるわけですね! 自己結合自体は普通にjoinで同じソースを置くだけ。例では、「連続して同じ値が入っている箇所」を省くため、インデックスを1つずらして結合。ただ、普通に結合すると最後の値が無いので、結合から抜け落ちてしまう。というわけで、内部結合ではなく外部結合にする。外部結合はDefaultIfEmptyを使って、MSDNに記事があるのをそのまんまな方向で。
var list = new List<int>();
list.Add(array[0]);
for (int i = 1; i < array.Length; i++)
{
if (array[i] != array[i - 1]) list.Add(array[i]);
}
……でも結局、こんな例ならばList使った方が遥かにスッキリなのであった。意味ないねー。とても。少しはまともな例題が考え出せやしないものなのかしらん。
リストの分割
- 2009-03-03
// リスト自体は何だっていいので適当に生成
var list = Enumerable.Repeat("a", 50);
// 25以下をtrue、それ以上をfalseとするkeyでグループ分け
var result = list
.Select((value, index) => new { value, index })
.GroupBy(t => t.index <= 25, t => t.value);
// 多分、こんな感じに取り出す……?
var first = result.Single(g => g.Key == true);
var second = result.Single(g => g.Key == false);
2ch見てたら、要素数50のリストを0-25と26-50に分割するのどーするの、という質問が出てたのでごにゃごにゃ考えた。最初、list.GroupBy(i => i < 25);なんて答えたのだけど、即座に値じゃなくて添え字で分けたいんでしょーがボケ、と突っ込みが入ったので書き換えたのが↑のもの。微妙な長さと無駄手間感が何とも言えない、ただのLINQ遊びでしかない状態になってしまった。うーん。これはこれで面白いと思うのだけど。ただ、もう少し短くできないかな……?グループ化が挟まると、その後がどうしても長ったらしく。ていうか、あと、trueとfalseしかないんだから、First()とLast()でいいよね、という話ではある。
// 条件ばかりが無駄に多くて限りなく微妙
var alphabet = Enumerable.Range('A', 'z' + 1 - 'A')
.Where(c => c <= 'Z' || c >= 'a')
.GroupBy(c => c <= 'Z', c => (char)c);
// ていうか、意味ないよね、これ、全く
var upper = alphabet.First();
var lower = alphabet.Last();
何だか悔しいので、もう少し考えてみた。例えばアルファベットの大文字と小文字とか!……意味ない。死ぬほど意味がない。少なくとも、これだけを考えるならEnumerable.Range('a', 26).Select(i => ((char)i).ToString());でいいし。ダメだこりゃ。
追記
あ、違う、こういう場合はGroupByじゃなくてToLookup使えばいいんだった。GroupByと違ってキーでアクセス出来るようになるから、SingleだのFirstだのといった微妙なアクセスじゃなく(しかも、これらを使うと毎回走査してるってことだよね!) result[true]とかalphabet[true]とかでアクセス出来る。これで遙かにスッキリ。Lookupは素晴らしい。けど、ついつい忘れてしまう。
var result = list
.Select((value, index) => new { value, index })
.ToLookup(t => (t.index <= 25) ? "以下" : "より上", t => t.value);
var ika = result["以下"]; // こんな感じで
var tako = result["より上"]; // 取り出せるわけです!
これならすっきり。めでたしめでたし。キーは日本語でもboolでもenumでも好きなモノを使うといいさあー。
追追記
さっそく Enumerable.ToLookup が役立った - NyaRuRuの日記
つまりこういうことなのであった。間違いなく昔読んだはずなのに、すっかり忘れていたという事実が何よりも悲しい。あと、ToLookupで検索しても全然引っかからないというのが、限りなく人気無いメソッドのようで悲しい。私も忘れてたけどね!
C#とLINQでFizzBuzz
- 2009-02-28
using System;
using System.Collections.Generic;
using System.Linq;
class Program
{
static void Main()
{
// FizzBuzzの無限リスト
var FizzBuzz = Enumerable.Range(0, int.MaxValue).Select(i =>
(i % 15 == 0) ? "FizzBuzz" :
(i % 3 == 0) ? "Fizz" :
(i % 5 == 0) ? "Buzz" :
i.ToString());
// Skipが起点、Takeが表示数に対応する
FizzBuzz.Skip(1).Take(100).ForEach((s, i) => Console.WriteLine("{0,3}:{1}", i + 1, s));
}
}
public static class ExtensionMethods
{
// indexが取れるForEach
public static void ForEach<T>(this IEnumerable<T> source, Action<T, int> action)
{
int index = 0;
foreach (var item in source) { action(item, index++); }
}
}
何だかふと思って、今更ながらにanarchy golf - FizzBuzzをやってた。とりあえずC#でSize 123で同率一位達成なので満足です。以上。で終わるのもアレなので、記念にFizzBuzzをきちんと書くならどうなるかなあ、と考えてみた。
C#3.0だとLINQを使うのがきっとスマートです、よね。無限リストを作ってやるのが一番、かな。Skipがstart、Takeがcountに相当するってのが何だか面白い。しかし毎度ながらEnumerable.Range(0,int.MaxValue)という表記が冗長に過ぎてげんなり。あとIEnumerableにForEachが用意されてないのもなあ。ToList()は色々と違うと思うので、本当に何とかして欲しい。
UbiquityとJavaScript
- 2009-01-14
あまりの酷いJavaScript素人っぷりに嫌気がさしたので、JavaScript 第5版を読みました。とりあえず9章まで。というわけで、それをもとにして書きなおしてみました。functionがコンストラクタなんだねー、とかprototypeとか。そんな感想を反映。前よりは遥かにマシ、だとは思います。
んで、機能的には大幅アップです。modeを確定させる前は全部のモードから表示。modeを確定させたら、前回はプレビュー領域が余りまくりで勿体なかったので、前後3人のスコアを同時表示に変更。これでわりと実用的になってきたんじゃあないかと思われますがどうでしょう?若干、というかかなり動作が怪しいー通信しすぎー、一覧表示でてこねーぞごるぁー、ってところがあるので、追々直します。
ジオメトリランキング表示Ubiquityコマンド
- 2009-01-13
ちょっと前からゴニョゴニョと弄り中のジオメトリウォーズ2のランキング。実用的なものを、と考えた結果、FirefoxのアドオンUbiquityのコマンドに仕上げてみました。Ubiquityをインストールした状態で上記GitHubのサイトに飛ぶと上部にSubscribeするかどうかの情報バーが出るので、Subscribeすればコマンドのインストールが完了します。
使い方
Ubiquityを起動し(デフォルトではCtrl+Space)「gwre2 GAMERTAG mode MODE」とコマンドを入力するとプレビュー領域にランキングとスコアが表示され、そのままエンターキーを押すと、テキストキャレットの位置にスコアが挿入されます。例えば「gwre2 SeaWeeze mode Deadline」と入力すると「Rank:143|Score:45218795」がプレビュー領域に、エンターを押すことで「45218795」が挿入される、というわけです。
入力補完が効くので実際の入力は「gw SeaWeeze mode d」だけでOKです。また、modeを入力すると下にその他の候補が出るので、下カーソルを押すことでスムーズに他のモードのランク/スコアを見ることが可能です。残念ながら今のところUbiquityの仕様で5つまでしか出せませんけど(モードは6つあるので一つ足りない、ので基本的にSequenceが場外)
ネットを見ててゲーマータグがあると、この人のジオメトリのスコアはどのぐらいだろうなあ、とかすぐ思ってしまう人にお薦め。定期的に自分のスコアを張り付けている人にもお薦め。もうメモらなくても済む!
コード品質
ソースコード丸見え!見ちゃいやあ。そうです、品質最悪です。実のところjavascriptが全然分からないのです。そしてjQueryは初めて触った、ので全く分からない。Ubiquityも初めて、当然さっぱり。分からないx3により酷いことになっています。いやあ、困った困った。誰か改良してくれれば……。というのはともかく、明らかに酷いのは分かっているので、ちょくちょくと改良していこうとは思っています。
modeを入力しない状態だとpreview領域に全部のスコアとランクを出す。というほうが便利ですよねー。ただそうなるとサーバーにリクエスト飛ばしまくることになるので(単純にx6というだけじゃなくて、ゲーマータグの確定前にもリクエストが飛ぶので、x15ぐらいになりそう)どうかなー、と思ったんですが、どーなんでしょうかね。やっぱ不便なので、全部のスコアとランクが出た方がいいには違いないんですけどねえ。
リーダーボード分解
- 2009-01-09
static void Main(string[] args)
{
var service = new XBLLeaderboardSvc.ServiceSoapClient("ServiceSoap");
var result = service.getLeaderboardNearGamertag(2, 4, "SeaWeeze", 10)
.Skip(1) // 最初の1個目は値の個数なのでスキップ
.Select((value, index) => new
{
value = value,
index = index / 3,
attr = (index % 3 == 0) ? "Rank" :
(index % 3 == 1) ? "Tag" :
"Score"
})
.GroupBy(t => t.index, t => new { t.value, t.attr })
.Select(g => new
{
tag = g.First(t => t.attr == "Tag").value,
rank = int.Parse(g.First(t => t.attr == "Rank").value),
score = int.Parse(g.First(t => t.attr == "Score").value)
});
}
今年はプログラミング分を強化したいので、コードも張り付けていこう。勿論恥ずかしいけれど、照れてても成長に繋がらないと思うので、積極的に行きたいです。言語は基本的にC#3.0。というわけで、前回、GeometryWarsのLeaderboardの値を取得するの続き。ただの文字列配列で帰ってくるので分解しなきゃねー、ということで分解、まで。この後の予定は未定。ゆったりとSilverlightで簡単なグラフでも作ってから本格的に考えようかな―、と思っています。
サービス参照を追加するだけで、メソッド一発(getLeaderboardNearGamertag())で軽く取得出来る。これは楽ちん。配列はLINQを使って分解することにしました。3つ区切りなので3で割ってインデックスと属性振ってグループ化してタグから匿名型作って……。何かイマイチ。attrが野暮ったい。この辺、何とかしたい気はとてもする。
と、進捗的にはつまり全く進んでないということですね! いや、ここずっとexception conflictやってるし……。
GeometryWars Leaderboard API
- 2009-01-05
Xbox360がネットランキングを標準搭載したように、次世代XboxはWeb上からランキングデータを取得できるAPIを標準公開して欲しい。いや、公開すべき。そうすれば、あんなこともこんなことも、夢が膨らむ。残念ながら現行世代のXbox360はム・リ。ではあるものの、最近ではウェブ上からランキングを参照出来るゲームも多くなってきた、ということは需要の後押しがある、ということで次世代Xboxでの搭載は夢ではなさそう。HALO3のマッチングシステムと共に標準搭載の勢いでよろしくお願いしたい。
で、本題。ジオメトリ2もメーカー側のランキングや、ランキング付きゲーマーカードのようにウェブ上に公開可能な仕組みは用意されていた、けど、一般人が弄れるAPIは公開されていなかった。しかしBizarre Creationsはついにやってくれた。さらっとBBS上に公開したよー、とのお知らせが!
というわけで、そのBizarre CreationsのXBLAタイトル用のLeadboard取得WebServiceの解説。呼び出し方法が数字直打ちで分かりづらければ、帰ってくる値も素の配列という分かりづらさなので、私が調べた範囲で簡単に解説します。いや、数字ぽてぽて打って確認するだけで別に手間でもなんでもないですけどね。
getLeaderboardは順位のランキング。Overallって奴ですね。パラメータはgameIdとleaderboardIdはあとで解説します。rankToStartで開始する順位を、numToShowで取得する量を決めます。残念ながらrankToStartは901まで、numToShowも有効なのは100まで、ということで1000位までしか取得できません。十分ですけど。
getLeaderboardNearGamertagは任意のゲーマータグの人の前後のランキングを取得する。gamerTagはゲーマータグ、numToShowは前後の量。こちらのnumToShowは有効な値は101までになっています。自分の前に50人+自分の後ろに50人、ということですね。
さて、gameIdですけど、0はGWRE1、1はBoomBoomRocket、2はGWRE2。GWRE1のleaderboardIdは、0がEVOLVED、1がRETRO。BoomBoomRocketは割愛(笑) GWRE2は、0=Deadline,1=Evolved,2=Sequence,3=Waves,4=Pacifism,5=Kingでした。変な並び順ですけど気にしないことにしましょう。いや、めっちゃ気になるけど。
で、取得できるxmlは全部stringというそっけなさ。一番目が取得したデータの個数、それ以降は3つ区切りで「順位、ゲーマータグ、スコア」になっているようです。
ついでにRESTでの取得。http://www.bizarrecreations.com/XBLLeaderboardSvc/Service.asmx/getLeaderboardNearGamertag?gameId=2&leaderboardId=4&gamerTag=SeaWeeze&numToShow=11、このURLはgameIdが2なのでGWRE2、leaderboardIdが4なのでPacifism、gamerTagがSeaWeezeなので、まあ、私ですね、で、numToShowが11なので前後5人ずつのランキングが取得出来ます。
いやあ、素晴らしいですね。色々と素っ気なさ過ぎじゃね?って気がしなくもないですが、それが非公開のサービスを一般向けに公開してあげる、という優しさに感じられて嬉しかったりして。実際問題、こんなに大盤振る舞いで公開してくれている会社なんて無いですからねえ。今後もBizarreに続いてくれると(Bungieぐらいかな、もしやるとしても)嬉しいし、その勢いで次世代Xboxでは公開が当然、みたいな形になってくれれば、なお嬉しい。
そのためにも、せっかく公開してくれたこのサービスを使って何かを作らなければ!と思うんだけど、いざ何かっていうと何も思い浮かばない。プログラム流用で、ランキングが100落ちる度にTwitterに投稿するツールとか、どうでもいいネタしか浮かばない。というわけで、何かアイディアがあればください。