use regex as keywords in ruby hash - ruby

I have hash mapping
H = {
"alc" => "AL",
"alco" => "AL",
"alcoh" => "AL",
"alcohol" => "AL",
"alcoholic" => "AL",
}
now I want to use a regex to represent all the keys, like
H={
/^alc/ => "AL"
}
later on I want to use H["alc"] or H["alco"] to retrieve the value. But if I use regex, I can not get the value properly. What should I do?

class MyHash < Hash
def [](a)
self.select {|k| k =~ a}.shift[1]
end
end
result = MyHash.new
result[/^alc/] = "AL"
puts result['alcohol'] #=> 'AL'
I would create a subclass of the hash and then over write this method. This way you can still keep the regular hash functionality elsewhere.

Make a subclass, inherit Hash class and override [] behaviour so it checks whether it matches each regex in your hash and returns the corresponding value.

Related

Remove nil values from hash

I am looking to remove keys from hash that have nil value. article is a class storing each article, and attributes method stores the article as hash.
Expected Result:
{"articles":[{"results":[{"author":null,"title":"Former bar manager jailed for preying on homeless 14-year-old girl","summary":"<p><img src=\"http://images.theage.com.au/2015/08/24/6790912/Thumbnail999662740gisd08image.related.thumbnail.320x214.gj68pg.png1440386418031.jpg-90x60.jpg\" width=\"90\" height=\"60\" style=\"float:left;margin:4px;border:0px\"/></p>A man who preyed on a 14-year-old girl he came across living on the streets of Wodonga has been jailed for nine months.","images":null,"source":null,"date":"Mon, 24 Aug 2015 03:20:21 +0000","guid":"<guid isPermaLink=\"false\">gj68pg</guid>","link":"http://www.theage.com.au/victoria/former-bar-manager-jailed-for-preying-on-homeless-14yearold-girl-20150824-gj68pg.html","section":null,"item_type":null,"updated_date":null,"created_date":null,"material_type_facet":null,"abstract":null,"byline":null,"kicker":null}]}]}
Looking to remove null values from the above output.
def attributes
hash = {
"author" => #author,
"title" => #title,
"summary" => #summary,
"images" => #images,
"source" => #source,
"date" => #date
}
hash = {}
count = 0
article.attributes.each do |key,value|
if value == nil
hash[count] = article.attributes.delete(key)
count += 1
end
end
hash.to_json
The result is as below:
{"0":null,"1":null,"2":null,"3":null,"4":null,"5":null,"6":null,"7":null,"8":null,"9":null,"10":null}
If you're on Ruby >= 2.4.6, you can use compact.
https://apidock.com/ruby/v2_4_6/Hash/compact
Example:
hash = {:foo => nil, :bar => "bar"}
=> {:foo=>nil, :bar=>"bar"}
hash.compact
=> {:bar=>"bar"}
hash
=> {:foo=>nil, :bar=>"bar"}
There's also compact! which removes nil values, or nil if no values in the hash were nil (from version >= 2.5.5).
https://apidock.com/ruby/v2_4_6/Hash/compact%21
Example:
hash
=> {:foo=>nil, :bar=>"bar"}
hash.compact!
=> {:bar=>"bar"}
hash
=> {:bar=>"bar"}
hash.compact!
=> nil
How about trying:
hash = article.attributes.select {|k, v| v }
If the value is false or nil, the attribute will be ignored.
If you want to keep the false value and only eliminate nil, you could run:
hash = article.attributes.select {|k, v| !v.nil? }
You can remove all the nil values from a hash namely "h":
h.delete_if { |k, v| v.nil? }
and you can use this one to remove empty values, too:
h.delete_if { |k, v| v.nil? || v.empty? }
In my case I use refinements to Hash class
module HashUtil
refine Hash do
# simple but can not adapt to nested hash
def squeeze
select{|_, v| !v.nil? }
end
# complex but can adapt to nested hash
def squeeze_deep
each_with_object({}) do |(k, v), squeezed_hash|
if v.is_a?(Hash)
squeezed_hash[k] = v.squeeze
else
squeezed_hash[k] = v unless v.nil?
end
end
end
end
end
class Article
using HashUtil
def attributes
hash = {
"author" => #author,
"title" => #title,
"summary" => #summary,
"images" => #images,
"source" => #source,
"date" => #date
}
hash.squeeze
end
end
The given input is valid JSON, and the question has the JSON tag, so I'd like to offer a generic solution to the problem of recursively eliminating keys in JSON objects, wherever they occur, and no matter what the input JSON may be.
The solution is written in a new JSON-oriented programming language, jq, but even if you cannot use jq, the solution is so brief and elegant that it may suggest an implementation to the general problem in your language of choice.
Here it is - a one-liner:
walk( if type == "object" then with_entries( select(.value != null) ) else . end)
This presupposes jq version 1.5 (see https://stedolan.github.io/jq/).
If you have an older version of jq, the definition of walk/1 can easily be added. (See e.g. Transforming the name of key deeper in the JSON structure with jq)

Getting an array of hash values given specific keys

Given certain keys, I want to get an array of values from a hash (in the order I gave the keys). I had done this:
class Hash
def values_for_keys(*keys_requested)
result = []
keys_requested.each do |key|
result << self[key]
end
return result
end
end
I modified the Hash class because I do plan to use it almost everywhere in my code.
But I don't really like the idea of modifying a core class. Is there a builtin solution instead? (couldn't find any, so I had to write this).
You should be able to use values_at:
values_at(key, ...) → array
Return an array containing the values associated with the given keys. Also see Hash.select.
h = { "cat" => "feline", "dog" => "canine", "cow" => "bovine" }
h.values_at("cow", "cat") #=> ["bovine", "feline"]
The documentation doesn't specifically say anything about the order of the returned array but:
The example implies that the array will match the key order.
The standard implementation does things in the right order.
There's no other sensible way for the method to behave.
For example:
>> h = { :a => 'a', :b => 'b', :c => 'c' }
=> {:a=>"a", :b=>"b", :c=>"c"}
>> h.values_at(:c, :a)
=> ["c", "a"]
i will suggest you do this:
your_hash.select{|key,value| given_keys.include?(key)}.values

