同値関係を求めるパズル

元ネタもやっていなかったので、脳トレがわりにやってみた。
断っておきますが、やってみただけです。特に考察などを加えているわけではないので、あしからず。

今回のエントリは結城浩の『Perlクイズ』2004-06-18 No.0087をベースにしています。

問題

xxxx=yyyy という形式のデータをたくさん受け取り、等しいもの同士をグルーピングするプログラムを書いてください。データは標準入力から与え、グルーピングした結果は { xxxx, yyyy } のように集合のような形式で標準出力に出すことにします。以下に入力と出力の組の例を示します。グループ同士の出力順は問いませんが、グループの中の各要素は適宜ソートしてください。
同値関係を求めるパズル - rubyco(るびこ)の日記

自力で解いてみた。

m=[]
ARGF.read.each_line{|line|
  next unless /(.*?)=(.*)/ =~ line.chomp
  m.push [$1, $2]
}
r = []
while m.size > 0
  g={m[0][0] => true}
  while m.reject!{|x1, x2| g[x1] = g[x2] = true if g[x1] || g[x2]}
  end                                      
  r.push g.keys
end
r.each{|g|
  puts "{ " + g.sort.join(", ") + " }"
}

ビバ、力技。
特にwhile reject!あたりがナントモなコード…。

さて、最近の傾向のとおり、ここで出てくる変数を減らしていく。
ここで利用されるのは、ブロック内部での:x1, :x2, :lineと、最後にlocal_variablesをすると返ってくる :m, :g, :rの6つ。これを最終的には0にしたい。

マズは読み込み部分。
要はxxxxx=yyyyyになっている部分を抜き出せば良いわけで、それ以外は無視しているわけだけど、これは無駄。要するにソレはString#scanなわけで、ここはソレに置き換えすると:lineが減らせる。

次に出力部分。
一旦配列に収めているけど、これは冗長。
出力用内容の格納変数なぞは用意する必要も無く、その場で出力してしまえば話は早い。
これによって:rが無くなる。

最後に、reject!に対する引数。
ブロック引数を配列として一つの変数で受け取る。

で、こうしてみた。

m=ARGF.read.scan(/(.*?)=(.*)/)
while m.size > 0
  g={m[0][0] => true}
  while m.reject!{|x| g[x[0]] = g[x[1]] = true if g[x[0]] || g[x[1]]}
  end
  puts "{ " + g.keys.sort.join(", ") + " }"
end
p local_variables # => ["m", "g"]

で、大分短くなったし、変数も減らせた。
しかし、まだmとgとxの3つが残っている。
さて、ここから減らすにはどうしたらいいだろうか。