14

概要

ハッシュが与えられた場合、使用するキーのリストに基づいてサブセットハッシュを作成する最も効率的な方法は何ですか?

h1 = { a:1, b:2, c:3 }        # Given a hash...
p foo( h1, :a, :c, :d )       # ...create a method that...
#=> { :a=>1, :c=>3, :d=>nil } # ...returns specified keys...
#=> { :a=>1, :c=>3 }          # ...or perhaps only keys that exist

詳細

Sequelデータベースツールキットを使用すると、ハッシュを渡すことでモデルインスタンスを作成または更新できます。

foo = Product.create( hash_of_column_values )
foo.update( another_hash )

Sinatra Webフレームワークは、paramsフォーム変数、クエリ文字列パラメーター、およびルート一致を含む名前付きハッシュを利用できるようにします。

データベース列と同じ名前のフィールドのみを保持するフォームを作成し、それをこのルートに投稿すると、すべてが非常に便利に機能します。

post "/create_product" do
  new_product = Product.create params
  redirect "/product/#{new_product.id}"
end

ただし、これは壊れやすく危険です。悪意のあるハッカーが、変更を意図していない列を含むフォームを投稿して更新する可能性があるため、危険です。このルートで同じフォームを使用すると機能しないため、壊れやすいです。

post "/update_product/:foo" do |prod_id|
  if product = Product[prod_id]
    product.update(params)
    #=> <Sequel::Error: method foo= doesn't exist or access is restricted to it>
  end
end

したがって、堅牢性とセキュリティのために、これを記述できるようにしたいと思います。

post "/update_product/:foo" do |prod_id|
  if product = Product[prod_id]
    # Only update two specific fields
    product.update(params.slice(:name,:description))
    # The above assumes a Hash (or Sinatra params) monkeypatch
    # I will also accept standalone helper methods that perform the same
  end
end

...より冗長で非DRYオプションの代わりに:

post "/update_product/:foo" do |prod_id|
  if product = Product[prod_id]
    # Only update two specific fields
    product.update({
      name:params[:name],
      description:params[:description]
    })
  end
end

更新:ベンチマーク

(現在の)実装のベンチマークの結果は次のとおりです。

                    user     system      total        real
sawa2           0.250000   0.000000   0.250000 (  0.269027)
phrogz2         0.280000   0.000000   0.280000 (  0.275027)
sawa1           0.297000   0.000000   0.297000 (  0.293029)
phrogz3         0.296000   0.000000   0.296000 (  0.307031)
phrogz1         0.328000   0.000000   0.328000 (  0.319032)
activesupport   0.639000   0.000000   0.639000 (  0.657066)
mladen          1.716000   0.000000   1.716000 (  1.725172)

@sawaによる2番目の回答は、すべての中で最速であり、私のtapベースの実装の前にあります(彼の最初の回答に基づく)。チェックを追加することを選択すると、has_key?時間がほとんど追加されず、ActiveSupportの2倍以上の速度が得られます。

ベンチマークコードは次のとおりです。

h1 = Hash[ ('a'..'z').zip(1..26) ]
keys = %w[a z c d g A x]
n = 60000

require 'benchmark'
Benchmark.bmbm do |x|
  %w[ sawa2 phrogz2 sawa1 phrogz3 phrogz1 activesupport mladen ].each do |m|
    x.report(m){ n.times{ h1.send(m,*keys) } }
  end
end
4

5 に答える 5

19

active_supportが提供するsliceメソッドを使用します

require 'active_support/core_ext/hash/slice'
{a: 1, b: 2, c: 3}.slice(:a, :c)                  # => {a: 1, c: 3}

もちろん、必ずgemfileを更新してください。

gem 'active_support'
于 2011-04-13T19:27:56.863 に答える
5

気が変わった。前のものは良くないようです。

class Hash
  def slice1(*keys)
    keys.each_with_object({}){|k, h| h[k] = self[k]}
  end
  def slice2(*keys)
    h = {}
    keys.each{|k| h[k] = self[k]}
    h
  end
end
于 2011-04-13T17:35:52.323 に答える
3

Sequelには、更新時に特定の列のみを選択するためのサポートが組み込まれています。

product.update_fields(params, [:name, :description])

ただし、:nameまたは:descriptionがparamsに存在しない場合、これはまったく同じことにはなりません。ただし、ユーザーがフォームを使用することを期待している場合、それは問題にはなりません。

いつでもupdate_fieldsを展開して、ハッシュに存在しない場合に値をスキップするオプションを含むオプションハッシュを取得できます。まだリクエストを受け取っていません。

于 2011-04-14T19:41:13.943 に答える
2

多分

class Hash
  def slice *keys
    select{|k| keys.member?(k)}
  end
end

または、ActiveSupportをコピーするだけでもかまいませんがHash#slice、少し堅牢に見えます。

于 2011-04-13T17:50:37.550 に答える
0

これが私の実装です。私はベンチマークを行い、より高速な(または十分にエレガントな)ソリューションを受け入れます。

# Implementation 1
class Hash
  def slice(*keys)
    Hash[keys.zip(values_at *keys)]
  end
end

# Implementation 2
class Hash
  def slice(*keys)
    {}.tap{ |h| keys.each{ |k| h[k]=self[k] } }
  end
end

# Implementation 3 - silently ignore keys not in the original
class Hash
  def slice(*keys)
    {}.tap{ |h| keys.each{ |k| h[k]=self[k] if has_key?(k) } }
  end
end
于 2011-04-13T17:11:49.017 に答える