How do I assign multiple symbols to the same value in Ruby? - ruby

The idea of what I am trying to do is to clump synonym symbols to the same value, without having to redefine the same value over and over. Basically turn this:
fruits = { orange: "Citrus", grapefruit: "Citrus", tangerine: "Citrus" }
Into this:
fruits = { orange:, grapefruit:, tangerine: => "Citrus" }
What is the proper syntax for accomplishing this?
Thanks

Use a hash, in order to access the type of fruit using the fruit name. For example:
fruits = %i{ orange grapefruit tangerine apple }
citrus_fruits = %i{ orange grapefruit tangerine }
fruit_type = citrus_fruits.zip([:citrus] * citrus_fruits.length).to_h
fruit_type[:apple] = :rosaceae
puts fruit_type
# {:orange=>:citrus, :grapefruit=>:citrus, :tangerine=>:citrus, :apple=>:rosaceae}
Here, zip and to_h are used to simplify the hash creation and avoid repetitive code.

Group Keys by Value; Optionally Transform Returned Values
In Ruby, a Symbol is a core class that provides an identifier for things, and the Symbol is never duplicated during runtime. Setting aside how they're used internally, the most common use case for using a Symbol in your code is to define keys in a Hash. You can use other types of keys, but the properties of a Symbol make them especially useful as Hash keys.
With that out of the way, it looks like you're trying to group similar Hash values, but it's unclear how you expect to use this grouping. There is more than one way to do this, so I'll just pick one as an example.
Given a Hash like this one:
produce =
{
:orange => "citrus",
:grapefruit => "citrus",
:tangerine => "citrus",
:raspberry => "berry",
:strawberry => "berry",
:canteloupe => "melon",
:honeydew => "melon"
}
you can use Hash#group_by (inherited from Enumerable) to quickly sort your Hash by value. For example, using Ruby 3.0.0:
produce.group_by { _2 }
#=>
{"citrus"=>
[[:orange, "citrus"], [:grapefruit, "citrus"], [:tangerine, "citrus"]],
"berry"=>[[:raspberry, "berry"], [:strawberry, "berry"]],
"melon"=>[[:canteloupe, "melon"], [:honeydew, "melon"]]}
This returns a Hash grouped by your unique values, but you may prefer to discard the produce type in the nested Array objects. You can do that with Hash#transform_values like so:
produce.group_by { _2 }.transform_values { _1.map &:first }
#=>
{"citrus"=>[:orange, :grapefruit, :tangerine],
"berry"=>[:raspberry, :strawberry],
"melon"=>[:canteloupe, :honeydew]}
Either way, the main point is that a Hash key is associated with a value that can be of almost any class, and so you can examine the contents of each value to determine whether or not they belong to the grouping you want (which is currently defined by your key).
Your current data structure isn't really optimized for retrieving types of produce (e.g. citrus fruits) easily, but it can certainly be done. However, you may want to reconsider whether you have the right data structure for the way you want to access or manipulate your data. Your mileage will certainly vary.

