Rubyで階層の深いHashを手軽に作成する
元ネタ
階層の深いHashを作成するときに知っておいたら便利なこと - (゚∀゚)o彡 sasata299's blog
#!/usr/bin/ruby hash = Hash.new { |h,k| h[k] = {} } hash["foo"]["bar"] = 1 p hash # {"foo"=>{"bar"=>1}}
元ネタで紹介されているのは上記の方法ですね。
2階層の場合はこれで問題ないんですが、三階層以上の場合にエラるんですよね。
#!/usr/bin/ruby hash = Hash.new { |h,k| h[k] = {} } hash["hoge"]["piyo"]["neko"] = 3 # base.rb:4:in `<main>': undefined method `[]=' for nil:NilClass (NoMethodError)
とうことで
何階層でも自動生成してくれる方法を考えてみました。
基本的な考え方は一緒で、生成部分をProcにすることで毎回呼ぶように変更した感じ。
proc_hashと変数を作らないといけないのがちょっと難点。変数なくせないかlambdaやら試してみましたが上手く行かなかったです。残念。
proc_hash = Proc.new { |h, k| h[k] = Hash.new &proc_hash; } hash = Hash.new &proc_hash hash["foo"]["bar"] = 1 p hash # {"foo"=>{"bar"=>1}} hash["a"]["b"]["c"] = 5 p hash # append {"a"=>{"b"=>{"c"=>5}}} hash["neko"]["inu"]["saru"]["kiji"] = "momotarou++" p hash # append {"neko"=>{"inu"=>{"kiji"=>{"saru"=>"momotarou++"}}}}
caolan/asyncを使ったNode.jsのフロー制御
JavaScript Advent Calendar 2011 (Node.js/WebSocketsコース)の16日目です。
@koichikさんの13日目の記事にも書かれているように、Node.jsにて非同期処理を管理する方法には以下の2種類があります
- 非同期処理の呼び出し時にコールバック関数を与える。処理が終わったらエラーの有無と、結果が通知される。
- EventEmitterに対してon/onceでEventListenerをセットして非同期処理を実行する。非同期処理の実行によって発生したイベントやエラーはリスナーに通知される。
後者の説明はkoichikさんの記事にお任せして、この記事は前者を扱うライブラリcaolan/asyncについてみていきたいと思います。
caolan/asyncって?
https://github.com/caolan/async で公開されているフロー制御ライブラリです。
自分はNode.jsで使っていますが、ブラウザ上で動くJavaScriptを書く場合にも使用できるようです。
https://github.com/joyent/node/wiki/modules#wiki-async-flowに記載されているNode.js用フロー制御モジュールは既に50を超えていますが、試してみた中では一番使いやすいモジュールでした。
# さすがに50すべては試せていないです。githubのwatcher数1000オーバー、folk数62ということで少なくともフロー制御モジュールの最大手ではあるはず。
asyncが実現してくれるフロー制御の一部はまさに「非同期プログラミングの改善」のエッセンスの内容なので、先に読んでおくと理解が深まるかと思います。
何をしてくれる?
asyncがやってくれる処理には様々なものがありますが、まずはasync#seriesを例にして説明していきたいと思います。
hogeディレクトリを掘って、piyoディレクトリを掘って、その下にデータを書きだすコードをコールバックを使って書くと以下のようになります。
(ディレクトリ堀は再帰的にやってくれるモジュールがありますが見なかった方向で)
var fs = require('fs'); fs.mkdir('./hoge', function(err) { if(err) { // エラー処理 return; } fs.mkdir('./hoge/piyo', function(err) { if(err) { // エラー処理 return; } fs.writeFile('./hoge/piyo/neko', 'Hello Node.js', function(err) { if(err) { // エラー処理 return; } console.log('success'); }); }); });
この例のように、非同期処理の終了を待ってから次の処理をさせる場合、ネストが深くなる、エラー処理があちこちに分散するといった問題点があります。
では、このコードをasyncを使った形で書き直してみましょう。
var fs = require('fs'); var async = require('async'); var tasks = []; tasks.push(function(next) { fs.mkdir('./hoge', next); }); tasks.push(function(next) { fs.mkdir('./hoge/piyo', next); }); tasks.push(function(next) { fs.writeFile('./hoge/piyo/neko', 'Hello Node.js', next); }); async.series(tasks, function(err, results) { if(err) { // エラー処理 return; } console.log('success'); console.log(results); // [undefined, undefined, undefined] });
はい、エラー処理が一カ所にまとまりました。
ネストが深くなることもなく、処理もtasksにまとまっていて良い感じです。
async#seriesは第一引数で与えられたtasksの処理を順番に実行してくれるメソッドです。
tasksに格納された各処理は、自分の処理が終わった時点でnext()を呼び出し、「エラーの有無」と「自分の処理結果」を通知します。
エラーが発生しなかった場合には自動的に次の処理が呼び出され、 エラーが発生した場合には、それより先の処理は実行されずにasync.seriesの第二引数に渡された関数が呼び出されます。
errには発生したエラー(エラーなしの場合はnull)が入り、resultsには各処理の処理結果がまとめて入ります。
今回使ったfs#mkdirやfs#writeFileはエラーの有無のみを通知する為、resultsに関しては次項の例を見てもらった方が分かりやすいかと思います。*1
async#series, async#parallel
asyncモジュールにはseries以外にも様々なメソッドが用意されています。
ここからは、それらの処理の流れを図で表しながら簡単に説明していきたいと思います。
まずはasync#seriesです。
先ほど説明した通り、一つの処理が終わってから次の処理が行われ、それぞれの処理結果がendに集約されています。
凡例:
・青い四角:処理
・赤い四角:データ
・青線:処理の流れ
・赤線:データの流れ
・finish:async#****を呼び出すときに与えられるcallback関数
(エラー発生時か、全部の処理が終わった後に呼ばれる関数)
# 図はCacooにて作成。Excelは図を描くツールではないのです。
つづいて、async#parallel。こちらは全部の処理が同時に実行され、すべてが終わった時点でendが呼ばれます。
この場合もseriesと同じく、それぞれの処理結果はendに集約されています。
async#seriesとasync#parallelの違いを見るために「delayミリ秒待ってからtextの内容を出力し、処理結果としてresultを返す関数」を作って試してみます。
function delayedPrint(delay, text, result) { return function(next) { setTimeout(function() { util.log(text); next(null, result); }, delay); } } var tasks = []; tasks.push(delayedPrint(3000, 'test1', 'result1')); tasks.push(delayedPrint(2000, 'test2', 'result2')); tasks.push(delayedPrint(1000, 'test3', 'result3'));
async#seriesの場合
util.log('start'); async.series(tasks, function(err, results) { util.log('true end'); console.log(results); }); util.log('end? ... NO. while processing.');
実行結果:
16 Dec 01:39:13 - start 16 Dec 01:39:13 - end? ... NO. while processing. 16 Dec 01:39:16 - test1 16 Dec 01:39:18 - test2 16 Dec 01:39:19 - test3 16 Dec 01:39:19 - true end [ 'result1', 'result2', 'result3' ]
async#parallelの場合
util.log('start'); async.series(tasks, function(err, results) { util.log('true end'); console.log(results); }); util.log('end? ... NO. while processing.');
実行結果:
16 Dec 01:40:15 - start 16 Dec 01:40:15 - end? ... NO. while processing. 16 Dec 01:40:16 - test3 16 Dec 01:40:17 - test2 16 Dec 01:40:18 - test1 16 Dec 01:40:18 - true end [ 'result1', 'result2', 'result3' ]
これらの結果から以下のことが分かります。
- end?の時点ではまだ処理が終わっていない。全部の処理が終了したのはtrue endの時点。
- startからendまでの時間がseriesでは直列実行の為、3 + 2 + 1 = 6秒
- startからendまでの時間がparallelでは並列実行の為で、max(3, 2, 1) = 3秒
- parallelの例を見るとわかるようにresultsに入ってくるのは処理の完了順ではなくtasksの順
async#waterfall
async#waterfallを図に表すと以下のようになります。
処理の流れ自体はseriesと同等ですが、seriesと違い、処理結果が次の処理に与えられます。
便利なメソッドであることは確かなのですが、このメソッドに関しては気になっていることがある為、後日改めてエントリを起こそうと思います。
async#forEach, async#forEachSeries
前項で紹介したseries, parallel, waterfallは複数の処理の流れを制御する形でしたが、配列の各要素に対して同じ処理を実行することも良くあります。
同期処理であればArray#forEachを使えばよいのですが、その中に非同期処理が含まれている場合、Array#forEachから処理が返った時点では配列の各要素に対する処理が終わっていません。
asyncではこういった場合に使えるメソッドとしてforEach, forEachSeriesというメソッドを用意しています。
名前からもわかるようにforEachは並列処理を行い、forEachSeriesは直列に処理を行います。
async#map, async#mapSeries
forEachやforEachSeriesは配列の各要素に対して処理を行いますが、処理結果を返す機能は持っていません。
配列の要素を変換したい場合などにはArray#mapと同じく、async#mapを用います。
これもforEachと同じくasync#mapとasync#mapSeriesの二つが用意されているため、変換ロジックの重さに応じて好きな方を選ぶことができます。
ほかの関数
今回は紹介しきれませんでしたが、caolan/asyncには他にも以下のようなメソッドを提供しています。
- makeファイルのように各タスクに前提条件を指定すると、自動的に順番を組み立ててくれるauto
- forEach/mapのようにArrayが提供していた処理をやってくれるfilter, filterSeries, reject, rejectSeries, reduce, reduceRight, concat, concatSeries, some, any, every, all
- 条件を満たす間 or 満たすまで処理を続けるwhilst, until
- 並列で実行する最大数を指定して大量のタスクを処理させられるqueue
- 重い処理の処理結果を自動的にキャッシュして二回目以降の呼び出しを軽くするmemoize
これらのメソッドとEventEmitterをうまく使うことで処理の流れを整理し、処理の流れが分かりやすいコードを目指しましょう!
明日は@n_matsuiさんです。
*1:例選びに失敗した感がひしひしと
MacBookAirでWindowsを使う場合のキー設定
MacBookAirにBootCampでWindowsを入れて使っていますが109キーボードとは形状がことなる為、特殊キーの入力方法がややこしかったり、違和感があったりします。
自分が設定した内容をまとめてみました。
特殊キーの入力方法
まずは押し方次第で入力できる特殊キーの一覧。
Pause, Insert, PrintScreen, ScrollLockあたりは一風変わったキー入力ですね。
Windows環境のキー(仮想キーコード) | MBAのキー入力 |
---|---|
Backspace (0x08) | delete |
Pause (0x13) | fn + esc |
無変換 (0x1D) | 英数 |
ひらがな | かな |
Insert (0x2D) | fn + enter |
Delete (0x2E) | fn + delete |
左Win (0x5B) | 左command |
右Win (0x5C) | 右command |
IME ON/OFF | caps |
PageUp (0x21) | fn + ↑ |
PageDown (0x22) | fn + ↓ |
Home (0x24) | fn + ← |
End (0x23) | fn + → |
PrintScreen (0x2C) | fn + 左shift + F11 |
ScrollLock (0x91) | fn + 左shift + F12 |
Ctrl + Alt + Delete | fn + control + alt + delete |
キーレイアウトの変更
Windows環境の場合、以下のレジストリを変更することである程度キーレイアウトを変更することができます。
\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Keyboard Layout\Scancode Map
ChangeKeyというフリーソフトでも変更可能です。このアプリが中でやっていることはレジストリの変更なので「管理者として実行」で起動する必要あり。
自分の場合、普段使っているのが91キーボードなので、こんな風にレイアウトしてみました。
- 「Altキー」 → 「左Winキー」(交換)
- 「左Winキー」 → 「Altキー」(交換)
- 「右Winキー」 → 「アプリケーションキー」
- アプリケーションキー(右クリック同等)を入力する方法がShift + F10*1しかなかったので、使わない右Winキーを潰して割り当て
Googleで一文字検索してみた。(2011年版)
ふと思いついたので2008年にやった一文字検索を再度動かしてみた。
結果
雑感
- 3年前のスクリプトがそのままでも動くGoogleすごい。class="l"とか1文字なので心配していたけど大丈夫だった
- cinwz辺りを見ると単一文字の商品名でも一位をとれるみたい。SEOの成果?
- hはやっぱりジャンル的には変わらないらしい
- sは引き続き静岡
- yがYahooオークションからYahooJapanへ
- 人生オワタの一位オワタ\(^o^)/
- これのURL、http://ja.wikipedia.org/wiki/\(^o^)/だったのな。
ソース
require 'rubygems' require 'hpricot' require 'open-uri' require 'jcode' query = "abcdefghijklmnopqrstuvwxyz"; query.each_char do |letter| url = "http://www.google.co.jp/search?q=#{letter}&lr=lang_ja" doc = Hpricot(open(url).read) result = (doc./"//h3.r/a.l")[0] puts "<tr><td>#{letter}</td><td><a href=\"#{result[:href]}\">#{result.inner_text}</a></td></tr>" end
素のままでも動いたけど、Youtubeの別枠扱いをなくすためにXPathを変更
今ならhpricotではなくNokogiriとかかなぁ。
Ruby製バウリンガル
エイプリルフールも終わったのでわんわんをデコードするRuby製バウリンガルを載せてみる。
$KCODE = 'SJIS' require 'zlib' decode_table = { 'わん' => 0, 'きゃん' => 1, 'わおん' => 2, 'わーん' => 3, 'ばう' => 4, 'きゃうん' => 5, 'わう' => 6, 'きゅーん' => 7 } encoded_text = ARGF.readlines.join.gsub(/[\r\n。!]/, "") regexp = Regexp.new(decode_table.keys.join("|")) octets = encoded_text.scan(regexp).map {|octet| decode_table[octet]} compressed_text = octets.each_slice(3).map {|octet| octet.join.to_i(8) }.pack("C*") $>.write(Zlib::Inflate.inflate(compressed_text))
東京国際アニメフェアのチラシをCanvas使って編集してみる
Twitterを見ていたらRTでこんなのが流れてきたわけですよ。
だれか探偵もの漫画の人物紹介ページみたいに出店しなくなったアニメをシルエット表示に変えてくれないかな。 URL
2010-12-10 18:49:38 via web
最近Canvas使ってないなー。と思ったのでカリカリとBookmarklet書いてみました。
使い方
Firefox3.6.12とChrome8.0.552.215で動作確認。
1. Hatena::Letを開きます
2. 「bookmarklet:東京国際アニメフェアの開催告知チラシから参加拒...」をドラッグしてお気に入りに放り込みます。
3. 公式チラシ | 東京国際アニメフェア2011を開きます
4. 2番で追加したお気に入りを選択するとこんな感じになります。
しかし、最近の作品がわからない・・・
2010/12/11 01:58追記:
http://twitpic.com/3elscd にすごく分かりやすい画像があったので使わせてもらいました。
金星探査機あかつきの現状まとめ
12月7日22時の記者会見のまとめが主、手元にまとめたテキストをコピペ
時間のズレとかあるかもー 08時49分 軌道制御エンジン噴射開始 08時50分43秒 通信断絶開始(予定通り) 09時01分00秒 軌道制御エンジン噴射終了 09時12分03秒 通信再開予定だったが通信再開せず 10時03分 探査機側で通信アンテナをLGAに自動切り替え 10時28分 衛星のLGAからの通信が受信される MGAに切り替えるよう、衛星にコマンドを送信 コマンドは送信でき、MGAに切り替えが出来た 10時59分 MGA通信確立できず 11時29分 通信確立できなかったので、衛星側でLGAに自動切換 14時00分 スペインマドリッド局にて補足、LGAから受信、強さ変化確認 0.1rpm(10分で1周の周期)で回転していることが確認される セーフホールドモードと考えられるがこの時点では未確定 MGAに切り替えるように地上から地上からのコマンド 10分に1回、40秒受信可能 17時00分 回転停止コマンドを送信したが停止せず、セーフホールドモード継続 この際止まらなかったのはコマンドが衛星に届いていないと推定されている 衛星の軌道が本来と違う為に、アンテナがズレていた 21時00分 スペインマドリッド、アンテナ方向予測して修正 MGAでも通信できたが安定しない為、LGAで通信してテレメトリデータを受信中 現状: セーフホールドモードに入っているのは確定 LGAにてテレメトリの受信(22時〜24時ぐらい) 軌道は不明 トラブルなければ明日の昼までに採れるはずの情報 HouseKeeping情報は分かる 軌道決定もできる・・と良いな 参考: 地上の光回線 100000000 bps(100Mbps) HGA 256000 bps(256kbps)? MGA 512 bps LGA 8 bps