From: Tanaka Akira Date: 2010-11-27T19:51:28+09:00 Subject: [ruby-dev:42640] Re: Enumerable#categorize 2010年11月27日19:37 Urabe Shyouhei : >> 幸いにして第一要素から第二要素へのハッシュは Hash[ary] で作れるようになったので、 >> 逆の第二要素から第一要素へのハッシュについて考えましょう。 >> (同姓同名を考慮して、ハッシュの値は第一要素の配列としましょう) >> >> これを得るには現在、残念な事に、自分でループを書く必要があります。 > > この状況を改善するという選択肢はありませんか。つまりHash[ary].invertがもう少し > 賢くなればいいんですよね? いや、もっと一般的な事を考えています。 CSV から読み込む、という状況に触れたのもそうなのですが、 一般にフィールド (カラム) はふたつとは限りません。 また、必ずユニークなキーがあるとも限りません。 それもあって、最初にデータがハッシュとして存在する、 という仮定はしたくありません。 >> 引数の 1, 0 が何を意味するかというと、enumerable の要素から >> ハッシュのキー及び値を取り出す指定です。 >> 具体的には ary の各要素に対し、[] メソッドを呼び出し、 >> その引数に引き渡してキー/値を得ます。 >> つまり、["matz", "Yukihiro Matsumoto"][1] として "Yukihiro Matsumoto" というキーを得て、 >> ["matz", "Yukihiro Matsumoto"][0] として "matz" という値を得るわけです。 > > 分かりにくすぎると思います。 0, 1 というインデックスがわかりにくい、という意図でしょうか? > これが存在すること自体には特に反対しませんが、ちょっと抽象的すぎて逆に使いづら > いと感じます。最終的にこのアルゴリズムに落ちたとしても、ユーザーにはもう少し使 > いやすいAPIを提供すべきでしょう。たとえば数を数える専用のメソッドを作るとか。 ひとつひとつ進めていこうかと思っていて、 値を配列にする必要がない場合と、 数を数えるものについては、 また後で (気が向いたときに) やろうかと思っていました。 module Enumerable # :call-seq: # enum.unique_categorize(ksel1, ksel2, ..., vsel, [opts]) -> hash # enum.unique_categorize(ksel1, ksel2, ..., vsel, [opts]) {|s, v| ... } -> hash # # categorizes the elements in _enum_ and returns a hash. # This method assumes one element for a category by default. # # +unique_categorize+ takes one or more key selectors, # one value selector and # an optional option hash. # It also takes an optional block. # # The selectors specify how to extract a value from an element in _enum_. # See Enumerable#categorize for details of selectors. # # The key selectors, _kselN_, are used to extract hash keys from an element. # If two or more key selectors are specified, the result hash will be nested. # # The value selector, _vsel_, is used for the values of innermost hashes. # By default, this method assumes the key selectors categorizes elements in enum uniquely. # If the key selectors generates same keys for two or more elements, ArgumentError is raised. # This behavior can be customized by :seed option and the block. # # a = [{:fruit => "banana", :color => "yellow", :taste => "sweet", :price => 100}, # {:fruit => "melon", :color => "green", :taste => "sweet", :price => 300}, # {:fruit => "grapefruit", :color => "yellow", :taste => "tart", :price => 200}] # p a.unique_categorize(:fruit, :price) # #=> {"banana"=>100, "melon"=>300, "grapefruit"=>200} # # p a.unique_categorize(:color, :price) # # ArgumentError # # If the block is given, it is used for combining values in a category. # The arguments for the block is a seed and the value extracted by _vsel_. # The return value of the block is used as the next seed. # :seed option specifies the initial seed. # If :seed is not given, the first value for each category is used for the seed. # # p a.unique_categorize(:taste, :price) {|s, v| s + v } # #=> {"sweet"=>400, "tart"=>200} # # p a.unique_categorize(:color, :price) {|s, v| s + v } # #=> {"yellow"=>300, "green"=>300} # def unique_categorize(*args, &update_proc) opts = args.last.kind_of?(Hash) ? args.pop.dup : {} if update_proc opts[:update] = lambda {|ks, s, v| update_proc.call(s, v) } else seed = Object.new opts[:seed] = seed opts[:update] = lambda {|ks, s, v| if s.equal? seed v else raise ArgumentError, "ambiguous key: #{ks.map {|k| k.inspect }.join(',')}" end } end categorize(*(args + [opts])) end # :call-seq: # enum.category_count(ksel1, ksel2, ...) # # counts elements in _enum_ for each category defined by the key selectors. # # a = [{:fruit => "banana", :color => "yellow", :taste => "sweet", :price => 100}, # {:fruit => "melon", :color => "green", :taste => "sweet", :price => 300}, # {:fruit => "grapefruit", :color => "yellow", :taste => "tart", :price => 200}] # # p a.category_count(:color) # #=> {"yellow"=>2, "green"=>1} # # p a.category_count(:taste) # #=> {"sweet"=>2, "tart"=>1} # # p a.category_count(:taste, :color) # #=> {"sweet"=>{"yellow"=>1, "green"=>1}, "tart"=>{"yellow"=>1}} # # The selectors specify how to extract a value from an element in _enum_. # See Enumerable#categorize for details of selectors. # def category_count(*args) categorize(*(args + [lambda {|e| 1 }, {:update => lambda {|ks, s, v| s + v }}])) end end -- [田中 哲][たなか あきら][Tanaka Akira]