Your comment about motherboards and blueprints suggest that you are given something like
h = { :mb1=>:bp3, :mb_2=>:bp1, :mb3=>:bp3, :mb4=>:bp2, :mb5=>:bp1 }
and want to produce the hash
{ :bp3=>[:mb1, :mb3], :bp1=>[:mb_2, :mb5], :bp2=>[:mb4] }
One of many ways to do that is the following:
h.each_with_object({}) { |(k,v),g| (g[v] ||= []) << k }
See Enumerable#each_with_object, and to understand why I've written the block varaiables |(k,v),g|, see array decomposition.
This is a condensed translation of the following code (which I've salted with three puts statement to show the calculations being performed):
g = {}
h.each do |key_value_pair|
k, v = key_value_pair
puts "\nkey_value_pair = #{key_value_pair}, k = #{k}, v = #{v}"
puts "g[#{v}] set to [] because g[#{v}] == nil" if g[v].nil?
g[v] = [] if g[v].nil?
g[v] << k
puts "g after g[#{v}] << #{g}"
end
#=> {:mb1=>:bp3, :mb_2=>:bp1, :mb3=>:bp3, :mb4=>:bp2, :mb5=>:bp1}
The following is displayed:
key_value_pair = [:mb1, :bp3], k = mb1, v = bp3
g[bp3] set to [] because g[bp3] == nil
g after g[bp3] << {:bp3=>[:mb1]}
key_value_pair = [:mb_2, :bp1], k = mb_2, v = bp1
g[bp1] set to [] because g[bp1] == nil
g after g[bp1] << {:bp3=>[:mb1], :bp1=>[:mb_2]}
key_value_pair = [:mb3, :bp3], k = mb3, v = bp3
g after g[bp3] << {:bp3=>[:mb1, :mb3], :bp1=>[:mb_2]}
key_value_pair = [:mb4, :bp2], k = mb4, v = bp2
g[bp2] set to [] because g[bp2] == nil
g after g[bp2] << {:bp3=>[:mb1, :mb3], :bp1=>[:mb_2], :bp2=>[:mb4]}
key_value_pair = [:mb5, :bp1], k = mb5, v = bp1
g after g[bp1] << {:bp3=>[:mb1, :mb3], :bp1=>[:mb_2, :mb5], :bp2=>[:mb4]}

#Charles Persson, if I understood your question correctly, your main goal is to refactor this snippet:
hash = {
:orange => "citrus",
:grapefruit => "citrus",
:tangerine => "citrus",
:raspberry => "berry",
:cherry => "drupe",
:strawberry => "berry",
:canteloupe => "melon",
:honeydew => "melon",
:apple => "pome"
}
to something similar to:
hash = {
[:orange, :grapefruit, :tangerine] => "citrus",
[:raspberry, :strawberry] => "berry",
:cherry => "drupe",
[:canteloupe, :honeydew] => "melon",
:apple => "pome"
}
If I am right, then I can suggest implementing a method like this one:
# source - an input hash that can contain arrays as keys.
# dest - a new output hash where all array keys are replaced by singular keys.
def mhash(source)
dest = {}
source.each_pair do |key, value|
if key.instance_of?(Array)
key.each do |sub_key|
dest[sub_key] = value
end
else
dest[key] = value
end
end
dest
end
or its shorter alternative:
def mhash(source)
source.each_pair.with_object({}) do |(key, value), dest|
key.instance_of?(Array) ? key.each { |sub_key| dest[sub_key] = value } : dest[key] = value
end
end
This way, you will be able to write a code like the following:
hash = mhash({
[:orange, :grapefruit, :tangerine] => "citrus",
[:raspberry, :strawberry] => "berry",
:cherry => "drupe",
[:canteloupe, :honeydew] => "melon",
:apple => "pome"
})
p hash
# => {
:orange => "citrus",
:grapefruit => "citrus",
:tangerine => "citrus",
:raspberry => "berry",
:strawberry => "berry",
:cherry => "drupe",
:canteloupe => "melon",
:honeydew => "melon",
:apple => "pome"
}
Otherwise, please provide a better explanation of your question. Thanks.

Related

visiting hash with keys from array

I have a big hash with lots of nested key value pairs.
Eg.
h = {"foo" => {"bar" => {"hello" => {"world" => "result" } } } }
Now I want to access result and I have keys for that in array in proper sequence.
keys_arr = ["foo", "bar", "hello", "world"]
The motive is clear, I want to do following:
h["foo"]["bar"]["hello"]["world"]
# => "result"
But I don't know how to do this. I am currently doing:
key = '["' + keys_arr.join('"]["') + '"]'
eval("h"+key)
# => "result"
Which looks like a hack. Also it greatly reduces my ability to work with hash in real environment.
Please suggest alternate and better ways.
Using Enumerable#inject (or Enumerable#reduce):
h = {"foo" => {"bar" => {"hello" => {"world" => "result" } } } }
keys_arr = ["foo", "bar", "hello", "world"]
keys_arr.inject(h) { |x, k| x[k] }
# => "result"
UPDATE
If you want to do something like: h["foo"]["bar"]["hello"]["world"] = "ruby"
innermost = keys_arr[0...-1].inject(h) { |x, k| x[k] } # the innermost hash
innermost[keys_arr[-1]] = "ruby"
keys_arr.inject(h, :[])
will do
Another way:
h = {"foo" => {"bar" => {"hello" => {"world" => 10 } } } }
keys = ["foo", "bar", "hello", "world"]
result = h
keys.each do |key|
result = result[key]
end
puts result #=>10
If the key may not exist, see here:
Dealing with many [...] in Ruby

