Delete array of keys in Ruby Hash - ruby

How would I go about deleting an array of keys in a hash? For example, you can call:
hash.delete(some_key)
But how can I do this:
hash.delete([key1,key2,key3,...])
without needing to looping manually.

You can iterate over an array of keys and delete everyone of them:
[key1, key2, key3].each { |k| some_hash.delete k }
Can't remember any better solution.

This is exactly what you are looking for...
You can do it like this without looping through the array unnecessarily.
keys_to_delete = [key1, key2, key3]
hash_array.except!(*keys_to_delete)
The result is stored in hash_array

You can try to use Hash#delete_if:
delete_if deletes every key-value pair from hsh for which block evaluates to true.
array_hash.delete_if { |key, _| [key1, key2, key3].include? key }
UPDATE
If you don't want to iterate over array of keys, you can use Set instead of Array (since Set uses Hash as storage include? is O(1)):
require 'set'
keys = [key1,key2,key3].to_set
array_hash.delete_if { |key, _| keys.include? key }

Maybe it's worth to make a method
class Hash
def delete_by_keys *keys
keys.each{|k| delete(k)}
end
end
hash_array.delete_by_keys(key1,key2,..)

ActiveSupport (part of Rails) implements exactly this, as Hash#except and Hash#except!
def except!(*keys)
keys.each { |key| delete(key) }
self
end
See http://apidock.com/rails/v4.0.2/Hash/except%21

Related

sort ruby hash by specific key

I have a ruby hash like this
{"id"=>62, "name"=>"Wine and Spirits"}
{"id"=>63, "name"=>"Tobacco"}
{"id"=>64, "name"=>"Printing"}
{"id"=>65, "name"=>"Information Services"}
{"id"=>66, "name"=>"Business Supplies and Equipment"}
How do I sort this by name? I tried
categories.sort_by {|_key, value, _key1, value1| value1}
But that did not work
Assuming you want to sort an array of hashes:
def sort_by_name(hashes)
hashes.sort_by { |h| h["name"] }
end

Method to get value from a hash from array of keys in Ruby

