« サイエンス・イマジネーション/瀬名秀明編 | Main | ポメラ »

Ruby版 sort |uniq -c

[ruby-list:45572]は、awkで

/t$ cat A
yahoo
goo
google
yahoo
msn
msn
/t$ awk '{ class[$1]++ } END { for (i in class){print i, class[i]}}' A
google 1
msn 2
goo 1
yahoo 2

の様にやるようなワンライナをRubyでやるにはどうすればいいかという話題です。

/t$ cat A |sort |uniq -c
      1 goo
      1 google
      2 msn
      2 yahoo

でいいじゃまいかという意見もありますが、例えば先頭1文字が同じものでカウントしたいなどいろんな場合があるでしょう。

私も同じようなスクリプトは毎日書いてます。Apacheのログからユーザーごとのアクセス数を出すとか普通のことですよね。スクリプト言語が書けない人はどうやって仕事してるのか、とても不思議です。

sortとuniqの結果と同じものをRubyで出すのは、こんな感じでしょうか。これぐらいなら特に何も考えずに書けます

/t$ cat hoge.rb
#!/usr/bin/env ruby

h = Hash.new(0)

while line = gets
  h[line.chomp] +=1
end

h.to_a.sort{|a,b| a[0] <=> b[0]}.each do |i|
  puts "\t#{i[1]} #{i[0]}"
end
/t$ hoge.rb A
        1 goo
        1 google
        2 msn
        2 yahoo

ワンライナと言われると、ちょっと悩んじゃうな。

るびきちさんが、まず、上のawkの直訳版を出してくれています。awk - Perl - Rubyというのは直系の子孫のようなものですからawkで出来るようなことは、大抵Rubyでも(ちょっとめんどくさくなるけど)出来るようになってます。普段、RubyをRubyらしく使っているとあまり使わない機能かもしれませんけど。

こんな感じらしいです。

/t$ ruby -lne '(h||=Hash.new(0))[$_]+=1;  <==実際には改行なし
END{for k,v in h do print k," ",v end}' A
yahoo 2
google 1
goo 1
msn 2

コマンドラインの-nはPerlゆずりのワンライナ支援機能。ループを勝手にやってくれます。-lは$_がchopした形で得られます。これは初めて知りました。見逃してるものが結構あるんだなあ。awkのBEGIN{},END{}もあったりします。まあ、普通にスクリプトを書いてるときは滅多に使うことはないかもしれません。

h||=Hash.new(0)

h = Hash.new(0) unless h

です。Perlっぽい書き方ですね(ほんとか?)

石塚さんの回答は、

/t$ ruby -e "puts ARGF.group_by{|w| w}  <==実際には改行なし
.map{|key, ary| [key.chomp, ary.size].join(' ')}" A
-e:1: undefined method `group_by' for ARGF:Object (NoMethodError)

ありゃ・・・?

/t$ ruby -v
ruby 1.8.6 (2007-03-13 patchlevel 0) [i386-cygwin]

すいません。Rubyが古かったです・・・orz

めんどくさいのでcygwinを丸ごとアップデートしてっと、

/t$ ruby -v
ruby 1.8.7 (2008-08-11 patchlevel 72) [i386-cygwin]
/t$ ruby -e "puts ARGF.group_by{|w| w}  <==実際には改行なし
.map{|key, ary| [key.chomp, ary.size].join(' ')}" A
google 1
msn 2
goo 1
yahoo 2

確かにできてますが、高階関数は頭を使います。えーっと・・・、わからん。irbで追っかけてみましょう。

/t$ irb
>> lines = File.readlines("A")
=> ["yahoo\r\n", "goo\r\n", "google\r\n", <==実際には改行なし
"yahoo\r\n", "msn\r\n", "msn\r\n"]

まず、各行の配列を作っておきます。ARGFはこれと同じ扱いが出来る組み込み変数です

>> lines.group_by{|w| w}
=> {"google\r\n"=>["google\r\n"],
    "msn\r\n"=>["msn\r\n", "msn\r\n"],
    "goo\r\n"=>["goo\r\n"],
    "yahoo\r\n"=>["yahoo\r\n", "yahoo\r\n"]}

それをgroup_byする。この例だと、group_byがどういう動作をするのはよくわからないですけど

>> lines.group_by{|w| w[0,1]}
=> {"m"=>["msn\r\n", "msn\r\n"],
    "y"=>["yahoo\r\n", "yahoo\r\n"],
    "g"=>["goo\r\n", "google\r\n"]}

こういう例だとわかりますね。確かに分類してます

>> lines.group_by{|w| w}  <==実際には改行なし
   .map{|key, ary| [key.chomp, ary.size]}
=> [["google", 1], ["msn", 2],
    ["goo", 1], ["yahoo", 2]]

そして、各組の数を数えて

>> lines.group_by{|w| w}  <==実際には改行なし
.map{|key, ary| [key.chomp, ary.size].join(" ")}
=> ["google 1", "msn 2", "goo 1", "yahoo 2"]

出力を作って

>> puts lines.group_by{|w| w} <==実際には改行なし
.map{|key, ary| [key.chomp, ary.size].join(" ")}
google 1
msn 2
goo 1
yahoo 2
=> nil

putsと。putsって配列に対して実行するとこうなるんですね。

なかなか面白いです

|
|

« サイエンス・イマジネーション/瀬名秀明編 | Main | ポメラ »

パソコン・インターネット」カテゴリの記事

Comments

Post a comment



(Not displayed with comment.)


Comments are moderated, and will not appear on this weblog until the author has approved them.



TrackBack

TrackBack URL for this entry:
http://app.cocolog-nifty.com/t/trackback/47905/42869128

Listed below are links to weblogs that reference Ruby版 sort |uniq -c:

« サイエンス・イマジネーション/瀬名秀明編 | Main | ポメラ »