dynamically finding value from key string in nested hash

I have a user inputed string called x_value whose value contains something like ticker|high. Whenever there is a |, that indicates that the latter is a child of the former. The purpose of the method is to return a specific value within a hash.
sections = []
object.x_value.split('|').each do |part|
sections << part.to_sym
end
I then want to drill down the data hash and retrieve the value of the last key.
data = {"ticker":{"high":529.5,"low":465,"avg":497.25,"vol":7520812.018}}
In this example
data[sections[0]][sections[1]] returns the expected 529.5 value. However, the user may have different hashes and different levels deep of nested key/values. How can I write this?
I have tried data[sections], but that didn't work.
Use Enumerable#reduce
data = {"ticker" => {"high" => 529.5, "low" => 465,"avg" => 497.25,"vol" => 7520812.018}}
"ticker|high".split('|').reduce(data) { |dat,val| dat[val] } #=> 592.5
more example:
data = {"more_ticker" => {"ticker" => {"high" => 529.5, "low" => 465,"avg" => 497.25,"vol" => 7520812.018}}}
"more_ticker|ticker|avg".split('|').reduce(data) { |dat,val| dat[val] }
#=> 497.25
You could also use recursion:
def getit(hash, x_value)
recurse(hash, x_value.split('|'))
end
def recurse(hash, keys)
k = keys.shift
keys.empty? ? hash[k] : recurse(hash[k], keys)
end
data = {"ticker" => {"high" => 529.5, "low" => 465}}
getit(data, "ticker|high") #=> 529.5
getit(data, "ticker") #=> {"high"=>529.5, "low"=>465}
data = {"more_ticker" => {"ticker" => {"high" => 529.5, "low" => 465}}}
getit(data, "more_ticker|ticker|low") #=> 465
getit(data, "more_ticker|ticker|avg") #=> nil

I can't find a way to create a simple multidimensional array or hash in Ruby

You Ruby pros will laugh but I'm having such a hard time with this. I've searched and searched and tried a lot of different things but nothing seems right. I guess I'm just used to dealing with arrays in js and php. Here is what I want to do; consider this pseudo code:
i = 0
foreach (items as item) {
myarray[i]['title'] = item['title']
myarray[i]['desc'] = item['desc']
i++
}
Right, so then I can loop through myarray or access 'title' and 'desc' by the index (i). Simplest thing in the world. I've found a few ways to make it work in Ruby but they've all been really messy or confusing. I want to know the right way to do it, and the cleanest.
Unless you are actually updating my_array (which implies that there is probably a better way to do this), you probably want map instead:
items = [
{'title' => 't1', 'desc' => 'd1', 'other' => 'o1'},
{'title' => 't2', 'desc' => 'd2', 'other' => 'o2'},
{'title' => 't3', 'desc' => 'd3', 'other' => 'o3'},
]
my_array = items.map do |item|
{'title' => item['title'], 'desc' => item['desc'] }
end
items # => [{"title"=>"t1", "desc"=>"d1", "other"=>"o1"}, {"title"=>"t2", "desc"=>"d2", "other"=>"o2"}, {"title"=>"t3", "desc"=>"d3", "other"=>"o3"}]
my_array # => [{"title"=>"t1", "desc"=>"d1"}, {"title"=>"t2", "desc"=>"d2"}, {"title"=>"t3", "desc"=>"d3"}]
I'm not quite sure why you are trying to do this, as it seems like items is already an array with hashes inside it, and in my code below, myarray is exactly the same as items.
Try using each_with_index instead of a foreach loop:
items.each_with_index do |item, index|
myarray[index] = item
end
If you have extra attributes in each item, such as a id or something, then you would want to remove those extra attributes before you add the item to myarray.
titles = ["t1", "t2", "t3"]
descs = ["d1", "d2", "d3"]
h= Hash.new
titles.each.with_index{ |v,i| h[i] = {title: "#{v}" } }
puts h[0][:title] #=> t1
puts h #=> {0=>{:title=>"t1"}, 1=>{:title=>"t2"}...}
descs.each.with_index{ |v,i| h[i] = h[i].merge( {desc: "#{v}" } ) }
puts h[0][:desc] #=> d1
puts h #=> {0=>{:title=>"t1", :desc=>"d1"}, 1=>...