Converting nested hash keys from CamelCase to snake_case in Ruby

I'm trying to build an API wrapper gem, and having issues with converting hash keys to a more Rubyish format from the JSON the API returns.
The JSON contains multiple layers of nesting, both Hashes and Arrays. What I want to do is to recursively convert all keys to snake_case for easier use.
Here's what I've got so far:
def convert_hash_keys(value)
return value if (not value.is_a?(Array) and not value.is_a?(Hash))
result = value.inject({}) do |new, (key, value)|
new[to_snake_case(key.to_s).to_sym] = convert_hash_keys(value)
new
end
result
end
The above calls this method to convert strings to snake_case:
def to_snake_case(string)
string.gsub(/::/, '/').
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
gsub(/([a-z\d])([A-Z])/,'\1_\2').
tr("-", "_").
downcase
end
Ideally, the result would be similar to the following:
hash = {:HashKey => {:NestedHashKey => [{:Key => "value"}]}}
convert_hash_keys(hash)
# => {:hash_key => {:nested_hash_key => [{:key => "value"}]}}
I'm getting the recursion wrong, and every version of this sort of solution I've tried either doesn't convert symbols beyond the first level, or goes overboard and tries to convert the entire hash, including values.
Trying to solve all this in a helper class, rather than modifying the actual Hash and String functions, if possible.
Thank you in advance.
If you use Rails:
Example with hash: camelCase to snake_case:
hash = { camelCase: 'value1', changeMe: 'value2' }
hash.transform_keys { |key| key.to_s.underscore }
# => { "camel_case" => "value1", "change_me" => "value2" }
source:
http://apidock.com/rails/v4.0.2/Hash/transform_keys
For nested attributes use deep_transform_keys instead of transform_keys, example:
hash = { camelCase: 'value1', changeMe: { hereToo: { andMe: 'thanks' } } }
hash.deep_transform_keys { |key| key.to_s.underscore }
# => {"camel_case"=>"value1", "change_me"=>{"here_too"=>{"and_me"=>"thanks"}}}
source: http://apidock.com/rails/v4.2.7/Hash/deep_transform_keys
You need to treat Array and Hash separately. And, if you're in Rails, you can use underscore instead of your homebrew to_snake_case. First a little helper to reduce the noise:
def underscore_key(k)
k.to_s.underscore.to_sym
# Or, if you're not in Rails:
# to_snake_case(k.to_s).to_sym
end
If your Hashes will have keys that aren't Symbols or Strings then you can modify underscore_key appropriately.
If you have an Array, then you just want to recursively apply convert_hash_keys to each element of the Array; if you have a Hash, you want to fix the keys with underscore_key and apply convert_hash_keys to each of the values; if you have something else then you want to pass it through untouched:
def convert_hash_keys(value)
case value
when Array
value.map { |v| convert_hash_keys(v) }
# or `value.map(&method(:convert_hash_keys))`
when Hash
Hash[value.map { |k, v| [underscore_key(k), convert_hash_keys(v)] }]
else
value
end
end
I use this short form:
hash.transform_keys(&:underscore)
And, as #Shanaka Kuruwita pointed out, to deeply transform all the nested hashes:
hash.deep_transform_keys(&:underscore)
The accepted answer by 'mu is too short' has been converted into a gem, futurechimp's Plissken:
https://github.com/futurechimp/plissken/blob/master/lib/plissken/ext/hash/to_snake_keys.rb
This looks like it should work outside of Rails as the underscore functionality is included.
Use deep_transform_keys for recursive conversion.
transform_keys only convert it in high level
hash = { camelCase: 'value1', changeMe: {nestedMe: 'value2'} }
hash.transform_keys { |key| key.to_s.underscore }
# => { "camel_case" => "value1", "change_me" => {nestedMe: 'value2'} }
deep_transform_keys will go deeper and transform all nested hashes as well.
hash = { camelCase: 'value1', changeMe: {nestedMe: 'value2'} }
hash.deep_transform_keys { |key| key.to_s.underscore }
# => { "camel_case" => "value1", "change_me" => {nested_me: 'value2'} }
If you're using the active_support library, you can use deep_transform_keys! like so:
hash.deep_transform_keys! do |key|
k = key.to_s.snakecase rescue key
k.to_sym rescue key
end
This works both to camelCase and snake_case deep nested keys of an object, which is very useful for a JSON API:
def camelize_keys(object)
deep_transform_keys_in_object!(object) { |key| key.to_s.camelize(:lower) }
end
def snakecase_keys(object)
deep_transform_keys_in_object!(object) { |key| key.to_s.underscore.to_sym }
end
def deep_transform_keys_in_object!(object, &block)
case object
when Hash
object.keys.each do |key|
value = object.delete(key)
object[yield(key)] = deep_transform_keys_in_object!(value, &block)
end
object
when Array
object.map! { |e| deep_transform_keys_in_object!(e, &block) }
else
object
end
end

