keys of a hash that loads from a json string - ruby

We know that Ruby has a feature of symbol, typically a symbol is used as a hash key that save space vs a string object. Say:
myhash[:mykey] = "myvalue"
But if I load a hash from json string, say:
str = '{"mykey": "myvalue"}'
myhash = JSON.parse(str)
Then I must use string key to access the hash:
puts myhash["mykey"] # myvalue
Is this reasonable? Why JSON.parse just put symbol for hash keys?

Returning keys as strings is JSON default behavior. You can override by providing additional symbolize_names argument.
str = '{"mykey": "myvalue"}'
JSON.parse(str)
#=> {"mykey"=>"myvalue"}
JSON.parse(str, {:symbolize_names => true})
#=> {:mykey=>"myvalue"}
As #Matt said, in his comment, if the key happens to have whitespace (eg: my key ), it will key it as- :"my key".

Related

How to parse on a hash of symbols

I tried to downcase a parsed option to match only symbols in lower-case format because, when I match an uppercase OR mixed-case word my parser returns a nil value.
I don't want to have a hash like [:ens, :ENS, :eNS, :enS ...]:
opts.on("-i", "--instance [INSTANCE]", [:ens, :etu], "Selectionnez l'instance de Gitlab (etu, ens)") do |instance|
# puts instance.inspect
Options[:instance] = instance
end
Example:
./gitlabCollect -t my_token -k my_keyword -i ENS
will not work because the hash returned is:
{:token=>"my_token", :keyword=>"my_keyword", :instance=>nil}
It seems you don't understand some things about symbols, they map to hashes based on their characters. :sss != :Sss. Try :sss.hash and :Sss.hash in irb/pry. You will see they map to two different numbers. If you need to be case indiscriminate, you can always convert to a string and upcase it.
symbol1 = :sss
symbol2 = :Sss
symbol1.to_s.upcase == symbol2.to_s.upcase #true

Ruby regex string into key value pairs

I have a String like this:
price<=>656000<br>bathrooms<=>1<br>bedrooms<=>3<br>pets<=>1<br>surface<=>60<br>brokerfree<=>1
model<=>opel/corsa<br>mileage<=>67000<br>vinnumber<=>unknown<br>price<=>145000<br>year<=>2010<br>condition<=>2<br>transmission<=>unknown<br>cartype<=>1
I want a Hash:
:model => 'opel/corsa'
etc etc... the string is variable so this is also valid:
year<=>2015<br>condition<=>1<br>price<=>2100mileage<=>22000<br>price<=>120000<br>year<=>2012<br>condition<=>2
or this
price<=>656000<br>bathrooms<=>1<br>bedrooms<=>3<br>pets<=>1<br>surface<=>60<br>brokerfree<=>1
model<=>opel/corsa<br>mileage<=>67000<br>vinnumber<=>unknown<br>price<=>145000<br>year<=>2010<br>condition<=>2<br>transmission<=>unknown<br>cartype<=>1
You don't need a regex. You can use plain ruby methods.
array = string.split('<br>')
hash = Hash[array.map {|el| el.split('<=>') }]
You can also use .to_h method of Array, see the following example.
string = "model<=>opel/corsa<br>mileage<=>67000<br>vinnumber<=>unknown<br>price<=>145000<br>year<=>2010<br>condition<=>2<br>transmission<=>unknown<br>cartype<=>1"
hash = string.split('<br>').map{|a| a.split('<=>')}.to_h
## OUTPUT
{"model"=>"opel/corsa", "mileage"=>"67000", "vinnumber"=>"unknown", "price"=>"145000", "year"=>"2010", "condition"=>"2", "transmission"=>"unknown", "cartype"=>"1"}
If str is your string, you can construct your hash as follows:
Hash[*str.split(/<=>|<br>/)]
#=> {"price"=>"145000", "bathrooms"=>"1", "bedrooms"=>"3", "pets"=>"1",
# "surface"=>"60", "brokerfree"=>"1", "model"=>"opel/corsa",
# "mileage"=>"67000", "vinnumber"=>"unknown", "year"=>"2010",
# "condition"=>"2", "transmission"=>"unknown", "cartype"=>"1"}
A second example:
str = "year<=>2015<br>condition<=>1<br>price<=>2100<br>mileage<=>22000"+
"<br>price<=>120000<br>year<=>2012<br>condition<=>2"
Hash[*str.split(/<=>|<br>/)]
#=> {"year"=>"2012", "condition"=>"2", "price"=>"120000", "mileage"=>"22000"}

Check the string with hash key

