From: Tanaka Akira Date: 2010-11-28T23:57:23+09:00 Subject: [ruby-dev:42654] Re: Enumerable#categorize 2010年11月28日0:46 Yukihiro Matsumoto : > 無理矢理、本題の categorize に戻すと、個人的には田中さんが提 > 示された仕様は、個別には使える局面があるのは分かるのですが、 > ひとつのメソッドとしては高機能すぎるような印象があります。た > とえば、さきほどの私の xxx が同じキーを集めて分類するとして、 > そのメソッドに categorize という名前を付けるとすると、私好み > のデザインなのですが。 categorize と hashmap を試しに作ってみると、こんなかんじですかね。 オプションをいじるとどっちも実現できるので、どっちを基盤にしてもいいんですが、 ここでは categorize を基盤にして、hashmap は categorize を使って 実装してあります。 私の最初の案と比較すると、 機能的には後処理を入れられないという点が違いでしょうか。 これはブロックが返す配列の長さが一定とは限らないため、 ハッシュを作り終えた後、どこが値なのかわからないからです。 結果として、各カテゴリ毎に平均を求めるなど、後処理が必要なものは 一回ではできなくて、categorize したあとに hashmap するとかになるのでしょう。 ただこれは、注意深くやれば、私の案と違って、場所によってネストの深さが 異なるハッシュを生成できるということでもあります。 a = %w[alpaca alpha alphabet alpine alpine] pp a.categorize(:op=>:+) {|s| s.split(//) + [:count, 1] } #=> {"a"=> {"l"=> {"p"=> {"a"=>{"c"=>{"a"=>{:count=>1}}}, "h"=>{"a"=>{:count=>1, "b"=>{"e"=>{"t"=>{:count=>1}}}}}, "i"=>{"n"=>{"e"=>{:count=>2}}}}}}} うんまぁこれはこれでいいような気がします。 module Enumerable # call-seq: # enum.categorize([opts]) {|elt| [key1, ..., val] } -> hash # # categorizes the elements in _enum_ and returns a hash. # # The block is called for each elements in _enum_. # The block should return an array which contains # one or more keys and one value. # # The keys and value are used to construct the result hash. # If two or more keys are provieded # (i.e. the length of the array is longer than 2), # the result hash will be nested. # # The value of innermost hash is an array which contains values for # corresponding keys. # (This behavior can be customized by :seed, :op and :update option.) # # a = [{:fruit => "banana", :color => "yellow", :taste => "sweet"}, # {:fruit => "melon", :color => "green", :taste => "sweet"}, # {:fruit => "grapefruit", :color => "yellow", :taste => "tart"}] # p a.categorize {|h| h.values_at(:color, :fruit) } # #=> {"yellow"=>["banana", "grapefruit"], "green"=>["melon"]} # # pp a.categorize {|h| h.values_at(:taste, :color, :fruit) } # #=> {"sweet"=>{"yellow"=>["banana"], "green"=>["melon"]}, # # "tart"=>{"yellow"=>["grapefruit"]}} # # This method can take an option hash. # Available options are follows: # # - :seed specifies seed value. # - :op specifies a procedure from seed and value to next seed. # - :update specifies a procedure from keys, seed and value to next seed. # # The default behavior, array construction, can be implemented as follows. # :seed => nil # :op => lambda {|s, v| !s ? [v] : (s << v) } # def categorize(opts={}) if opts.include? :seed has_seed = true seed = opts[:seed] else has_seed = false seed = nil end if opts.include?(:update) && opts.include?(:op) raise ArgumentError, "both :update and :op specified" elsif opts.include?(:update) update = opts[:update].to_proc elsif opts.include?(:op) op = opts[:op].to_proc update = lambda {|ks, s, v| op.call(s, v) } else update = lambda {|ks, s, v| !s ? [v] : (s << v) } if !has_seed has_seed = true seed = nil end end result = {} self.each {|elt| kv = yield(elt) kv = kv.to_ary h = result 0.upto(kv.length-3) {|i| k = kv[i] h = (h[k] ||= {}) } lastk = kv[-2] v = kv[-1] if !h.include?(lastk) if !has_seed h[lastk] = v else h[lastk] = update.call(kv[0...-1], seed, v) end else h[lastk] = update.call(kv[0...-1], h[lastk], v) end } result end # call-seq: # enum.hashmap([opts]) {|elt| [key1, ..., val] } -> hash def hashmap(opts={}, &block) opts = Hash[opts] opts[:update] ||= lambda {|ks, x, y| y } categorize(opts, &block) end end -- [田中 哲][たなか あきら][Tanaka Akira]