Ruby value of a hash key?

I've got a list of values that are in a Ruby hash. Is there a way to check the value of the key and if it equals "X", then do "Y"?
I can test to see if the hash has a key using hash.has_key?, but now I need to know if hash.key == "X" then...?
Hashes are indexed using the square brackets ([]). Just as arrays. But instead of indexing with the numerical index, hashes are indexed using either the string literal you used for the key, or the symbol.
So if your hash is similar to
hash = { "key1" => "value1", "key2" => "value2" }
you can access the value with
hash["key1"]
or for
hash = { :key1 => "value1", :key2 => "value2"}
or the new format supported in Ruby 1.9
hash = { key1: "value1", key2: "value2" }
you can access the value with
hash[:key1]
This question seems to be ambiguous.
I'll try with my interpretation of the request.
def do_something(data)
puts "Found! #{data}"
end
a = { 'x' => 'test', 'y' => 'foo', 'z' => 'bar' }
a.each { |key,value| do_something(value) if key == 'x' }
This will loop over all the key,value pairs and do something only if the key is 'x'.
As an addition to e.g. #Intrepidd s answer, in certain situations you want to use fetch instead of []. For fetch not to throw an exception when the key is not found, pass it a default value.
puts "ok" if hash.fetch('key', nil) == 'X'
Reference: https://docs.ruby-lang.org/en/2.3.0/Hash.html .
How about this?
puts "ok" if hash_variable["key"] == "X"
You can access hash values with the [] operator
It seems that your question is maybe a bit ambiguous.
If “values” in the first sentence means any generic value (i.e. object, since everything in Ruby can be viewed as an object), then one of the other answers probably tells you what you need to know (i.e. use Hash#[] (e.g. hash[some_key]) to find the value associated with a key).
If, however, “values” in first sentence is taken to mean the value part of the “key, value pairs” (as are stored in hashes), then your question seems like it might be about working in the other direction (key for a given value).
You can find a key that leads to a certain value with Hash#key.
ruby-1.9.2-head :001 > hash = { :a => '1', :b => :two, :c => 3, 'bee' => :two }
=> {:a=>"1", :b=>:two, :c=>3, "bee"=>:two}
ruby-1.9.2-head :002 > a_value = :two
=> :two
ruby-1.9.2-head :003 > hash.key(a_value)
=> :b
If you are using a Ruby earlier than 1.9, you can use Hash#index.
When there are multiple keys with the desired value, the method will only return one of them. If you want all the keys with a given value, you may have to iterate a bit:
ruby-1.9.2-head :004 > hash[:b] == hash['bee']
=> true
ruby-1.9.2-head :005 > keys = hash.inject([]) do # all keys with value a_value
ruby-1.9.2-head :006 > |l,kv| kv[1] == a_value ? l << kv[0] : l
ruby-1.9.2-head :007?> end
=> [:b, "bee"]
Once you have a key (the keys) that lead to the value, you can compare them and act on them with if/unless/case expressions, custom methods that take blocks, et cetera. Just how you compare them depends on the kind of objects you are using for keys (people often use strings and symbols, but Ruby hashes can use any kind of object as keys (as long as they are not modified while they serve as keys)).
I didn't understand your problem clearly but I think this is what you're looking for(Based on my understanding)
person = {"name"=>"BillGates", "company_name"=>"Microsoft", "position"=>"Chairman"}
person.delete_if {|key, value| key == "name"} #doing something if the key == "something"
Output: {"company_name"=>"Microsoft", "position"=>"Chairman"}

Ruby Style: How to check whether a nested hash element exists

Consider a "person" stored in a hash. Two examples are:
fred = {:person => {:name => "Fred", :spouse => "Wilma", :children => {:child => {:name => "Pebbles"}}}}
slate = {:person => {:name => "Mr. Slate", :spouse => "Mrs. Slate"}}
If the "person" doesn't have any children, the "children" element is not present. So, for Mr. Slate, we can check whether he has parents:
slate_has_children = !slate[:person][:children].nil?
So, what if we don't know that "slate" is a "person" hash? Consider:
dino = {:pet => {:name => "Dino"}}
We can't easily check for children any longer:
dino_has_children = !dino[:person][:children].nil?
NoMethodError: undefined method `[]' for nil:NilClass
So, how would you check the structure of a hash, especially if it is nested deeply (even deeper than the examples provided here)? Maybe a better question is: What's the "Ruby way" to do this?
The most obvious way to do this is to simply check each step of the way:
has_children = slate[:person] && slate[:person][:children]
Use of .nil? is really only required when you use false as a placeholder value, and in practice this is rare. Generally you can simply test it exists.
Update: If you're using Ruby 2.3 or later there's a built-in dig method that does what's described in this answer.
If not, you can also define your own Hash "dig" method which can simplify this substantially:
class Hash
def dig(*path)
path.inject(self) do |location, key|
location.respond_to?(:keys) ? location[key] : nil
end
end
end
This method will check each step of the way and avoid tripping up on calls to nil. For shallow structures the utility is somewhat limited, but for deeply nested structures I find it's invaluable:
has_children = slate.dig(:person, :children)
You might also make this more robust, for example, testing if the :children entry is actually populated:
children = slate.dig(:person, :children)
has_children = children && !children.empty?
With Ruby 2.3 we'll have support for the safe navigation operator:
https://www.ruby-lang.org/en/news/2015/11/11/ruby-2-3-0-preview1-released/
has_children now could be written as:
has_children = slate[:person]&.[](:children)
dig is being added as well:
has_children = slate.dig(:person, :children)
Another alternative:
dino.fetch(:person, {})[:children]
You can use the andand gem:
require 'andand'
fred[:person].andand[:children].nil? #=> false
dino[:person].andand[:children].nil? #=> true
You can find further explanations at http://andand.rubyforge.org/.
One could use hash with default value of {} - empty hash. For example,
dino = Hash.new({})
dino[:pet] = {:name => "Dino"}
dino_has_children = !dino[:person][:children].nil? #=> false
That works with already created Hash as well:
dino = {:pet=>{:name=>"Dino"}}
dino.default = {}
dino_has_children = !dino[:person][:children].nil? #=> false
Or you can define [] method for nil class
class NilClass
def [](* args)
nil
end
end
nil[:a] #=> nil
Traditionally, you really had to do something like this:
structure[:a] && structure[:a][:b]
However, Ruby 2.3 added a feature that makes this way more graceful:
structure.dig :a, :b # nil if it misses anywhere along the way
There is a gem called ruby_dig that will back-patch this for you.
def flatten_hash(hash)
hash.each_with_object({}) do |(k, v), h|
if v.is_a? Hash
flatten_hash(v).map do |h_k, h_v|
h["#{k}_#{h_k}"] = h_v
end
else
h[k] = v
end
end
end
irb(main):012:0> fred = {:person => {:name => "Fred", :spouse => "Wilma", :children => {:child => {:name => "Pebbles"}}}}
=> {:person=>{:name=>"Fred", :spouse=>"Wilma", :children=>{:child=>{:name=>"Pebbles"}}}}
irb(main):013:0> slate = {:person => {:name => "Mr. Slate", :spouse => "Mrs. Slate"}}
=> {:person=>{:name=>"Mr. Slate", :spouse=>"Mrs. Slate"}}
irb(main):014:0> flatten_hash(fred).keys.any? { |k| k.include?("children") }
=> true
irb(main):015:0> flatten_hash(slate).keys.any? { |k| k.include?("children") }
=> false
This will flatten all the hashes into one and then any? returns true if any key matching the substring "children" exist.
This might also help.
dino_has_children = !dino.fetch(person, {})[:children].nil?
Note that in rails you can also do:
dino_has_children = !dino[person].try(:[], :children).nil? #
Here is a way you can do a deep check for any falsy values in the hash and any nested hashes without monkey patching the Ruby Hash class (PLEASE don't monkey patch on the Ruby classes, such is something you should not do, EVER).
(Assuming Rails, although you could easily modify this to work outside of Rails)
def deep_all_present?(hash)
fail ArgumentError, 'deep_all_present? only accepts Hashes' unless hash.is_a? Hash
hash.each do |key, value|
return false if key.blank? || value.blank?
return deep_all_present?(value) if value.is_a? Hash
end
true
end
Simplifying the above answers here:
Create a Recursive Hash method whose value cannot be nil, like as follows.
def recursive_hash
Hash.new {|key, value| key[value] = recursive_hash}
end
> slate = recursive_hash
> slate[:person][:name] = "Mr. Slate"
> slate[:person][:spouse] = "Mrs. Slate"
> slate
=> {:person=>{:name=>"Mr. Slate", :spouse=>"Mrs. Slate"}}
slate[:person][:state][:city]
=> {}
If you don't mind creating empty hashes if the value does not exists for the key :)
You can try to play with
dino.default = {}
Or for example:
empty_hash = {}
empty_hash.default = empty_hash
dino.default = empty_hash
That way you can call
empty_hash[:a][:b][:c][:d][:e] # and so on...
dino[:person][:children] # at worst it returns {}
Given
x = {:a => {:b => 'c'}}
y = {}
you could check x and y like this:
(x[:a] || {})[:b] # 'c'
(y[:a] || {})[:b] # nil
Thks #tadman for the answer.
For those who want perfs (and are stuck with ruby < 2.3), this method is 2.5x faster:
unless Hash.method_defined? :dig
class Hash
def dig(*path)
val, index, len = self, 0, path.length
index += 1 while(index < len && val = val[path[index]])
val
end
end
end
and if you use RubyInline, this method is 16x faster:
unless Hash.method_defined? :dig
require 'inline'
class Hash
inline do |builder|
builder.c_raw '
VALUE dig(int argc, VALUE *argv, VALUE self) {
rb_check_arity(argc, 1, UNLIMITED_ARGUMENTS);
self = rb_hash_aref(self, *argv);
if (NIL_P(self) || !--argc) return self;
++argv;
return dig(argc, argv, self);
}'
end
end
end
You can also define a module to alias the brackets methods and use the Ruby syntax to read/write nested elements.
UPDATE: Instead of overriding the bracket accessors, request Hash instance to extend the module.
module Nesty
def []=(*keys,value)
key = keys.pop
if keys.empty?
super(key, value)
else
if self[*keys].is_a? Hash
self[*keys][key] = value
else
self[*keys] = { key => value}
end
end
end
def [](*keys)
self.dig(*keys)
end
end
class Hash
def nesty
self.extend Nesty
self
end
end
Then you can do:
irb> a = {}.nesty
=> {}
irb> a[:a, :b, :c] = "value"
=> "value"
irb> a
=> {:a=>{:b=>{:c=>"value"}}}
irb> a[:a,:b,:c]
=> "value"
irb> a[:a,:b]
=> {:c=>"value"}
irb> a[:a,:d] = "another value"
=> "another value"
irb> a
=> {:a=>{:b=>{:c=>"value"}, :d=>"another value"}}
I don't know how "Ruby" it is(!), but the KeyDial gem which I wrote lets you do this basically without changing your original syntax:
has_kids = !dino[:person][:children].nil?
becomes:
has_kids = !dino.dial[:person][:children].call.nil?
This uses some trickery to intermediate the key access calls. At call, it will try to dig the previous keys on dino, and if it hits an error (as it will), returns nil. nil? then of course returns true.
You can use a combination of & and key? it is O(1) compared to dig which is O(n) and this will make sure person is accessed without NoMethodError: undefined method `[]' for nil:NilClass
fred[:person]&.key?(:children) //=>true
slate[:person]&.key?(:children)

Resources