I am using Ruby 1.9.
I have a hash:
Hash_List={"ruby"=>"fun to learn","the rails"=>"It is a framework"}
I have a string like this:
test_string="I am learning the ruby by myself and also the rails."
I need to check if test_string contains words that match the keys of Hash_List. And if it does, replace the words with the matching hash value.
I used this code to check, but it is returning them empty:
another_hash=Hash_List.select{|key,value| key.include? test_string}
OK, hold onto your hat:
HASH_LIST = {
"ruby" => "fun to learn",
"the rails" => "It is a framework"
}
test_string = "I am learning the ruby by myself and also the rails."
keys_regex = /\b (?:#{Regexp.union(HASH_LIST.keys).source}) \b/x # => /\b (?:ruby|the\ rails) \b/x
test_string.gsub(keys_regex, HASH_LIST) # => "I am learning the fun to learn by myself and also It is a framework."
Ruby's got some great tricks up its sleeve, one of which is how we can throw a regular expression and a hash at gsub, and it'll search for every match of the regular expression, look up the matching "hits" as keys in the hash, and substitute the values back into the string:
gsub(pattern, hash) → new_str
...If the second argument is a Hash, and the matched text is one of its keys, the corresponding value is the replacement string....
Regexp.union(HASH_LIST.keys) # => /ruby|the\ rails/
Regexp.union(HASH_LIST.keys).source # => "ruby|the\\ rails"
Note that the first returns a regular expression and the second returns a string. This is important when we embed them into another regular expression:
/#{Regexp.union(HASH_LIST.keys)}/ # => /(?-mix:ruby|the\ rails)/
/#{Regexp.union(HASH_LIST.keys).source}/ # => /ruby|the\ rails/
The first can quietly destroy what you think is a simple search, because of the ?-mix: flags, which ends up embedding different flags inside the pattern.
The Regexp documentation covers all this well.
This capability is the core to making an extremely high-speed templating routine in Ruby.
You could do that as follows:
Hash_List.each_with_object(test_string.dup) { |(k,v),s| s.sub!(/#{k}/, v) }
#=> "I am learning the fun to learn by myself and also It is a framework."
First, follow naming conventions. Variables are snake_case, and names of classes are CamelCase.
hash = {"ruby" => "fun to learn", "rails" => "It is a framework"}
words = test_string.split(' ') # => ["I", "am", "learning", ...]
another_hash = hash.select{|key,value| words.include?(key)}
Answering your question: split your test string in words with #split and then check whether words include a key.
For checking if the string is substring of another string use String#[String] method:
another_hash = hash.select{|key, value| test_string[key]}

Check if a string includes any of the keys in a hash and return the value of the key it contains

I have a hash with multiple keys and a string which contains either none or one of the keys in the hash.
h = {"k1"=>"v1", "k2"=>"v2", "k3"=>"v3"}
s = "this is an example string that might occur with a key somewhere in the string k1(with special characters like (^&*$##!^&&*))"
What would be the best way to check if s contains any of the keys in h and if it does, return the value of the key that it contains?
For instance, for the above examples of h and s, the output should be v1.
Edit: Only the string would be user defined. The hash will always be the same.
I find this way readable:
hash_key_in_s = s[Regexp.union(h.keys)]
p h[hash_key_in_s] #=> "v1"
Or in one line:
p h.fetch s[Regexp.union(h.keys)] #=> "v1"
And here is a version not using regexp:
p h.fetch( h.keys.find{|key|s[key]} ) #=> "v1"
create a regex out of Hash h keys and match in string:
h[s.match(/#{h.keys.join('|')}/).to_s]
# => "v1"
Or as Amadan suggested using Regexp#escape for safety:
h[s.match(/#{h.keys.map(&Regexp.method(:escape)).join('|')}/).to_s]
# => "v1"
If String s was evenly spaced, we could have done something like this too:
s = "this is an example string that might occur with a key somewhere in the string k1 (with special characters like (^&*$\##!^&&*))"
h[(s.split & h.keys).first]
# => "v1"

How to use a regex to match a symbol like a string literal in a hash object?

Using str as the key in the hash works fine.
string = 'The dog and cat'
replace = {'dog' => 'woof', 'cat' => 'meow'}
replace.default = 'unknown'
string.gsub(/\w+/, replace)
# => "unknown woof unknown meow"
How do I get the same result with a sym as the hash key?
string = 'The dog and cat'
replace = {dog: 'woof', cat: 'meow'}
replace.default = 'unknown'
string.gsub(/\w+/, replace)
# => "unknown unknown unknown unknown" (actual result)
# => "unknown woof unknown meow" using symbols as hash keys? (desired result)
Some attempts I made:
string.gsub(/(\w+)/, replace[:$1])
# => "unknown unknown unknown unknown"
string.split.map(&:to_sym).to_s.gsub(/\w+/, replace)
# => "[:unknown, :unknown, :unknown, :unknown]"
In the below code :
string = 'The dog and cat'
replace = {dog: 'woof', cat: 'meow'}
replace.default = 'unknown'
string.gsub(/\w+/, replace)
You made the hash replace, default value as 'unknown'. It means, when you will look for a value from the hash replace, if the key is not present, then the hash would return you unknown as value. It will work this way as you defined the hash in this way.
Now #gsub method, giving all the word match like 'the', 'dog' etc, but none of those string are key to your hash replace, thus as I said above, hash replace will return the default value every time unknown.
Remember - In the hash replace = {dog: 'woof', cat: 'meow'}, keys are symbols, like :dog, :cat. But #gsub gives you all the match as a string.
Thus to make it work you need to use block as below :
string = 'The dog and cat'
replace = {dog: 'woof', cat: 'meow'}
replace.default = 'unknown'
string.gsub(/\w+/) { |m| replace[m.to_sym] }
# => "unknown woof unknown meow"
If you want to keep all the other words as-is, but just replace what's in the hash, you can do this:
replace = {'dog' => 'woof', 'cat' => 'meow'}
my_string = 'The dog and cat'
my_string.gsub(/\w+/) {|m| (replace.key? m) ? replace[m] : m}
Which will yield:
The woof and meow
Or more compactly (thanks to #Arup for the hint):
my_string.gsub(/\w+/) {|m| replace.fetch(m, m)}
It's not clear why you'd want to replace "unknown" words with unknown; That's not something very useful in normal string processing or template-processing code. As a result it seems like you're asking an X-Y question, wanting to know how to do "Y" when you should really be doing "X".
Here's some code, and information that could be helpful:
string = 'The dog and cat'
replacement_hash = {dog: 'woof', cat: 'meow'}
First, be very careful using a variable with the same name as a method. Ruby can figure out what you mean usually, but your brain will fail to always make the connection. The brains of people who use, or maintain, your code might not do as well. So, instead of a hash called replace, use something like replacement_hash.
Normally we don't want to define a hash using symbols as the keys if we're going to be doing search and replace actions against a string. Instead we'd want the actual string values; It's more straight-forward that way. That said, this will walk through a hash with symbols as keys, and generate a new hash using the equivalent strings as keys:
replacement_hash2 = Hash[replacement_hash.keys.map(&:to_s).zip(replacement_hash.values)] # => {"dog"=>"woof", "cat"=>"meow"}
We give gsub a regular expression, and it will walk through the string looking for matches to that pattern. One of its cool features is that we can give it a hash, and for each match found it will look through the associated hash and return the value for matching keys.
Here's how to easily build a pattern that matches the keys in the hash. I'm using a case-insensitive pattern, but YMMV:
key_regex = /\b(?:#{ Regexp.union(replacement_hash.keys.map(&:to_s)).source })\b/i # => /\b(?:dog|cat)\b/i
In its most basic form, here's a gsub that uses a pattern to look up values in a hash:
string.gsub(key_regex, replacement_hash2) # => "The woof and meow"
It's also possible to search the string using a pattern, then pass the "hits" to a block, which then computes the needed replacements:
string.gsub(key_regex) { |w| replacement_hash2[w] } # => "The woof and meow"
or:
string.gsub(key_regex) { |w| replacement_hash[w.to_sym] } # => "The woof and meow"
But wait! There's more!
If you don't want the surgical approach, you can also use a more generic ("shotgun approach") regex pattern and handle both hits and misses when looking in the hash:
string.gsub(/\S+/) { |w| replacement_hash[w.to_sym] || 'unknown' } # => "unknown woof unknown meow"
That turns your original code into a single, simple, line of code. Change the regexp as necessary.
Meditate on this is the contents of the block above don't make sense:
replacement_hash[:dog] # => "woof"
replacement_hash[:foo] # => nil
nil || 'unknown' # => "unknown"
Notice, that it's important to convert the suspect/target word matched into a symbol using your replacement_hash hash. That can get sticky, or convoluted, because some strings don't convert to symbols cleanly and result in a double-quoted symbol. You'd have to account for that in your hash definition:
'foo'.to_sym # => :foo
'foo_bar'.to_sym # => :foo_bar
'foo-bar'.to_sym # => :"foo-bar"
'foo bar'.to_sym # => :"foo bar"

Resources