Reputation: 16671
I have:
event = {"first_type_a" => 100, "first_type_b" => false, "second_type_a" => "abc", "second_type_b" => false}
I am trying to change the keys of event
based on the original. If the name contains a substring, the name will be changed, and the new key, pair value should be added, and the old pair should be removed. I expect to get:
event = {"important.a_1" => 100, "important.b_1" => false, "second_type_a" => "abc", "second_type_b" => false}
What would be the most efficient way to update event
?
I expected this to work:
event.each_pair { |k, v|
if !!(/first_type_*/ =~ k) do
key = "#{["important", k.split("_", 3)[2]].join(".")}";
event.merge!(key: v);
event.delete(k)
end
}
but it raises an error:
simpleLoop.rb:5: syntax error, unexpected keyword_do, expecting keyword_then or ';' or '\n'
... if !!(/first_type_*/ =~ k) do
... ^~
simpleLoop.rb:9: syntax error, unexpected keyword_end, expecting '}'
end
^~~
simpleLoop.rb:21: embedded document meets end of file
I thought to approach it differently:
if result = event.keys.find { |k| k.include? "first_type_" } [
key = "#{["important", k.split("_", 3)[2]].join(".")}"
event.merge!(key: v)
event.delete(k)
]
but still no luck. I am counting all brackets, and as the error indicates, it is something there, but I can't find it. Does the order matter?
Upvotes: 0
Views: 1192
Reputation: 11193
You could define a method to be used inside the transform key call:
def transform(str)
return ['important.', str.split('_').last, '_1'].join() if str[0..4] == 'first' # I checked just for "first"
str
end
event.transform_keys! { |k| transform(k) } # Ruby >= 2.5
event.map { |k, v| [transform(k), v] }.to_h # Ruby < 2.5
Using Hash#each_pair
but with Enumerable#each_with_object
:
event.each_pair.with_object({}) { |(k, v), h| h[transform(k)] = v }
Or use as one liner:
event.transform_keys { |str| str[0..4] == 'first' ? ['important.', str.split('_').last, '_1'].join() : str }
Upvotes: 1
Reputation: 110685
I will just show how this can be done quite economically in Ruby. Until recently, when wanting to modify keys of a hash one usually would do one of two things:
a
of two-element arrays (key-value pairs), modify the first element of each element of a
(the key) and then convert a
to the desired hash.Recently (with MRI v2.4) the Ruby monks bestowed on us the handy methods Hash#transform_keys and Hash#transform_keys!. We can use the first of these profitably here. First we need a regular expression to match keys.
r = /
\A # match beginning of string
first_type_ # match string
(\p{Lower}+) # match 1+ lowercase letters in capture group 1
\z # match the end of the string
/x # free-spacing regex definition mode
Conventionally, this is written
r = /\Afirst_type_(\p{Lower}+)\z/
The use of free-spacing mode makes the regex self-documenting. We now apply the transform_keys
method, together with the method String#sub and the regex just defined.
event = {"first_type_a"=>100, "first_type_b"=>false,
"second_type_a"=>"abc", "second_type_b"=>false}
event.transform_keys { |k| k.sub(r, "important.#{'\1'}_1") }
#=> {"important.a_1"=>100, "important.b_1"=>false,
# "second_type_a"=>"abc", "second_type_b"=>false}
In the regex the p{} construct expression \p{Lower}
could be replaced with \p{L}
, the POSIX bracket expression [[:lower:]]
(both match Unicode letters) or [a-z]
, but the last has the disadvantage that it will not match letters with diacritical marks. That includes letters of words borrowed from other languages that are used in English text (such as rosé, the wine). Search Regexp for documentation of POSIX and \p{}
expressions.
If "first_type_"
could be followed by lowercase or uppercase letters use \p{Alpha}
; if it could be followed by alphanumeric characters, use \p{Alnum}
, and so on.
Upvotes: 1
Reputation: 3716
The if
starts a block, such as other structures if ... else ... end
do ... end
begin ... rescue ... end
Therefore your first example remove the do
after the if
, the block is already open. I also made it clearer by changing the block after each_pair
to use do ... end
rather than braces to help avoid confusing a hash with a block.
event = { 'first_type_a' => 100, 'first_type_b' => false, 'second_type_a' => 'abc', 'second_type_b' => false }
new_event = {}
event.each_pair do |k, v|
if !!(/first_type_*/ =~ k)
important_key = ['important', k.split('_', 3)[2]].join('.')
new_event[important_key] = v
else
new_event[k] = v
end
end
Upvotes: 1