[#42564] [Ruby 1.9-Feature#4043][Open] グローバル関数current_classの提案 — Makoto Kishimoto <redmine@...>

Feature #4043: =E3=82=B0=E3=83=AD=E3=83=BC=E3=83=90=E3=83=AB=E9=96=A2=E6=95=

15 messages 2010/11/11
[#42774] Re: [Ruby 1.9-Feature#4043][Open] グローバル関数current_classの提案 — Yukihiro Matsumoto <matz@...> 2010/12/16

まつもと ゆきひろです

[#42834] Re: [Ruby 1.9-Feature#4043][Open] グローバル関数current_classの提案 — "KISHIMOTO, Makoto" <ksmakoto@...4u.or.jp> 2010/12/21

きしもとです

[#42835] Re: [Ruby 1.9-Feature#4043][Open] グローバル関数current_classの提案 — Yukihiro Matsumoto <matz@...> 2010/12/21

まつもと ゆきひろです

[#42838] Re: [Ruby 1.9-Feature#4043][Open] グローバル関数current_classの提案 — "KISHIMOTO, Makoto" <ksmakoto@...4u.or.jp> 2010/12/21

きしもとです

[#42845] Re: [Ruby 1.9-Feature#4043][Open] グローバル関数current_classの提案 — Yukihiro Matsumoto <matz@...> 2010/12/21

まつもと ゆきひろです

[#42577] Rubyのバグレポートのガイドライン — "Shota Fukumori (sora_h)" <sorah@...>

sora_hです。

11 messages 2010/11/15
[#42588] Re: Rubyのバグレポートのガイドライン — Yugui <yugui@...> 2010/11/18

2010/11/15 Shota Fukumori (sora_h) <sorah@tubusu.net>:

[#42638] Enumerable#categorize — Tanaka Akira <akr@...>

enumerable から hash を生成するメソッドとして

25 messages 2010/11/27
[#42643] Re: Enumerable#categorize — Yukihiro Matsumoto <matz@...> 2010/11/27

まつもと ゆきひろです

[ruby-dev:42654] Re: Enumerable#categorize

From: Tanaka Akira <akr@...>
Date: 2010-11-28 14:57:23 UTC
List: ruby-dev #42654
2010年11月28日0:46 Yukihiro Matsumoto <matz@ruby-lang.org>:

> 無理矢理、本題の 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]

In This Thread