I am trying to write a function which returns the value of a hash key, when provided with an array of keys (and 'nil' if the key doesn't exist).
Consider the hash:
my_hash = {
font_size: 10,
font_family: "Arial",
boo: {
name: 'blah'
}
}
A stub of the method might be:
def get_value(hash, array_of_keys)
...
end
The reason being that I can access different keys in the hash which may not actually exist. So for example, I want 'blah', normally I would call my_hash[:boo][:name] however it may not already exist or it may be very deep. What I would like to do is have my function, which I could call with get_value(my_hash, [:boo, :name]) so that I can test that each key exists (so I don't get exceptions if any of them do not exist, and where it doesn't matter how 'deep' the value might be).
In my case, its not feasible to check the existence of each value I require (I might need to test existence of 10 consecutive keys) I simply need a function that I can say which value I need and get the value if it exists, and nil if it doesn't (so an exception isn't thrown trying to retrieve it).
I gave it a shot, trying to use a recursive function that 'pop's the first element in the array each time it loops but i couldn't workout how to return my value.
def get_value(val, arr)
if arr.count > 1
arr.each do |a|
get_value val[a], arr.pop
end
else
val[a]
end
end
get_value s, [:boo, :name]
This is my attempt (which doesn't work obviously) - can anyone help me solve this problem, or have an alternate solution that might be more elegant? A couple of points:
The keys are not guaranteed to exist, and I'd like to return nil in
those cases, and
The hash could potentially be very deep so it needs to handle any hash and any number of keys in the array (which is why I thought recursion might be the answer, but now I'm not so sure).
[:font_size].inject(my_hash){|h, k| h.to_h[k]} #=> 10
[:boo, :name].inject(my_hash){|h, k| h.to_h[k]} #=> "blah"
[:boo, :foo].inject(my_hash){|h, k| h.to_h[k]} #=> nil
class Hash
def get_value(array_of_keys)
return nil unless array_of_keys.is_a? Array
return nil if array_of_keys.empty?
return nil unless self.has_key? array_of_keys.first
if self[array_of_keys.first].is_a? Hash
self[array_of_keys.first].get_value(array_of_keys[1..-1])
else
self[array_of_keys.first]
end
end
end
my_hash = {
font_size: 10,
font_family: "Arial",
boo: {
name: 'blah'
} ,
radley: {
one: {
more: {
time: 'now'
}
}
}
}
p my_hash.get_value [:boo, :name]
p my_hash.get_value [:radley, :one, :more, :time]
p my_hash.get_value [:radley, :one, :other, :time]
Explanation
I'm adding the method directly to the Hash class so that you can call it as an instance method on existing hashes (makes for a nicer usage).
The method takes your array, returns nil if the argument is not actually an array, if the array is empty, of if the Hash doesn't contain a key matching the first element of the array.
Next, check to see if the value of that key is itself a Hash.
If so, call this very method on that Hash, with the rest of the array!
If the value is anything but a hash, return that value.
My stab at it, using recursion:
def get_value(hash, keys_array)
key = keys_array.shift
if hash.has_key? key
if hash[key].is_a?(Hash) && keys_array.size >= 1
get_value(hash[key], keys_array)
else
hash[key]
end
else
nil
end
end
I remove the first key from the keys_array
I check if that key exists in the Hash
If yes I check if the value for that key is a Hash and that we still have keys left in the array, if that's the case I call the method with that value and the array of keys left
If it's not a Hash, I just return the value
If the key does not exist, return nil
Nice problem by the way :)

How do I subgroup this hash that has already been grouped?

I have a set of word strings which I am turning into a hash, grouped by the size of the string. I am doing this by:
hash = set.group_by(&:size)
resulting in
hash = {5=>[apple, andys, throw, balls], 7=>[bananas, oranges]}
I want to further group the hash values by first letter, so the the end results looks like:
hash = {5=>{a=>[apple, andys],b=>[balls],t=>[throw]}, 7=>{b=>[bananas], o=>[oranges]}}
I tried putting
hash.each_value do | value |
value = value.group_by(&:chr)
end
after the first group_by but that only seems to return the original hash. I am admittedly a ruby beginner so I'm not sure if I could do this in one fell swoop, or exactly how (&:size) notation works, if I were asked to write it out. Thoughts?
To update your hash you need to do like this
hash.each do |key, value|
hash[key] = value.group_by(&:chr)
end
I'd keep the whole computation functional:
>> Hash[set.group_by(&:size).map { |k, vs| [k, vs.group_by(&:chr)] }]
=> {5=>{"a"=>["apple", "andys"], "t"=>["throw"], "b"=>["balls"]},
7=>{"b"=>["bananas"], "o"=>["oranges"]}}

Idiomatic way of detecting duplicate keys in Ruby?

I've just noticed that Ruby doesn't raise an exception or even supply a warning if you supply duplicate keys to a hash:
$VERBOSE = true
key_value_pairs_with_duplicates = [[1,"a"], [1, "b"]]
# No warning produced
Hash[key_value_pairs_with_duplicates] # => {1=>"b"}
# Also no warning
hash_created_by_literal_with_duplicate_keys = {1 => "a", 1=> "b"} # => {1=>"b"}
For key_value_pairs_with_duplicates, I could detect duplicate keys by doing
keys = key_value_pairs_with_duplicates.map(&:first)
raise "Duplicate keys" unless keys.uniq == keys
Or by doing
procedurally_produced_hash = {}
key_value_pairs_with_duplicates.each do |key, value|
raise "Duplicate key" if procedurally_produced_hash.has_key?(key)
procedurally_produced_hash[key] = value
end
Or
hash = Hash[key_value_pairs_with_duplicates]
raise "Duplicate keys" unless hash.length == key_value_pairs_with_duplicates.length
But is there an idiomatic way to do it?
Hash#merge takes an optional block to define how to handle duplicate keys.
http://www.ruby-doc.org/core-1.9.3/Hash.html#method-i-merge
Taking advantage of the fact this block is only called on duplicate keys:
>> a = {a: 1, b: 2}
=> {:a=>1, :b=>2}
>> a.merge(c: 3) { |key, old, new| fail "Duplicate key: #{key}" }
=> {:a=>1, :b=>2, :c=>3}
>> a.merge(b: 10, c: 3) { |key, old, new| fail "Duplicate key: #{key}" }
RuntimeError: Duplicate key: b
I think there are two idiomatic ways to handle this:
Use one of the Hash extensions that allow multiple values per key, or
Extend Hash (or patch w/ flag method) and implement []= to throw a dupe key exception.
You could also just decorate an existing hash with the []= that throws, or alias_method--either way, it's straight-forward, and pretty Ruby-ish.
I would simply build a hash form the array, checking for a value before overwriting a key. This way it avoid creating any unnecessary temporary collections.
def make_hash(key_value_pairs_with_duplicates)
result = {}
key_value_pairs_with_duplicates.each do |pair|
key, value = pair
raise "Duplicate key" if result.has_key?(key)
result[key] = value
end
result
end
But no, I don't think there is an "idiomatic" way to doing this. It just follows the last in rule, and if you don't like that it's up to you to fix it.
In the literal form you are probably out of luck. But in the literal form why would you need to validate this? You are not getting it from a dynamic source if it's literal, so if you choose to dupe keys, it's your own fault. Just, uh... don't do that.
In other answers I've already stated my opinion that Ruby needs a standard method to build a hash from an enumerable. So, as you need your own abstraction for the task anyway, let's just take Facets' mash with the implementation you like the most (Enumerable#inject + Hash#update looks good to me) and add the check:
module Enumerable
def mash
inject({}) do |hash, item|
key, value = block_given? ? yield(item) : item
fail("Repeated key: #{key}") if hash.has_key?(key) # <- new line
hash.update(key => value)
end
end
end
I think most people here overthink the problem. To deal with duplicate keys, I'd simply do this:
arr = [ [:a,1], [:b,2], [:c,3] ]
hsh = {}
arr.each do |k,v|
raise("Whoa! I already have :#{k} key.") if hsh.has_key?(k)
x[k] = v
end
Or make a method out of this, maybe even extend a Hash class with it. Or create a child of Hash class (UniqueHash?) which would have this functionality by default.
But is it worth it? (I don't think so.) How often do we need to deal with duplicate keys in hash like this?
Latest Ruby versions do supply a warning when duplicating a key. However they still go ahead and re-assign the duplicate's value to the key, which is not always desired behaviour. IMO, the best way to deal with this is to override the construction/assignment methods. E.g. to override #[]=
class MyHash < Hash
def []=(key,val)
if self.has_key?(key)
puts("key: #{key} already has a value!")
else
super(key,val)
end
end
end
So when you run:
h = MyHash.new
h[:A] = ['red']
h[:B] = ['green']
h[:A] = ['blue']
it will output
key: A already has a value!
{:A=>["red"], :B=>["green"]}
Of course you can tailor the overridden behaviour any which way you want.
I would avoid using an array to model an hash at all. In other words, don't construct the array of pairs in the first place. I'm not being facetious or dismissive. I'm speaking as someone who has used arrays of pairs and (even worse) balanced arrays many times, and always regretted it.

Search ruby hash for empty value

I have a ruby hash like this
h = {"a" => "1", "b" => "", "c" => "2"}
Now I have a ruby function which evaluates this hash and returns true if it finds a key with an empty value. I have the following function which always returns true even if all keys in the hash are not empty
def hash_has_blank(hsh)
hsh.each do |k,v|
if v.empty?
return true
end
end
return false
end
What am I doing wrong here?
Try this:
def hash_has_blank hsh
hsh.values.any? &:empty?
end
Or:
def hash_has_blank hsh
hsh.values.any?{|i|i.empty?}
end
If you are using an old 1.8.x Ruby
I hope you're ready to learn some ruby magic here. I wouldn't define such a function globally like you did. If it's an operation on a hash, than it should be an instance method on the Hash class you can do it like this:
class Hash
def has_blank?
self.reject{|k,v| !v.nil? || v.length > 0}.size > 0
end
end
reject will return a new hash with all the empty strings, and than it will be checked how big this new hash is.
a possibly more efficient way (it shouldn't traverse the whole array):
class Hash
def has_blank?
self.values.any?{|v| v.nil? || v.length == 0}
end
end
But this will still traverse the whole hash, if there is no empty value
I've changed the empty? to !nil? || length >0 because I don't know how your empty method works.
If you just want to check if any of the values is an empty string you could do
h.has_value?('')
but your function seems to work fine.
I'd consider refactoring your model domain. Obviously the hash represents something tangible. Why not make it an object? If the item can be completely represented by a hash, you may wish to subclass Hash. If it's more complicated, the hash can be an attribute.
Secondly, the reason for which you are checking blanks can be named to better reflect your domain. You haven't told us the "why", but let's assume that your Item is only valid if it doesn't have any blank values.
class MyItem < Hash
def valid?
!invalid?
end
def invalid?
values.any?{|i| i.empty?}
end
end
The point is, if you can establish a vocabulary that makes sense in your domain, your code will be cleaner and more understandable. Using a Hash is just a means to an end and you'd be better off using more descriptive, domain-specific terms.
Using the example above, you'd be able to do:
my_item = MyItem["a" => "1", "b" => "", "c" => "2"]
my_item.valid? #=> false

Resources