From: "trans (Thomas Sawyer)" Date: 2012-03-18T23:01:28+09:00 Subject: [ruby-core:43450] [ruby-trunk - Feature #4151] Enumerable#categorize Issue #4151 has been updated by trans (Thomas Sawyer). I'm sorry, but #categorize has big code smell. Seems like too much functionality is being shoved into one method. It is still not clear to me after reading over explanation many times how #categorize works. So either it is relatively straight forward but the explanation is overly complexed, or the method itself is too complex. The later seems more likely given the number of options, especially lambda options, the method takes. Let's look at a given example: * [ruby-talk:288931] [["1", "01-02-2008", 5], ["1", "01-03-2008", 10], ["2", "12-25-2007", 5], ["1", "01-04-2008", 15]] to {"1" => {"01-02-2008" => 5, "01-03-2008" => 10, "01-04-2008" => 15}, "2" => {"12-25-2007" => 5}} Implemented as: orig.categorize(:op=>lambda {|x,y| y}) {|e| e } Traditionally, the above would be something like: hash = Hash.new{|h,k| h[k]={}} list.each do |group, date, size| hash[group][date] ||= 0 hash[group][date] += size end This may be longer but it is very readable. Actually, if Ruby would *finally* offer some convenience method for the first line, e.g. `Hash.auto{{}}` instead of `Hash.new{|h,k| h[k]={}}`, then hash = Hash.auto{Hash.auto(0)} list.each{ |group, date, size| hash[group][date] += size } I suspect almost every case for using #categorize will be able to be treated in much the same manner. This is not to say however that I don't think some form(s) of `Enumerable -> Hash` would not be useful. I think it would, in fact I sometimes use Enumerable#mash (alias #graph). [1,2,3].mash{ |v| [v.to_s, v] } #=> {'1'=>1, '2'=>2, '3'=>3} But I would rather see a few methods that cover basic use cases that can work together and with other methods to build up more complex solutions then to create a single method that tries to cover every complex possibility in one go. ---------------------------------------- Feature #4151: Enumerable#categorize https://bugs.ruby-lang.org/issues/4151#change-24918 Author: akr (Akira Tanaka) Status: Open Priority: Low Assignee: akr (Akira Tanaka) Category: Target version: =begin Hi. How about a method for converting enumerable to hash? 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. p (0..10).categorize {|e| [e % 3, e % 5] } #=> {0=>[0, 3, 1, 4], 1=>[1, 4, 2, 0], 2=>[2, 0, 3]} The keys and value are used to construct the result hash. If two or more keys are provided (i.e. the length of the array is longer than 2), the result hash will be nested. p (0..10).categorize {|e| [e&4, e&2, e&1, e] } #=> {0=>{0=>{0=>[0, 8], # 1=>[1, 9]}, # 2=>{0=>[2, 10], # 1=>[3]}}, # 4=>{0=>{0=>[4], # 1=>[5]}, # 2=>{0=>[6], # 1=>[7]}}} 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. 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 seed and block value to next seed. :seed, :op and :update customizes how to generate the innermost hash value. :seed and :op behavies like Enumerable#inject. If _seed_ and _op_ is specified, the result value is generated as follows. op.call(..., op.call(op.call(seed, v0), v1), ...) :update works as :op except the second argument is the block value itself instead of the last value of the block value. If :seed option is not given, the first value is used as the seed. # The arguments for :op option procedure are the seed and the value. # (i.e. the last element of the array returned from the block.) r = [0].categorize(:seed => :s, :op => lambda {|x,y| p [x,y] #=> [:s, :v] 1 }) {|e| p e #=> 0 [:k, :v] } p r #=> {:k=>1} # The arguments for :update option procedure are the seed and the array # returned from the block. r = [0].categorize(:seed => :s, :update => lambda {|x,y| p [x,y] #=> [:s, [:k, :v]] 1 }) {|e| p e #=> 0 [:k, :v] } p r #=> {:k=>1} The default behavior, array construction, can be implemented as follows. :seed => nil :op => lambda {|s, v| !s ? [v] : (s << v) } Note that matz doesn't find satisfact in the method name, "categorize". [ruby-dev:42681] Also note that matz wants another method than this method, which the hash value is the last value, not an array of all values. This can be implemented by enum.categorize(:op=>lambda {|x,y| y}) { ... }. But good method name is not found yet. [ruby-dev:42643] -- Tanaka Akira Attachment: enum-categorize.patch =end -- http://bugs.ruby-lang.org/