Rubyのtapはメソッドチェーンだけのものじゃない!
あるインスタンス変数にオブジェクトがセットされているときに、その参照を外しつつそのオブジェクトを返すというメソッドが必要になったんだよ。次のような感じだよ。
@name = nil #at initialize def set_name(name) @name = name end def reset_name end set_name('Charlie') # => "Charlie" reset_name # => "Charlie"
ここでreset_nameしたときに、インスタンス変数にセットされてたオブジェクトを返したいんだ。なんかよくありそうだよね。
こんなときは普通reset_nameを、次のように書くと思うんだ。
def reset_name name = @name @name = nil name end set_name('Charlie') # => "Charlie" reset_name # => "Charlie"
でもなんかスマートじゃないよね。
で、少し考えたんだけど、多重代入を使うともう少しマシになるんだ。
def reset_name name, @name = @name, nil name end set_name('Charlie') # => "Charlie" reset_name # => "Charlie"
でもやっぱり返り値を確保するためだけにローカル変数を用意しなきゃならないなんて、なんかイケてないよね..
そこでObject#tapの出番ですよ奥さん..
def reset_name @name.tap { @name = nil } end set_name('Charlie') # => "Charlie" reset_name # => "Charlie" @name # => nil
tapはそのブロックを評価するけれどもその結果を捨ててしまうという、謙虚な変わり者のメソッドだよ。
なかなかイケてると思うんだけど、どうかな?周知のTipsだったらごめんね。
Rubyのエニュメレータ内での破壊行為は止めてください!
RubyのArrayにはrotate!という便利なメソッドがあるよ。このメソッドは文字通り配列の要素をローテートするんだ。
a = [1,2,3] a.rotate! # => [2, 3, 1] a.rotate! # => [3, 1, 2] a # => [3, 1, 2]
メソッド名の最後に!(ビックリマーク)があるから、これは元のオブジェクト自身を変えるよ。
昨日僕はこのrotate!メソッドにおけるローテートの過程を取りたいと思ったんだよ。で、次のようなコードを書いてみたんだ。
a = [1,2,3] 3.times.map { a.rotate! }
そうしたら期待したものとは違う、次のような結果が返ってきたんだ。
# => [[1, 2, 3], [1, 2, 3], [1, 2, 3]]
あれ?mapがいけないのかな..
q = [] 3.times { q << a.rotate! } q # => [[1, 2, 3], [1, 2, 3], [1, 2, 3]]
ローテートしてないのかと思って、ブロック内でpしてみたらちゃんとしてるんだよ。
a = [1,2,3] 3.times.map { p a.rotate! } # => [[1, 2, 3], [1, 2, 3], [1, 2, 3]] # >> [2, 3, 1] # >> [3, 1, 2] # >> [1, 2, 3]
なんか変だな..
で、少し考えたら理由がわかったんだ。Array#rotate!はselfを返すんだったよ。
a = [1,2,3] a.object_id # => 2151892940 3.times.map { a.rotate!.object_id } # => [2151892940, 2151892940, 2151892940]
つまりmapの返り値はa.rotate!のスナップショットの配列を返すんじゃなくて、元オブジェクトの参照の配列を返すんだよ。で、mapの返り値はすべての要素に対するイテレートが終わってから返されるから(当然だよね)、その時点つまり最後のa.rotate!の後における元オブジェクトの状態がすべての配列の要素として返されることになるんだ。
つまりこれは次のコードと同じようなことなんだよ。
a = [1,2,3] b = a.rotate! c = a.rotate! d = a.rotate! [b, c, d] # => [[1, 2, 3], [1, 2, 3], [1, 2, 3]]
だからスナップショットつまり途中経過がほしい場合は、さっきみたいにpしたり、to_sしたりdupしたりする必要があるんだね。
a = [1,2,3] 3.times.map { a.rotate!.to_s } # => ["[2, 3, 1]", "[3, 1, 2]", "[1, 2, 3]"] a = [1,2,3] 3.times.map { a.rotate!.dup } # => [[2, 3, 1], [3, 1, 2], [1, 2, 3]]
同じことはほかのRubyの破壊的メソッドでも起きるよ。
s = "hello, world!" s.size.times.map { p s.chop! } # => ["", "", "", "", "", "", "", "", "", "", "", "", ""] # >> "hello, world" # >> "hello, worl" # >> "hello, wor" # >> "hello, wo" # >> "hello, w" # >> "hello, " # >> "hello," # >> "hello" # >> "hell" # >> "hel" # >> "he" # >> "h" # >> ""
うっかりしてるとまたミスしそうだよ。分かってる人には当たり前のことなんだろうけど、僕はちょっと嵌っちゃったから書いてみたよ :)
だからビルのエレベーター内での危険行為はもう止めようよ!
だからRubyのエニュメレータ内での破壊行為はもう止めようよ!
Rubyで英文小説をWordleしようよ
Wordleって知ってる?Wordleはテキスト中の単語をグラフィカルに配置して、表示するツール/サービスだよ。
Wordle - Beautiful Word Clouds
例えばProject Gutenbergから、「Alice's Adventures In Wonderland」を取ってきて、Createページのテキストボックスにこれを貼りつければ、こんなものができるんだよ。
すばらしいよね!Wordleではテキスト中の単語の出現頻度に応じて、文字の大きさを調整してるよ。加えてRandomizeボタンを押したり、FontやLayoutやColorを変えたりすることで、全く違ったイメージのWordleが作れるよ。
Wordleのアルゴリズムについては、「Beautiful Visualization」というデータビジュアライゼーションの本に解説があるよ。
Wordleはすばらしいんだけど一点だけ不満があるよ。それはその単語の大きさがそのテキストの特徴を、必ずしもうまく表していないことだよ。つまりWordleではストップワードの除去が、あまりうまくいっていないんだよ。ちなみにストップワードはその言語で一般的に使われる語、例えばthe a forとかの非特徴的な単語のことだよ。先の結果を見ると、余り特徴的でない単語が並んでることがわかるよね。
で、以前にこのブログのチュートリアルで作った、WordDictionaryクラスを思い出したんだよ。
Rubyチュートリアル ~英文小説の最頻出ワードを見つけよう!(最終回) - hp12c
WordDictionaryクラスでは、他のテキストからなるベース辞書との比較で、対象テキストの特徴語を抽出できるんだったよ。試しにちょっと固めの小説をベースとして、アリスの特徴語を抽出してみるよ。
require_relative "word_dictionary" alice = "alices_adventures_in_wonderland.txt" bases = %w(english_literature.txt analyze_people_on_sight.txt) alice_wd = WordDictionary.new(alice) base_wd = bases.map { |base| WordDictionary.new(base, base) }.inject(:+) p alice_wd.uniq_words(40, base_wd) # >> [["alice", 403], ["turtle", 59], ["hatter", 56], ["mock", 56], ["gryphon", 55], ["rabbit", 51], ["mouse", 44], ["ve", 44], ["duchess", 42], ["tone", 40], ["dormouse", 40], ["cat", 37], ["march", 35], ["hare", 31], ["white", 30], ["replied", 29], ["caterpillar", 28], ["jury", 22], ["cried", 20], ["sort", 20], ["tea", 19], ["soup", 18], ["spoke", 17], ["sat", 17], ["talking", 17], ["garden", 16], ["hastily", 16], ["arm", 15], ["mad", 15], ["suppose", 14], ["didn", 14], ["anxiously", 14], ["dinah", 14], ["baby", 14], ["footman", 14], ["yes", 13], ["dodo", 13], ["cats", 13], ["wouldn", 13], ["dance", 13]]
なんかそれっぽい単語が抽出されたね
うれしいことにWordleのサイトでは、単語とその重み付けのリストを渡して、Wordleを作ることもできるんだよ(Advancedページ)。早々WordDictinaryで抽出したAliceの特徴語を使って、Wordleを作ってみたよ。
Alices Adventures In Wonderland (40 words)
Alices Adventures In Wonderland (100 words)
なんかいい感じだよね!他の小説でも試してみるよ。
The Adventures Of Sherlock Holmes
Wordleは楽しいから是非とも試してみて!僕が作ったWordleは次のURLで見れるよ。
http://www.wordle.net/gallery?username=merborne:title=Wordle - Gallery: merborne
Rubyでビックリ階乗を解こう! ~人間の実労時間を最適化する
「ビックリ階乗(Exclamatory Factorial)」って知ってますか?ええ、知るわけないです。なぜならいま僕が、次のツイートの解に命名したばかりの言葉だからです。*1
なかなか意味深なツイートですが、自分が先生だったらこの解答に◯を付けざるを得ないでしょう。答えにビックリマークを付ける人はいませんからね!
さて、プログラムする身としては文系理系両者の反応よりも、このような「ビックリ階乗」が、どれくらい奇跡的なものなのかが気になります。つまりa - (b / c) の解(先の例では24)と、(a - b) / c の解の階乗(4!)とが、一致する組み合わせは果たしてどれくらいあるのでしょうか。
数学的に書くとこういうことです。
そんな思いは当然僕だけではありませんでした。*2
「40 - 32 / 2 = 4!」 - エンジニアのソフトウェア的愛情
このブログより1000以下の数字で、15組のビックリ階乗があることがわかっています。ここで1000までの数を考えたときa b c の組み合わせ数は、10億通り()にもなりますからその確率は..
ビックリ階乗は奇跡的な組み合わせなんですね!
しかしそれにしても10億通りの組み合わせとなると、まじめにそのすべてを試してみると当然に、その実行時間が問題になります。先のブログでは試行錯誤して最初のプログラムから1800倍の高速化を実現して0.1秒以下で答えがでます。手元でRuby版も試して見ましたが0.037秒でした。ステキすぎます!
で、これ以上僕のできることは何も無いのですが、コードの実行時間というものを完全に無視してコードの読み易さ、つまり人間の実労時間の最適化という一点に焦点を合わせてRubyでコードを書いてみようと思います^^;
さて、ビックリ階乗の数式をもう一度確認します。
これをRubyの式に置き換えます。
f1 = ->a,b,c{ a - b / c } f2 = ->a,b,c{ (a - b) / c } a, b, c = 40, 32, 2 f1[a,b,c] == factorial(f2[a,b,c]) # => true
メソッドでもいいですが、ここでは一行で済むProcを使います。
このコードは一見よさそうですが、一部に問題があります。
10 / 3 # => 3
そうですRubyでは整数同士の除算は、余りを無視してしまいます。
しかしこの問題はrequire 'mathn' することで解決します。
require 'mathn' 10 / 3 # => (10/3)
次にfactorialメソッドってのがダサいですね。こうしましょう。
class Integer def ! (1..self).inject(:*) end end 4.! # => 24
require 'mathn' f1 = ->a,b,c{ a - b / c } f2 = ->a,b,c{ (a - b) / c } a, b, c = 40, 32, 2 f1[a,b,c] == f2[a,b,c].! # => true
良くなりましたね!
次にa b c についての10億の組み合わせを作ります。
set = [*2..1000].repeated_permutation(3) # => #<Enumerator: [2, 3, 4..]:repeated_permutation(3)>
そこから先の条件に見合うものだけセレクトします。
selected = set.select { |abc| f1[*abc] == f2[*abc].! }
結果をプリントします。
pp = ->abc{ print "(%i - %i) / %i = %i\n" % [*abc, f2[*abc]] print " %i - %i / %i = %i! = %i\n\n" % [*abc, f2[*abc], f1[*abc]] } selected.each { |abc| pp[abc] }
さあこれらを組み合わせて!
完成です! exclamation.rbで保存して、実行してみましょう! いきなり1000もなんですから、まずは[*2..100]から..
% time ruby exclamation.rb (25 - 5) / 5 = 4 25 - 5 / 5 = 4! = 24 (30 - 18) / 3 = 4 30 - 18 / 3 = 4! = 24 (40 - 32) / 2 = 4 40 - 32 / 2 = 4! = 24 ruby exclamation.rb 3.25s user 0.03s system 99% cpu 3.287 total
おおっ、良い感じじゃないですか!
では1000で..
% time ruby exclamation.rb ... ... ...
全く反応ありません^^;
仕方が無いので require 'mathn' はやめて、a <= b, (b % c) != 0, ((a - b) % c) != 0 の条件だけ入れて足切りします。
[*2..1000].repeated_permutation(3) .select { |a,b,c| next if a <= b || (b % c) != 0 || ((a - b) % c) != 0 f1[a,b,c] == f2[a,b,c].! } .each { |abc| pp[abc] }
いざ!
% time ruby exclamation.rb ... ...
ちょっとトイレ行ってきます..
% time ruby exclamation.rb ... ...
お茶飲んできます..
でましたよ!
(25 - 5) / 5 = 4 25 - 5 / 5 = 4! = 24 (30 - 18) / 3 = 4 30 - 18 / 3 = 4! = 24 (40 - 32) / 2 = 4 40 - 32 / 2 = 4! = 24 (138 - 108) / 6 = 5 138 - 108 / 6 = 5! = 120 (230 - 220) / 2 = 5 230 - 220 / 2 = 5! = 120 (721 - 103) / 103 = 6 721 - 103 / 103 = 6! = 720 (728 - 416) / 52 = 6 728 - 416 / 52 = 6! = 720 (731 - 473) / 43 = 6 731 - 473 / 43 = 6! = 720 (735 - 525) / 35 = 6 735 - 525 / 35 = 6! = 720 (748 - 616) / 22 = 6 748 - 616 / 22 = 6! = 720 (756 - 648) / 18 = 6 756 - 648 / 18 = 6! = 720 (765 - 675) / 15 = 6 765 - 675 / 15 = 6! = 720 (816 - 768) / 8 = 6 816 - 768 / 8 = 6! = 720 (833 - 791) / 7 = 6 833 - 791 / 7 = 6! = 720 (952 - 928) / 4 = 6 952 - 928 / 4 = 6! = 720 ruby exclamation.rb 349.56s user 0.72s system 99% cpu 5:51.87 total
6分!!!
使い捨てプログラムとしては許容できる範囲...
判断は各人にお任せします..
Rubyでアナグラムしようよ
アナグラム(anagram)をご存知ですか?アナグラムは単語や文の文字を入れ替えて、別の意味を持った単語や文を作る遊びです。例えば"note"には"tone"、"master"には"stream"というアナグラムがあります。
もちろん日本語アナグラムもあります。"タモリ"は"モリタ"のアナグラムです。"いきるいみなんて"と"みんないきている"は、一見哲学的問答に見えますが、これもアナグラムなんです:)
少しやって頂ければ分かりますが、アナグラムを見つけるのは意外と難しいです。試しに"friend" と"setter"と"resort"のアナグラムを、それぞれちょっと考えてみてください。(答え)*1
そんなわけで..
Rubyにアナグラムを見つけてもらいましょう
RubyでAnagramを作る
指定の英単語に対する、複数のアナグラムを見つけるプログラムを考えます。こんな感じです。
find_anagrams('name') # => ['mean', 'amen']
これを実現するには少なくとも次のステップが必要そうです。
- 英単語リストを用意する
- 指定単語のアナグラムを英単語リストから見つけ出す
指定単語のアナグラムを英単語リストから見つけ出す
単語リストはどこかにきっとあるでしょうから後回しにして、アナグラムを見つける方法を先に考えます。
先に示した"name"と"mean"と"amen"はアナグラムですが、どうやってRubyにそれを判断させればいいでしょうか。
いい方法を思いつきました。単語を文字で区切って配列化し引き算するんです。
w1 = 'name' w2 = 'mean' w3 = 'amen' w4 = 'man' ws1 = w1.split(//) # => ["n", "a", "m", "e"] ws2 = w2.split(//) # => ["m", "e", "a", "n"] ws3 = w3.split(//) # => ["a", "m", "e", "n"] ws4 = w4.split(//) # => ["m", "a", "n"] ws1 - ws2 # => [] ws1 - ws3 # => [] ws1 - ws4 # => ["e"]
空配列になったらアナグラムです!
と、言いたいのですがこれはダメです。
w1 = 'name' w5 = 'amenman' w1.split(//) - w5.split(//) # => [] w1 = 'aaabbbccc' w2 = 'abc' w1.split(//) - w2.split(//) # => []
引く配列要素が多かったり、重複要素がある場合は、期待する結果になりません。
実はいい方法があります。各単語のシグネチャーを作って、これを比較するのです。で、このシグネチャーは何かというと、単語の文字をソートしたものです。
w1 = 'name' w1.chars.sort.join.intern # => :aemn
アナグラムな単語は、このシグネチャーがおなじになるはずです。
やってみましょう。
w1 = 'name' w2 = 'mean' w3 = 'amen' w4 = 'amenman' sig1 = w1.chars.sort.join.intern # => :aemn sig2 = w2.chars.sort.join.intern # => :aemn sig3 = w3.chars.sort.join.intern # => :aemn sig4 = w4.chars.sort.join.intern # => :aaemmnn sig1 == sig2 # => true sig1 == sig3 # => true sig1 == sig4 # => false
いいですね!*2
ではこれをメソッド化しておきましょう。
def signature(word) word.downcase.chars.sort.join.intern end %w(name mean amen man).map { |word| signature word } # => [:aemn, :aemn, :aemn, :amn]
単語リスト
さて次に単語リストを用意します。ネットから拾う手もありますが、都合の良いことにMacの /usr/share/dict/ には最初から単語リストwordsが入ってるんです。
覗いてみます。
% head /usr/share/dict/words A a aa aal aalii aam Aani aardvark aardwolf Aaron % tail /usr/share/dict/words zymotoxic zymurgy Zyrenian Zyrian Zyryan zythem Zythia zythum Zyzomys Zyzzogeton
語数を調べましょう。
% wc -l /usr/share/dict/words 234936 /usr/share/dict/words
十分ですね。
アナグラム辞書
さて先の方針で、単語同士を直接比較するのではなくて、そのシグネチャーを比較することとしました。ですから単語リストの各単語をそのシグネチャーで引ける辞書(アナグラム辞書)を作る必要があります。毎回単語リストのシグネチャーを計算するのは、効率的ではありませんからね。
データ構造は次のようなものがよさそうです。
{:aemn => ['name', 'mean', 'amen'], :amn => ['man']}
シグネチャーをkeyとして、それを持った単語のリストをvalueとするハッシュです。
それでは単語リストからアナグラム辞書を作る、build_anagramsメソッドを定義しましょう。入力は単語の配列です。
def build_anagrams(words) words.map { |word| [signature(word), word] } .inject({}) { |h, (sign, word)| h[sign] ||= []; h[sign] << word; h } .select { |sign, words| words.size > 1 } end
まずmapでシグネチャーと単語の組みを作って、injectで共通のシグネチャーの指す配列に単語を追加していきます。injectの使い方では注意点が2つあります。1つは h[sign] ||= [] での初期化が必要なこと、1つは各イテレートでハッシュhを返すことです。ちなみに次のような書き方もできます。
def build_anagrams(words) mem = Hash.new{|h, k| h[k] = []} words.map { |word| [signature(word), word] } .each_with_object(mem) { |(sign, word), h| h[sign] << word } .select { |sign, words| words.size > 1 } end
さてこのメソッドを試してみましょう。
word_list = %w(name mean amen man) Anagrams = build_anagrams(word_list) # => {:aemn=>["name", "mean", "amen"], :amn=>["man"]}
いいですね!
これでもう最初に示した、find_anagramsメソッドが書けますね。
def find_anagrams(word) sign = signature(word) res = Anagrams[sign] res ? res - [word] : [] end find_anagrams("name") # => ["mean", "amen"] find_anagrams("age") # => []
単語リストの読み込み
ここまで来ればあと一歩です。単語リストのファイルをオープンして、メモリー上に読み出し、そこから単語の配列を作ります。最初は小さな単語ファイル(sample)を用意して、試してみるのがいいですね。
name mean amen man man MAN street sweet Tester retest word world tower rowet WROTE X a monopersulphuric b
コードは次のようになります。
def build_wordlist(path) File.open(path) do |f| f.map { |line| line.chomp.downcase }.uniq.reject { |word| word.size < 2 } end end WORDS = build_wordlist('./sample') # => ["name", "mean", "amen", "man", "street", "sweet", "tester", "retest", "word", "world", "tower", "rowet", "wrote", "monopersulphuric"]
改行を除去して全て小文字化し、重複と空行と一文字単語を除去します。
さあ完成です! コードをまとめてみましょう。
anagramコマンド
後述するAnagram辞書を作ると、Terminalでanagramコマンドが使えます。
% ./anagram dream team ruby dream => ["armed", "derma", "ramed"] team => ["mate", "meat", "meta", "tame", "tema"] ruby => ["bury"]
アナグラムを見つけたい1または複数の単語を、コマンドの引数として渡します。
Anagram辞書の作成
Anagram.buildクラスメソッドで、Anagram辞書を作ります。
% irb >> require 'anagram' >> >> File.open('/usr/share/dict/words') do |f| >> Angram.build(f) >> end
辞書はカレントディレクトリに、YAMLファイル(anagram.yml)で保存されます。
Anagram.findクラスメソッド
Anagram辞書ができれば、findクラスメソッドが使えます。
>> Anagram.find 'time' #=> ["emit", "item", "mite"] >> Anagram.find 'beer' #=> ["bere", "bree"]
Anagram.anagrams?クラスメソッド
このメソッドは引数に渡した単語同士が、アナグラムか検査します。
>> Anagram.anagrams? 'beer', 'bair' #=> false >> Anagram.anagrams? 'time', 'emit', 'item' #=> true
anagrams?メソッドは日本語でも文章でも使えます。
>> Anagram.anagrams? 'いきるいみなんて', 'みんないきている' #=> true >> sentence1 = "To be or not to be: that is the question; whether 'tis nobler in the mind to suffer the slings and arrows of outrageous fortune..." >> sentence2 = "In one of the Bard's best-thought-of tragedies our insistent hero, Hamlet, queries on two fronts about how life turns rotten." >> Anagram.anagrams?(sentence1, sentence2) #=> true
こんな長いアナグラムを考える人がいるんですね..
Anagramオブジェクト
Anagram.newでAnagramオブジェクトを生成することで、Anagram#find #longest #most および#allの各メソッドが使えるようになります。Anagram#findはAnagram.findと同じです。
>> an = Anagram.new >> an.find 'visit' #=> ["vitis"] >> an.find 'master' #=> ["martes", "remast", "stream"] >> an.find 'version' #=> [] >> an.find 'bridge' #=> ["begird"] >> an.find 'paper' #=> ["rappe"] >> an.find 'speech' #=> [] >> an.find 'take' #=> ["kate", "keta", "teak"] >> an.find 'language' #=> ["ganguela"] >>
Anagram#longest は辞書における、長い単語のアナグラムを上位から表示します。Anagram#most は最も組数の多いアナグラムを上位から表示します。
>> an.longest(size:10).each {|l| p l} ["hydropneumopericardium", "pneumohydropericardium"] ["cholecystoduodenostomy", "duodenocholecystostomy"] ["glossolabiopharyngeal", "labioglossopharyngeal"] ["chromophotolithograph", "photochromolithograph"] ["duodenopancreatectomy", "pancreatoduodenectomy"] ["anatomicopathological", "pathologicoanatomical"] ["encephalomeningocele", "meningoencephalocele"] ["glossolabiolaryngeal", "labioglossolaryngeal"] ["anatomicophysiologic", "physiologicoanatomic"] ["pericardiacophrenic", "phrenicopericardiac"] >> an.most(size:10).each {|m| p m} ["angor", "argon", "goran", "grano", "groan", "nagor", "orang", "organ", "rogan", "ronga"] ["elaps", "lapse", "lepas", "pales", "salep", "saple", "sepal", "slape", "spale", "speal"] ["caret", "carte", "cater", "crate", "creat", "creta", "react", "recta", "trace"] ["ester", "estre", "reest", "reset", "steer", "stere", "stree", "terse", "tsere"] ["leapt", "palet", "patel", "pelta", "petal", "plate", "pleat", "tepal"] ["armet", "mater", "metra", "ramet", "tamer", "terma", "trame", "trema"] ["asteer", "easter", "eastre", "reseat", "saeter", "seater", "staree", "teaser"] ["arist", "astir", "sitar", "stair", "stria", "tarsi", "tisar", "trias"] ["laster", "lastre", "rastle", "relast", "resalt", "salter", "slater", "stelar"] ["dater", "derat", "detar", "drate", "rated", "trade", "tread"]
Anagram#all は辞書における、すべてのアナグラムの組みを表示します。
>> an.all.size #=> 14212 >> an.all.take 5 #=> [["aal", "ala"], ["aam", "ama"], ["aaronic", "nicarao", "ocarina"], ["aaronite", "aeration"], ["aaru", "aura"]] >> an.all.select {|set| set.size > 3 && set.first =~ /^ru/} => [["ruinate", "taurine", "uranite", "urinate"], ["runite", "triune", "uniter", "untire"], ["rusa", "saur", "sura", "ursa", "usar"], ["ruse", "suer", "sure", "user"]]
なおAnagram.newは単語リストファイルを引数に取ることができます。
>> an = Anagram.new(open 'sample_dic')
こうするとAnagram辞書を作らずに、各インスタンスメソッドが使えるようになります。
暇なときに遊んでやってください :-)
(追記:2011-12-07) コードを一部修正しました。