LoginSignup
1
1

More than 5 years have passed since last update.

仕分メソッドaccountとmapの違い

Last updated at Posted at 2017-07-18

仕分メソッドaccountとは

各点が含まれる区間を戻す。発端はgroup_by のような処理で、要素数を保ったまま戻り値を得るメソッド(名称募集)

  • 各点
distances = [0, 20, 40, 60, 80, 100, 120, 126.153]`
  • 区間集合
sections = [
  {"距離" =>   0,     "高さ" => 80.650},
  {"距離" =>  40,     "高さ" => 78.650},
  {"距離" => 126.153, "高さ" => 76.065}
]
  • 計算結果
=> distances.account(sections.dup, "距離")
{"距離"=>0, :under=>{"距離"=>0, "高さ"=>80.65}, :over=>{"距離"=>40, "高さ"=>78.65}}
{"距離"=>20, :under=>{"距離"=>0, "高さ"=>80.65}, :over=>{"距離"=>40, "高さ"=>78.65}}
{"距離"=>40, :under=>{"距離"=>40, "高さ"=>78.65}, :over=>{"距離"=>126.153, "高さ"=>76.065}}
{"距離"=>60, :under=>{"距離"=>40, "高さ"=>78.65}, :over=>{"距離"=>126.153, "高さ"=>76.065}}
{"距離"=>80, :under=>{"距離"=>40, "高さ"=>78.65}, :over=>{"距離"=>126.153, "高さ"=>76.065}}
{"距離"=>100, :under=>{"距離"=>40, "高さ"=>78.65}, :over=>{"距離"=>126.153, "高さ"=>76.065}}
{"距離"=>120, :under=>{"距離"=>40, "高さ"=>78.65}, :over=>{"距離"=>126.153, "高さ"=>76.065}}
{"距離"=>126.153, :under=>{"距離"=>126.153, "高さ"=>76.065}, :over=>nil}

仕分account処理の考察

  • 特徴

    • 配列distancesとsectionsで順序(ここでは"距離")が結合基準(距離の小<大)
    • 結合基準が一致(==)の場合がmap。そのためmapとは本質的に異なる
  • 性能

    • 一点に対して区間集合の中のどの区間に属すか、検索すると各点数n × 区間数mの手間
    • 一方でaccountは判定毎に比較母数の区間数mが減るので、格段に手間が減る
    • 一つ一つ検索するよりも、まとめて与えて判定する方が良い
    • ... なのでそれなりに存在価値があるのではなかろうか。

実装はsort_by()を模倣して

 判定時の値をブロックの評価結果で指定

class Array
  def account(sectionlist, &block)
    return self.map{ |distance|
      while sectionlist.length > 0 do
        under = sectionlist.first()
        over  = sectionlist.length < 2 ? nil : sectionlist[1];
        if (over.nil? || distance < (block_given? ? yield(over): over))
          break {:key => distance, :under => under, :over => over}
        end
        sectionlist.shift();
      end
    }
  end
end

=begin
各点の高さを求めようとすると、accountとmapは本質的に異なるとはいえ、冗長。
=end
puts distances.account(sections.dup){|section| section["距離"]}

puts distances.account(sections.dup){|section| section["距離"]}.map{|distance, under, over|
  height = if over.nil?
             under["高さ"]
           else
             grad   = (over["高さ"] - under["高さ"])/(over["距離"] - under["距離"])

             under["高さ"] + grad * (distance - under["距離"]);
           end
  {"距離" => distance, "高さ" => height};
}

重み付け関数という位置づけがいいのかもしれない。

  def weighted(sectionlist, key, &block)
    return self.account(sectionlist){|section| section[key]}.map{|v|
      block_given? ? block.call(v[:key], v[:under], v[:over]) : v
    }
  end

puts
puts distances.weighted(sections.dup, "距離"){|distance, under, over|
  height = if over.nil?
   ... 以下略
  {"距離" => distance, "高さ" => height};
}
1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1