ruby: add boundaries \b option when using Regexp.union (array)? - ruby-on-rails

I am creating a regex matcher using:
Regexp.new(Regexp.union(some_hash.keys))
is it possible to add a boundaries filter to each element of the array so I have:
/\bkey1\b|\bkey2\b|,....../

For regexp keys:
Regexp.union(some_hash.keys.map { |k| /\b#{k}\b/ })
or for literal keys:
Regexp.union(some_hash.keys.map { |k| /\b#{Regexp.escape(k)}\b/ })
The result of Regexp.union is already a Regexp, no need for Regexp.new. In fact, we can also use plain strings inside Regexp.union, the difference being we don't initialise the flags in each subexpression:
Regexp.union(some_hash.keys.map { |k| "\\b#{k}\\b" })
Regexp.union(some_hash.keys.map { |k| "\\b#{Regexp.escape(k)}\\b" })

You can generate regex in simplest way without using Regexp as
hash = {'a' => 1, 'b' => 2, 'c' => 3, 'd' => 4}
/\b#{hash.keys.join('\b|\b')}\b/
=>/\ba\b|\bb\b|\bc\b|\bd\b/

Not exactly but you can use the scan method ...
for example:-
a = "cruel world"
a.scan(/\w+/) #=> ["cruel", "world"]
a.scan(/.../) #=> ["cru", "el ", "wor"]
a.scan(/(...)/) #=> [["cru"], ["el "], ["wor"]]
a.scan(/(..)(..)/) #=> [["cr", "ue"], ["l ", "wo"]]

Related

Is there any shorter way to write this merger of hash?

I have two hashes. The first hash should be prioritized. It should be overwritten by the second hash only when it is nil or blank.
main_hash.merge!(option_hash) do |key, main_hash, option_hash|
main_hash.presence || option_hash.presence
end
Use your code, which is fine, but use curly braces for the block and shorter block variables. And you don't need the second presence
main_hash.merge!(option_hash){|_k, h1, h2| h1.presence || h2}
#merge! (and #merge) with a block only invokes the block to handle cases where a key is present in both hashes. Where option_hash keys are not present in main_hash, the key/value pair is simply inserted.
You can use #merge! with a block to do some neat tricks, like setting up a combined total of hash values.
hash_1 = {a: 1, b: 1, c: 1}
hash_2 = {b: 1, c: 1, d: 1}
hash_1.merge!(hash_2{|_k, v1, v2| v1 + v2}
=> {:a => 1, :b => 2, :c => 2, :d => 1}
This is shorter, but I think your way is more readable.
main_hash = {a:1, b:nil, c:3}
option_hash = {a:5, b:2, c:8}
main_hash = option_hash.merge(main_hash.reject{|_,v| v.blank?})
#=> {a:1, b:2, c:3}
You could do it this way and give the result a variable name that makes what is happening a bit clearer like merged_main_option_hash instead of just main_hash
[1] pry(main)> main_hash = { key1: 1, key2: 2, key3: '', key4: nil }
=> {:key1=>1, :key2=>2, :key3=>"", :key4=>nil}
[2] pry(main)> option_hash = { key2: 2.2, key3: 3, key4: 4 }
=> {:key2=>2.2, :key3=>3, :key4=>4}
[3] pry(main)> main_hash.reject!{ |key, value| value.blank? }.merge!(option_hash) { |key, main_hash_value, option_hash_value| main_hash_value }
=> {:key1=>1, :key2=>2, :key3=>3, :key4=>4}
[4] pry(main)>
Refer apidock:merge!, apidock:reject! for more details.

How do I check if my hash has a key from an array of strings?

With Ruby, if I have a hash, what is the fastest way to check if it has a key from an array of strings? So I could do this
has_key = false
arr_of_strings.each do |str|
if my_hash.has_key?(str)
has_key = true
break
end
end
But taht seems like way too many lines of code for such a simple inquiry.
As simple as this:
arr_of_strings.any? {|s| my_hash.key?(s) }
Or, to get bonus points for clever-yet-less-readable code:
arr_of_strings.any?(&my_hash.method(:key?)) # => true
To see if the array and the keys have any in common, you can use set intersection:
(arr & hash.keys).any?
strings = ['a', 'b', 'c']
hash = {:a => 'apple', :b => 'bob', :d => 'thing'}
has_key = hash.keys.map(&:to_s) & strings # ['a', 'b']
has_key.any? # true
a one-liner that's similar, hash.keys.detect { |key| strings.include?(key.to_s) }.nil?

String interpolation with subhashes

In my code I want to use string interpolation for an email subject I am generating.
output = "this is my %{title}" % {title: "Text here"}
This works as expected, but is there a way to use hashes inside of hashes and still be able to use string interpolation?
It would be awesome if I could do something like:
output = "this is my %{title.text}" % {title: {text: "text here"}}
In Ruby 2.3, sprintf checks the hash's default value, so you could provide a default_proc to dig up the nested value:
hash = {title: {text: "text here"}}
hash.default_proc = proc { |h, k| h.dig(*k.to_s.split('.').map(&:to_sym)) }
"this is my %{title.text}" % hash
#=> "this is my text here"
Kind of hacky, but it seems to work.
I don't think this is possible with % method. You'd have to use regular Ruby interpolation with "#{}".
I'd also point out that you can use OpenStruct.
title = OpenStruct.new(text: 'text here')
output = "this is my #{title.text}"
It's actually not hard to make this work if you write a simple utility method to "squash" a nested Hash's keys, e.g.:
def squash_hash(hsh, stack=[])
hsh.reduce({}) do |res, (key, val)|
next_stack = [ *stack, key ]
if val.is_a?(Hash)
next res.merge(squash_hash(val, next_stack))
end
res.merge(next_stack.join(".").to_sym => val)
end
end
hsh = { foo: { bar: 1, baz: { qux: 2 } }, quux: 3 }
p squash_hash(hsh)
# => { :"foo.bar" => 1, :"foo.baz.qux" => 2, :quux => 3 }
puts <<END % squash_hash(hsh)
foo.bar: %{foo.bar}
foo.baz.qux: %{foo.baz.qux}
quux: %{quux}
END
# => foo.bar: 1
# foo.baz.qux: 2
# quux: 3

Find key that contains certain characters in a hashtable with Ruby

I have a hash that's something like this:
hash = { "key1-one" => 3, "key1-two" => 6, "key2-one" => 5, "key2-two" => 9 }
Now I want to find values with keys starting with key1, regardless of what follows. I've tried has_key? but it doesn't seem to work. I know I can use regex but is there already a built-in method for Ruby?
hash.select{ |key, _| key.start_with?("key1") }.values
I believe your hash would be look like this:
hash = { "key1-one"=>3, :"key1-two"=>6, "key2-one"=>5, "key2-two"=> 9 }
And try this:
hash.select { |k, _v| k.to_s.include? "key1" }.values
There's no need to create a new hash to extract the desired values:
hash.values_at *hash.keys.select { |k| k.start_with? 'key1' }
#=> [3, 6]
The could of course use a regex in the block instead:
{ |k| k =~ /^key1/ }

Convert this string to array of hashes

In Ruby or Rails What's the cleanest way to turn this string:
"[{one:1, two:2, three:3, four:4},{five:5, six:6}]"
into an array of hashes like this:
[{one:1, two:2, three:3, four:4},{five:5, six:6}]
Here is a one-liner on two lines:
s.split(/}\s*,\s*{/).
map{|s| Hash[s.scan(/(\w+):(\d+)/).map{|t| proc{|k,v| [k.to_sym, v.to_i]}.call(*t)}]}
NB I was using split(":") to separate keys from values, but #Cary Swoveland's use of parens in the regex is cooler. He forgot the key and value conversions, however.
Or a bit shorter, but uses array indexing instead of the proc, which some may find unappealing:
s.split(/}\s*,\s*{/).
map{|s| Hash[s.scan(/(\w+):(\d+)/).map{|t| [t[0].to_sym, t[1].to_i]}]}
Result:
=> [{:one=>1, :two=>2, :three=>3, :four=>4}, {:five=>5, :six=>6}]
Explanation: Start from the end. The last map processes a list of strings of the form "key: value" and returns a list of [:key, value] pairs. The scan processes one string of comma-separated key-value pairs into a list of "key: value" strings. Finally, the initial split separates the brace-enclosed comma-separated strings.
Try this:
"[{one:1, two:2, three:3, four:4},{five:5, six:6}]".
split(/\}[ ]*,[ ]*\{/).
map do |h_str|
Hash[
h_str.split(",").map do |kv|
kv.strip.
gsub(/[\[\]\{\}]/, '').
split(":")
end.map do |k, v|
[k.to_sym, v.to_i]
end
]
end
Not pretty, not optimized, but solves it. (It was fun to do, though :) )
a = "[{one:1, two:2, three:3, four:4},{five:5, six:6}]"
array = []
a.gsub(/\[|\]/, '').split(/{|}/).map{ |h| h if h.length > 0 && h != ','}.compact.each do |v|
hsh = {}
v.split(',').each do |kv|
arr = kv.split(':')
hsh.merge!({arr.first.split.join.to_sym => arr.last.to_i})
end
array << hsh
end
If you want me to explain it, just ask.
Another approach: Your string looks like a YAML or JSON -definition:
YAML
A slightly modified string works:
require 'yaml'
p YAML.load("[ { one: 1, two: 2, three: 3, four: 4}, { five: 5, six: 6 } ]")
#-> [{"one"=>1, "two"=>2, "three"=>3, "four"=>4}, {"five"=>5, "six"=>6}]
There are two problems:
The keys are strings, no symbols
You need some more spaces (one:1 is not recognized, you need a one: 1).
For problem 1 you need a gsub(/:/, ': ') (I hope there are no other : in your string)
For problem 2 was already a question: Hash[a.map{|(k,v)| [k.to_sym,v]}]
Full example:
require 'yaml'
input = "[{one:1, two:2, three:3, four:4},{five:5, six:6}]"
input.gsub!(/:/, ': ') #force correct YAML-syntax
p YAML.load(input).map{|a| Hash[a.map{|(k,v)| [k.to_sym,v]}]}
#-> [{:one=>1, :two=>2, :three=>3, :four=>4}, {:five=>5, :six=>6}]
JSON
With json you need additonal ", but the symbolization is easier:
require 'json'
input = '[ { "one":1, "two": 2, "three": 3, "four": 4},{ "five": 5, "six": 6} ]'
p JSON.parse(input)
#-> [{"one"=>1, "two"=>2, "three"=>3, "four"=>4}, {"five"=>5, "six"=>6}]
p JSON.parse(input, :symbolize_names => true)
#-> [{:one=>1, :two=>2, :three=>3, :four=>4}, {:five=>5, :six=>6}]
Example with original string:
require 'json'
input = "[{one: 1, two: 2, three:3, four:4},{five:5, six:6}]"
input.gsub!(/([[:alpha:]]+):/, '"\1":')
p JSON.parse(input)
#-> [{"one"=>1, "two"=>2, "three"=>3, "four"=>4}, {"five"=>5, "six"=>6}]
p JSON.parse(input, :symbolize_names => true)
#-> [{:one=>1, :two=>2, :three=>3, :four=>4}, {:five=>5, :six=>6}]
You could do as below.
Edit: I originally prepared this answer in haste, while on the road, on a borrowed computer with an unfamiliar operating system (Windows). After #sawa pointed out mistakes, I set about fixing it, but became so frustrated with the mechanics of doing so that I gave up and deleted my answer. Now that I'm home again, I have made what I believe are the necessary corrections.
Code
def extract_hashes(str)
str.scan(/\[?{(.+?)\}\]?/)
.map { |arr| Hash[arr.first
.scan(/\s*([a-z]+)\s*:\d*(\d+)/)
.map { |f,s| [f.to_sym, s.to_i] }
]
}
end
Example
str = "[{one:1, two:2, three:3, four:4},{five:5, six:6}]"
extract_hashes(str)
#=> [{:one=>1, :two=>2, :three=>3, :four=>4}, {:five=>5, :six=>6}]
Explanation
For str in the example above,
a = str.scan(/\[?{(.+?)\}\]?/)
#=> [["one:1, two:2, three:3, four:4"], ["five:5, six:6"]]
Enumerable#map first passes the first element of a into the block and assigns it to the block variable:
arr #=> ["one:1, two:2, three:3, four:4"]
Then
b = arr.first
#=> "one:1, two:2, three:3, four:4"
c = b.scan(/\s*([a-z]+)\s*:\d*(\d+)/)
#=> [["one", "1"], ["two", "2"], ["three", "3"], ["four", "4"]]
d = c.map { |f,s| [f.to_sym, s.to_i] }
#=> [[:one, 1], [:two, 2], [:three, 3], [:four, 4]]
e = Hash[d]
#=> {:one=>1, :two=>2, :three=>3, :four=>4}
In Ruby 2.0, Hash[d] can be replaced with d.to_h.
Thus, the first element of a is mapped to e.
Next, the outer map passes the second and last element of a into the block
arr #=> ["five:5, six:6"]
and we obtain:
Hash[arr.first
.scan(/\s*([a-z]+)\s*:\d*(\d+)/)
.map { |f,s| [f.to_sym, s.to_i] }
]
#=> {:five=>5, :six=>6}
which replaces a.last.

Resources