From: Yusuke ENDOH Date: 2010-12-06T20:22:41+09:00 Subject: [ruby-dev:42691] Re: Enumerable#categorize 遠藤です。 2010年11月27日18:45 Tanaka Akira : > enumerable から hash を生成するメソッドとして > Enumerable#categorize を追加するのはどうでしょうか。 みんなと同じ感想ですが、やはり 1 メソッドに詰め込み過ぎていると思います。 「要素の抽出」「分類集計処理」「後処理」のうち、2 番目に絞ったらいいのでは ないでしょうか。 [[1, 2], [1, 3], [2, 3]].aggregate #=> {1 => [2, 3], 2 => [3]} [[1, 2], [1, 3], [2, 3]].aggregate(:op=>:+) #=> {1 => 5, 2 => 3 } [[1, 2, 3]].aggregate #=> {1 => {2 => [3]} } # 議論の混乱防止のため、aggregate という名前にしています。 # 他の候補としては、to_hash 、hashtree とかどうでしょう。 コーナーケースはこんな感じでしょうか。 [[1, 2], [1, 2, 3]].aggregate #=> 例外 [].aggregate #=> {} [[]].aggregate #=> 例外 [[1]].aggregate #=> 例外 多少 categorize より長くなりますが、何をやっているかはずっとわかりやすく なると思います。 以下、akr さんの例を書き換えてみました。 ary = [["matz", "Yukihiro Matsumoto"], ["nobu", "Nobuyoshi Nakada"], ["akr", "Tanaka Akira"], ["usa", "Usaku NAKAMURA"], ["naruse", "NARUSE, Yui"], ["ko1", "SASADA Koichi"]] # akr さん版: # h = ary.categorize(1, 0) h = ary.map {|k, v| [v, k] }.aggregate #=> {"Yukihiro Matsumoto"=>["matz"], # "Nobuyoshi Nakada"=>["nobu"], # "Tanaka Akira"=>["akr"], # "Usaku NAKAMURA"=>["usa"], # "NARUSE, Yui"=>["naruse"], # "SASADA Koichi"=>["ko1"]} # akr さん版: # ary.categorize(lambda {|elt| elt[1] }, lambda {|elt| elt[0] }) h = ary.map {|elt| [elt[1], elt[0]] }.aggregate #=> {"Yukihiro Matsumoto"=>["matz"], # "Nobuyoshi Nakada"=>["nobu"], # "Tanaka Akira"=>["akr"], # "Usaku NAKAMURA"=>["usa"], # "NARUSE, Yui"=>["naruse"], # "SASADA Koichi"=>["ko1"]} # akr さん版: # h = ary.categorize(lambda {|e| e[0][0] }, lambda {|e| e[0][1]}, 0) h = ary.map {|e| [e[0][0], e[0][1], e[0]] }.aggregate #=> {"m"=>{"a"=>["matz"]}, # "n"=>{"o"=>["nobu"], "a"=>["naruse"]}, # "a"=>{"k"=>["akr"]}, # "u"=>{"s"=>["usa"]}, # "k"=>{"o"=>["ko1"]}} # akr さん版: # h = ary.categorize(lambda {|e| e[0][0] }, 1) {|ks, vs| vs.sort } h = ary.map {|elt| [elt[0][0], elt[1]] }.aggregate h.each {|ks, vs| h[ks] = vs.sort } # または vs.sort! #=> {"m"=>["Yukihiro Matsumoto"], # "n"=>["NARUSE, Yui", "Nobuyoshi Nakada"], # "a"=>["Tanaka Akira"], # "u"=>["Usaku NAKAMURA"], # "k"=>["SASADA Koichi"]} # akr さん版: # h = ary.categorize(1, 0) {|ks, vs| # raise "duplicate keys: #{ks.inspcet}" if vs.length != 1 # vs[0] # } h = ary.map {|k, v| [v, k] }.aggregate h.each {|ks, vs| raise "duplicate keys: #{ks.inspcet}" if vs.length != 1 h[ks] = vs[0] } #=> {"Yukihiro Matsumoto"=>"matz", # "Nobuyoshi Nakada"=>"nobu", # "Tanaka Akira"=>"akr", # "Usaku NAKAMURA"=>"usa", # "NARUSE, Yui"=>"naruse", # "SASADA Koichi"=>"ko1"} # akr さん版: # h = ary.categorize(lambda {|e| e[0][0] }, lambda {|e| 1 }, :op=>:+) h = ary.map {|e| [e[0][0], 1] }.aggregate(:op=>:+) #=> {"m"=>1, "n"=>2, "a"=>1, "u"=>1, "k"=>1} # akr さん版: # committers = open("ruby-committers.yml") {|f| YAML.load(f) } # pp committers.categorize("account", ["name", "nick"]) {|ks, vs| vs[0] } h = committers.map {|e| [e["account"], [e["name"], e["nick"]]] }.aggregate h.each {|ks, vs| h[ks] = vs[0] } 中間配列ができるのが嫌、と言われると思いますが、それは categorize 特有の 問題ではないので、map の Enumerator を返すバージョンを用意することで解決 すべきだと思います。 # 手前味噌ですが http://d.hatena.ne.jp/ku-ma-me/20091111/p2 また、Hash から Hash を返す map もあると便利かもしれません。 しばしば要望きてると思いますが、なんで用意されないんでしたっけ。 group_by との類似については、個人的にはあまり気になりません。 というか、group_by がどうにも使いにくすぎるのが行けないんだと思います。 あと、:seed は値ではなく値を生成する Proc を受け取るべきだと思います。 Hash.new([]) と同じ設計不良になってしまいます。 p [[1, 2], [1, 3], [2, 5]].categorize(0, 1, seed: [], op: proc {|x, e| x << e }) {1=>[2, 3, 5], 2=>[2, 3, 5]} -- Yusuke Endoh