Why can't I make a new hash from selected keys

2019-06-23 15:20发布

问题:

This has been bugging me for a while. It's not a difficult thing, but I don't know why there's no easy way to do it already, and I bet there is and I don't see it.

I just want to take a hash, like this:

cars = {:bob => 'Pontiac', :fred => 'Chrysler', 
        :lisa => 'Cadillac', :mary => 'Jaguar'}

and do something like

cars[:bob, :lisa]

and get

{:bob => 'Pontiac', :lisa => 'Cadillac'}

I did this, which works fine:

class Hash
  def pick(*keys)
    Hash[select { |k, v| keys.include?(k) }]
  end
end

ruby-1.8.7-p249 :008 > cars.pick(:bob, :lisa)
=> {:bob=>"Pontiac", :lisa=>"Cadillac"} 

There's obviously a zillion easy ways to do this, but I'm wondering if there's something built in I've missed, or a good an un-obvious reason it's not a standard and normal thing? Without it, I wind up using something like:

chosen_cars = {:bob => cars[:bob], :lisa => cars[:lisa]}

which isn't the end of the world, but it's not very pretty. It seems like this should be part of the regular vocabulary. What am I missing here?

(related questions, include this: Ruby Hash Whitelist Filter) (this blog post has precisely the same result as me, but again, why isn't this built in? http://matthewbass.com/2008/06/26/picking-values-from-ruby-hashes/ )

update:

I'm using Rails, which has ActiveSupport::CoreExtensions::Hash::Slice, which works exactly as I want it to, so problem solved, but still... maybe someone else will find their answer here :)

回答1:

Just to help others, some years after the fact:

Slice works nicely to do what I wanted.

> cars.slice(:bob, :lisa)
=> {:bob=>"Pontiac", :lisa=>"Cadillac"} 


回答2:

I've always thought that was a weird omission as well, but there really is no simple, standard method for that.

Your example above might be unnecessarily slow because it iterates over all the hash entries whether we need them or not, and then repeatedly searches through the keys parameter array. This code should be a bit faster (assuming that would ever matter -- and I haven't tried to benchmark it).

class Hash
  def pick(*keys)
    values = values_at(*keys)
    Hash[keys.zip(values)]
  end
end


回答3:

select deserves at least to be mentioned:

cars = {:bob => 'Pontiac', :fred => 'Chrysler', 
        :lisa => 'Cadillac', :mary => 'Jaguar'}
people = [:bob, :lisa]

p cars.select{|k, _| people.include?(k)}
#=> {:bob=>"Pontiac", :lisa=>"Cadillac"}


回答4:

Ruby makes it possible to add that feature without much pain:

class Hash
  alias old_accessor :[]

  def [](*key)
    key.is_a?(Array) ? self.dup.delete_if{|k, v| !key.include? k} : old_accessor(key)
  end
end

I hope this helps. I know it's not a built-in feature.



回答5:

{ height: '178cm', weight: '181lbs', salary: '$2/hour' }.select { |k,v| [:height, :weight].include?(k)  }


标签: ruby hash