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ページのテキストボックスにこれを貼りつければ、こんなものができるんだよ。


f:id:keyesberry:20111212175942p:image


すばらしいよね!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)
f:id:keyesberry:20111212175943p:image


Alices Adventures In Wonderland (100 words)
f:id:keyesberry:20111212175949p:image


なんかいい感じだよね!他の小説でも試してみるよ。


Pride And Prejudice
f:id:keyesberry:20111212175944p:image


The Adventures Of Sherlock Holmes
f:id:keyesberry:20111212175945p:image


Frankenstein
f:id:keyesberry:20111212175946p:image


Hamlet
f:id:keyesberry:20111212175947p:image


Peter Pan
f:id:keyesberry:20111212175948p:image


Wordleは楽しいから是非とも試してみて!僕が作ったWordleは次のURLで見れるよ。


http://www.wordle.net/gallery?username=merborne:title=Wordle - Gallery: merborne


GraphAzでアナグラムをビジュアライズしたよ

この前Rubyで単語のアナグラムを見つける、Anagramライブラリを書いたよ。


Rubyでアナグラムしようよ - hp12c


でもやっぱりアナグラムを単に、ターミナルに出力するだけじゃつまらないよね。


で、以前に作ったruby-graphvizをラップするGraphAzを思い出したよ。


Rubyでアニメーション・グラフを作ろう! - hp12c


それで最も組数の多いアナグラム上位5組を、GraphAzでビジュアライズしてみたらなんかきれいだったので、コードと共にここに貼っておくよ。円の中心がアナグラムシグネチャで、周囲がそのシグネチャを持った単語だよ。


f:id:keyesberry:20111211192807p:image


上位100組だとお花畑になるよ。
f:id:keyesberry:20111212000354p:image


Rubyでビックリ階乗を解こう! ~人間の実労時間を最適化する

「ビックリ階乗(Exclamatory Factorial)」って知ってますか?ええ、知るわけないです。なぜならいま僕が、次のツイートの解に命名したばかりの言葉だからです。*1


f:id:keyesberry:20111205222834p:image


なかなか意味深なツイートですが、自分が先生だったらこの解答に◯を付けざるを得ないでしょう。答えにビックリマークを付ける人はいませんからね!


さて、プログラムする身としては文系理系両者の反応よりも、このような「ビックリ階乗」が、どれくらい奇跡的なものなのかが気になります。つまりa - (b / c) の解(先の例では24)と、(a - b) / c の解の階乗(4!)とが、一致する組み合わせは果たしてどれくらいあるのでしょうか。


数学的に書くとこういうことです。

f1 = a - (b \div c)
f2 = (a - b) \div c


f1 == f2!


そんな思いは当然僕だけではありませんでした。*2


「40 - 32 / 2 = 4!」 - エンジニアのソフトウェア的愛情


このブログより1000以下の数字で、15組のビックリ階乗があることがわかっています。ここで1000までの数を考えたときa b c の組み合わせ数は、10億通り(1000^3)にもなりますからその確率は..

15 \div 10{,}0000{,}0000=0.000000015

ビックリ階乗は奇跡的な組み合わせなんですね!


しかしそれにしても10億通りの組み合わせとなると、まじめにそのすべてを試してみると当然に、その実行時間が問題になります。先のブログでは試行錯誤して最初のプログラムから1800倍の高速化を実現して0.1秒以下で答えがでます。手元でRuby版も試して見ましたが0.037秒でした。ステキすぎます!


で、これ以上僕のできることは何も無いのですが、コードの実行時間というものを完全に無視してコードの読み易さ、つまり人間の実労時間の最適化という一点に焦点を合わせてRubyでコードを書いてみようと思います^^;


さて、ビックリ階乗の数式をもう一度確認します。

f1 = a - (b \div c)
f2 = (a - b) \div c


f1 == f2!


これを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']


これを実現するには少なくとも次のステップが必要そうです。

  1. 英単語リストを用意する
  2. 指定単語のアナグラムを英単語リストから見つけ出す

指定単語のアナグラムを英単語リストから見つけ出す

単語リストはどこかにきっとあるでしょうから後回しにして、アナグラムを見つける方法を先に考えます。


先に示した"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"]

改行を除去して全て小文字化し、重複と空行と一文字単語を除去します。


さあ完成です! コードをまとめてみましょう。


Rubyならアナグラムも簡単ですね!


Anagramライブラリ

で、ここまでやったので、Anagramライブラリを書いて見ました。Rspecの練習を兼ねまして..^^;


melborne/anagram - GitHub


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) コードを一部修正しました。