Parslet subtree doesn't fire

Resume (I shrinked down the following long story to the simple problem)
tree = {:properties => [{:a => 'b'}, {:c => 'd'}]}
big_tree = {:properties => [{:a => 'b'}, {:c => 'd'}], :moves => [{:a => 'b'}, {:c => 'd'}]}
trans = Parslet::Transform.new do
rule(:properties => subtree(:nested)) do
out = {}
nested.each {|pair| out = out.merge pair}
{:properties => out}
end
end
pp tree
pp trans.apply(tree)
pp big_tree
pp trans.apply(big_tree)
# OUTPUT
{:properties=>[{:a=>"b"}, {:c=>"d"}]}
{:properties=>{:a=>"b", :c=>"d"}} # Worked with small tree
{:properties=>[{:a=>"b"}, {:c=>"d"}], :moves=>[{:a=>"b"}, {:c=>"d"}]}
{:properties=>[{:a=>"b"}, {:c=>"d"}], :moves=>[{:a=>"b"}, {:c=>"d"}]} # Didn't work with bigger tree
=========================FULL STORY (Not so relevant after the header)
I am making an SGF files parser with Parslet.
Now I am at the stage to make a Transformer.
From the Parser I already get the structs like that:
[{:properties=>
[{:name=>"GM"#2, :values=>[{:value=>"1"#5}]},
{:name=>"FF"#7, :values=>[{:value=>"4"#10}]},
{:name=>"SZ"#12, :values=>[{:value=>"19"#15}]},
{:name=>"AP"#18, :values=>[{:value=>"SmartGo Kifu:2.2"#21}]},
{:name=>"GN"#40, :values=>[{:value=>"2013-05-11g"#43}]},
{:name=>"PW"#57, :values=>[{:value=>"Dahan"#60}]},
{:name=>"PB"#68, :values=>[{:value=>"SmartGo"#71}]},
{:name=>"DT"#81, :values=>[{:value=>"2013-05-11"#84}]},
{:name=>"KM"#97, :values=>[{:value=>"6.5"#100}]},
{:name=>"RE"#106, :values=>[{:value=>"W+R"#109}]},
{:name=>"RU"#115, :values=>[{:value=>"AGA (Area)"#118}]},
{:name=>"ID"#129, :values=>[{:value=>"ch0"#132}]}],
:moves=>
[{:player=>"B"#137, :place=>"oq"#139},
{:player=>"W"#143, :place=>"dd"#145},
{:player=>"B"#149, :place=>"oo"#151},
...etc...
The ruleset I am using to Transform:
# Rewrite player: COLOR, place: X to COLOR: X
rule( player: simple(:p), place: simple(:pl)) do
if p == 'W'
{ white: pl }
elsif p == 'B'
{ black: pl }
end
end
# Un-nest single-value hash
rule( value: simple(:v)) { v }
# Rewrite name: KEY, values: SINGLE_VALUE to KEY: SINGLE_VALUE
rule( name: simple(:n), values: [ simple(:v) ]) { {n.to_sym => v} }
# A Problem!!!
rule( properties: subtree(:props) ) do
out = {}
props.each {|pair| pair.each {|k, v| out[k] = v}}
{ properties: out }
end
With such rules I get the following struct:
[{:properties=>
[{:GM=>"1"#5},
{:FF=>"4"#10},
{:SZ=>"19"#15},
{:AP=>"SmartGo Kifu:2.2"#21},
{:GN=>"2013-05-11g"#43},
{:PW=>"Dahan"#60},
{:PB=>"SmartGo"#71},
{:DT=>"2013-05-11"#84},
{:KM=>"6.5"#100},
{:RE=>"W+R"#109},
{:RU=>"AGA (Area)"#118},
{:ID=>"ch0"#132}],
:moves=>
[{:black=>"oq"#139},
{:white=>"dd"#145},
{:black=>"oo"#151},
...etc...
Everything is perfect.
The only Problem of mine is that :properties Array of Hashes.
In the end I want to have
[{:properties=>
{:GM=>"1"#5,
:FF=>"4"#10,
:SZ=>"19"#15,
:AP=>"SmartGo Kifu:2.2"#21,
:GN=>"2013-05-11g"#43,
:PW=>"Dahan"#60,
:PB=>"SmartGo"#71,
:DT=>"2013-05-11"#84,
:KM=>"6.5"#100,
:RE=>"W+R"#109,
:RU=>"AGA (Area)"#118,
:ID=>"ch0"#132},
:moves=>
[{:black=>"oq"#139},
{:white=>"dd"#145},
{:black=>"oo"#151},
...etc...
You see? Merge all arrayed hashes inside :properties, because after the previous transformations they now have unique keys. Also flatten the struct a bit.
Hey! I can do it manually. I mean to run a separate method like
merged_stuff = {}
tree.first[:properties].each {|pair| pair.each {|k, v| merged_stuff[k] = v}}
tree.first[:properties] = merged_stuff
But Why I Cannot Do That With The Neat Transform Rules, To Have All Transformation Logic In One Place?
The point is that rule( properties: subtree(:props) ) does not get fired at all. Even if I just return nil from the block, it doesn't change anything. So, seems, that this subtree doesn't catch the things, or I don't.
The problem is that :properties and :moves are keys in the same hash, and subtree apparently doesn't want to match part of a hash. If you remove :moves, the rule will be executed. It is kinda explained in the documentation:
A word on patterns
Given the PORO hash
{
:dog => 'terrier',
:cat => 'suit' }
one might assume that the following rule matches :dog and replaces it by 'foo':
rule(:dog => 'terrier') { 'foo' }
This is frankly impossible. How would 'foo' live besides :cat => 'suit'
inside the hash? It cannot. This is why hashes are either matched completely,
cats n’ all, or not at all.
though I must admit it's not a really clear example.
So the problem rule should look like this:
rule( properties: subtree(:props), moves: subtree(:m) ) do
out = {}
props.each {|pair| pair.each {|k, v| out[k] = v}}
{ properties: out , moves: m}
end
Transform rules match a whole node and replace it, so you need to match the whole hash, not just one key.
rule( properties: subtree(:props), moves: subtree(:moves) )
If you converted the {:name=>"GM", :values=>[{:value=>"1"}]} type things into objects (using OpenStruct say) then you don't need to use subtree, you can use sequence.

Ruby / Remove everything after a matched key / array of hashes

Let's say I have the following array of hashes:
h = [{"name" => "bob"}, {"car" => "toyota"}, {"age" => "25"}]
And I have the following key to match:
k = 'car'
How do I match the 'k' to 'h' and have delete every element after the match so that it returns:
h = [{"name" => "bob"}, {"car" => "toyota"}]
Just convert hash to array, do your task and then convert back
h = {"name" => "bob", "car" => "toyota", "age" => "25"}
array = h.to_a.flatten
index = array.index('car') + 1
h = Hash[*array[0..index]]
=> {"name"=>"bob", "car"=>"toyota"}
By the way, the hash is ordered only since Ruby 1.9
ar = [{"name" => "bob"}, {"car" => "toyota"}, {"age" => "25"}]
p ar[0 .. ar.index{|h| h.key?('car')}] #=>[{"name"=>"bob"}, {"car"=>"toyota"}]
I like megas' version, as its short and to the point. Another approach, which would be more explicit, would be iterating over the keys array of each hash. The keys of a hash are maintained in an ordered array (http://ruby-doc.org/core-1.9.3/Hash.html). They are ordered by when they were first entered. As a result, you can try the following:
newArray = Array.new
h.each do |hash| # Iterate through your array of hashes
newArray << hash
if hash.has_key?("car") # check if this hash is the "car" hash.
break # exits the block
end
end
This all depends, of course, on whether the array was created in the proper order. If it was, then you're golden.
A hash is unordered set by definition, so what you request is somewhat undefined. However you can do something like a hack:
h = {"name" => "bob", "car" => "toyota", "age" => "25"}
matched = false
key_given = "car"
h.each do |k,v|
if matched
h.delete(k)
end
if k == key_given
matched = true
next
end
end
I'm pretty late to the party here. I was looking for a solution to this same problem, but I didn't love these answers. So, here's my approach:
class Array
def take_until(&blk)
i = find_index &blk
take(i + 1)
end
end
h = [{"name" => "bob"}, {"car" => "toyota"}, {"age" => "25"}]
k = 'car'
h.take_until { |x| x.has_key?(k) }
=> [{"name"=>"bob"}, {"car"=>"toyota"}]

Resources