I'm trying to write my code more compact. I have three hashes. The first hash (#hash) is a collection of sub-hashes (value_1, value_2)
#hash = {
"Key" => ["value_1", "value_2"]
}
#value_1 = {
"Foo" => ["bar_1", "bar_2"]
}
#value_2 = {
"Foo2" => ["bar2_1", "bar2_2"]
}
Now, in my haml-view i'm trying to make something like this:
- i = 0
- #hash.each_value do |value|
- #value_[i].each_pair do |k, v|
= k
= v[0]
- i = i +1
I don't want to write one hash after the other. It's a bit similar to making a symbol out of a string, where you can write somthing like "value_#{i}".to_sym. I hope, somebody can follow and help me.
#hashes = [
{
"Foo" => ["bar_1", "bar_2"]
},
{
"Foo2" => ["bar2_1", "bar2_2"]
}
]
Then
#hashes.each do |v|
v.each_pair do |k,v|
= k
= v[0]
And if you need the index use each_with_index.
EDIT
Try this:
- #hash.each_value do |value|
- value.each do |v|
- instance_variable_get(:"##{v}").each_pair do |k, val|
= k
= val[0]
API
Related
Hello I am wondering if anyone may be able to give some assistance with two functions I am working on for a Ruby project. I have an array of Objects, and I need to get a certain attribute with the highest and lowest occurrence in each function respectively.
So far I have this, which works, but seems a bit too verbose:
def most_visited_port(time)
most_visited_port_name = ""
most_visited = 0
ending_port_array = []
#ships.each do |ship|
ending_port_array << ship.ending_port
most_visited = ending_port_array.sort.max_by { |v| ending_port_array.count(v) } != ending_port_array.sort.reverse.max_by { |v| ending_port_array.count(v) } ? false : ending_port_array.max_by { |v| ending_port_array.count(v) }
end
#ships.each do |ship|
if time.to_date === ship.time_arrived.to_date && ship.ending_port == most_visited
most_visited_port_name = ship.ending_port_name
end
end
pp most_visited_port_name
end
def least_visited_port(time)
least_visited_port_name = ""
least_visited = 0
ending_port_array = []
#ships.each do |ship|
ending_port_array << ship.ending_port
least_visited = ending_port_array.sort.min_by { |v| ending_port_array.count(v) } != ending_port_array.sort.reverse.min_by { |v| ending_port_array.count(v) } ? false : ending_port_array.min_by { |v| ending_port_array.count(v) }
end
#ships.each do |ship|
if time.to_date === ship.time_arrived.to_date && ship.ending_port == least_visited
least_visited_port_name = ship.ending_port_name
end
end
pp least_visited_port_name
end
Here is a sample of the array of Objects format:
[#<FleetShip:0x0000000108444450
#average_speed=46.02272727272727,
#beginning_port=7,
#beginning_port_name="Summermill",
#distance=81.0,
#ending_port=3,
#ending_port_name="Seamont",
#id=0,
#ship_name="Alpha",
#time_arrived=2016-06-12 08:05:36 -0500,
#time_left=2016-06-12 06:20:00 -0500>,
#<FleetShip:0x0000000108444400
#average_speed=32.01932579334578,
#beginning_port=7,
#beginning_port_name="Summermill",
#distance=81.0,
#ending_port=3,
#ending_port_name="Seamont",
#id=1,
#ship_name="Sea Ghost",
#time_arrived=2016-06-12 11:07:47 -0500,
#time_left=2016-06-12 08:36:00 -0500>]
But could anyone give some assistance on a possibly simpler or more concise way to pull it off?
For fun, you could use each_with_object to build a hash of ending_port values and their frequency, and then retrieve the most frequent.
I'm going to use a much simpler example.
A = Struct.new(:b)
c = [A.new(3), A.new(2), A.new(1), A.new(3), A.new(1), A.new(3), A.new(3)]
most_freq_b = c.each_with_object({}) { |x, h|
h[x.b] ||= 0
h[x.b] += 1
}.max_by(&:last).first
# => 3
This does not account for situations where more than one value for b occurs equal numbers of times. We can tweak it though, to accomplish this.
A = Struct.new(:b)
c = [A.new(3), A.new(2), A.new(1), A.new(3), A.new(1)]
freq = c.each_with_object({}) { |x, h|
h[x.b] ||= 0
h[x.b] += 1
}
highest_freq = freq.values.max
most_freq_b = freq.select { |_, v| v == highest_freq }.keys
# => [3, 1]
Alternatively, we can provide a default value of 0 for the hash, simplifying part of the code.
freq = c.each_with_object(Hash.new(0)) { |x, h|
h[x.b] += 1
}
I'm trying to make page with table which content is data from two arrays.
I have two lists(arrays) with hashes:
arr1 = [
{ "device"=>100, "phone"=>"12345" },
...,
{ "device"=>102, "phone"=>"12346" }
]
arr2 = [
{ "device"=>100, "type"=>"mobile", "name"=>"nokia" },
...,
{ "device"=>102, "type"=>"VIOP", "name"=>"smth" }
]
How can I join hashes from arr1 and arr2 by "device" to get a result array:
result = [
{ "device"=>100, "phone"=>"12345", "type"=>"mobile", "name"=>"nokia" },
...,
{ "device"=>102, "phone"=>"12346", "type"=>"VIOP", "name"=>"smth" }
]
Page which consist table with result array, loads very slowly and I need to find the fastest way to generate result_array.
Help me please.
This would work:
(arr1 + arr2).group_by { |i| i["device"] }.map { |d,(i1,i2)| i1.merge(i2)}
#=> [{"device"=>100, "phone"=>"12345", "type"=>"mobile", "name"=>"nokia"}, {"device"=>102, "phone"=>"12346", "type"=>"VIOP", "name"=>"smth"}]
Multiple ways to tackle it. Here is a quite readable way to do it:
# prepare an index hash for easier access of data by device
first_by_device = arr1.group_by {|a| a['device'] }
# build a new array joining both data hashes for each item
result = arr2.map do |item|
device = item['device']
item.merge first_by_device(device)
end
(arr1 + arr2).group_by { |i| i["device"] }.values.map{|x|x.reduce(&:merge)}
Maybe it's not the prettiest one, but it works:
result = arr1.collect{|a| h = arr2.select{|b| b["device"] ==
a["device"]}.first; h ? a.merge(h) : a }
Do you need something faster for large amount of data?
h = Hash.new
arr1.each do |a|
h[ a["device" ] ] ||= Hash.new
h[ a["device" ] ].merge!(a)
end
arr2.each do |a|
h[ a["device" ] ] ||= Hash.new
h[ a["device" ] ].merge!(a)
end
result = h.values
What's a convenient way to get a list of all Hash keys (with nesting) separated by dots?
Given I have a hash:
{ level1: { level21: { level31: 'val1',
level32: 'val2' },
level22: 'val3' }
}
Desired output (array of strings) which represents all key paths in a hash:
level1.level21.level31
level1.level21.level32
level1.level22
My current solution:
class HashKeysDumper
def self.dump(hash)
hash.map do |k, v|
if v.is_a? Hash
keys = dump(v)
keys.map { |k1| [k, k1].join('.') }
else
k.to_s
end
end.flatten
end
end
It also available as gist (with specs).
Well, it depends on what you mean by cleaner, but here's a smaller version that…
Will work on subclasses Hashes or Hash-alikes
Extends Hash, making it look cleaner in your code.
class Hash
def keydump
map{|k,v|v.keydump.map{|a|"#{k}.#{a}"} rescue k.to_s}.flatten
end
end
results:
{ level1: { level21: { level31: 'val1',
level32: 'val2' },
level22: 'val3' }
}.keydump
=> ["level1.level21.level31", "level1.level21.level32", "level1.level22"]
Here is my vision of this:
h = { 'level1' => { 'level2' => { 'level31' => 'val1', 'level32' => 'val2' } } }
class Hash
def nested_keys
self.inject([]) { |f, (k,v)| f += [k, v.is_a?(Hash) ? v.nested_keys : []] }.flatten
end
end
keys = h.nested_keys
p keys
#=> ["level1", "level2", "level31", "level32"]
k1, k2 = keys.shift, keys.shift
puts [k1, k2, keys.shift].join('.')
#=> level1.level2.level31
puts [k1, k2, keys.shift].join('.')
#=> level1.level2.level32
Here is a Working Demo
I just committed some code to RubyTree that adds from_hash() which would allow you to do this:
require 'rubytree'
Tree::TreeNode.from_hash(hash).each_leaf.map{|n| "#{n.name}.#{n.parentage.map(&:name).reverse.join('.')}" }
=> ["level1.level21.level31", "level1.level21.level32", "level1.level22"]
Aside from the gem require, it's a one-liner :)
I'm creating a nested hash in ruby rexml and want to update the hash when i enter a loop.
My code is like:
hash = {}
doc.elements.each(//address) do |n|
a = # ...
b = # ...
hash = { "NAME" => { a => { "ADDRESS" => b } } }
end
When I execute the above code the hash gets overwritten and I get only the info in the last iteration of the loop.
I don't want to use the following way as it makes my code verbose
hash["NAME"] = {}
hash["NAME"][a] = {}
and so on...
So could someone help me out on how to make this work...
Assuming the names are unique:
hash.merge!({"NAME" => { a => { "ADDRESS" => b } } })
You always create a new hash in each iteration, which gets saved in hash.
Just assign the key directly in the existing hash:
hash["NAME"] = { a => { "ADDRESS" => b } }
hash = {"NAME" => {}}
doc.elements.each('//address') do |n|
a = ...
b = ...
hash['NAME'][a] = {'ADDRESS' => b, 'PLACE' => ...}
end
blk = proc { |hash, key| hash[key] = Hash.new(&blk) }
hash = Hash.new(&blk)
doc.elements.each('//address').each do |n|
a = # ...
b = # ...
hash["NAME"][a]["ADDRESS"] = b
end
Basically creates a lazily instantiated infinitely recurring hash of hashes.
EDIT: Just thought of something that could work, this is only tested with a couple of very simple hashes so may have some problems.
class Hash
def can_recursively_merge? other
Hash === other
end
def recursive_merge! other
other.each do |key, value|
if self.include? key and self[key].can_recursively_merge? value
self[key].recursive_merge! value
else
self[key] = value
end
end
self
end
end
Then use hash.recursive_merge! { "NAME" => { a => { "ADDRESS" => b } } } in your code block.
This simply recursively merges a heirachy of hashes, and any other types if you define the recursive_merge! and can_recusively_merge? methods on them.
I have a simple array:
arr = ["apples", "bananas", "coconuts", "watermelons"]
I also have a function f that will perform an operation on a single string input and return a value. This operation is very expensive, so I would like to memoize the results in the hash.
I know I can make the desired hash with something like this:
h = {}
arr.each { |a| h[a] = f(a) }
What I'd like to do is not have to initialize h, so that I can just write something like this:
h = arr.(???) { |a| a => f(a) }
Can that be done?
Say you have a function with a funtastic name: "f"
def f(fruit)
fruit + "!"
end
arr = ["apples", "bananas", "coconuts", "watermelons"]
h = Hash[ *arr.collect { |v| [ v, f(v) ] }.flatten ]
will give you:
{"watermelons"=>"watermelons!", "bananas"=>"bananas!", "apples"=>"apples!", "coconuts"=>"coconuts!"}
Updated:
As mentioned in the comments, Ruby 1.8.7 introduces a nicer syntax for this:
h = Hash[arr.collect { |v| [v, f(v)] }]
Did some quick, dirty benchmarks on some of the given answers. (These findings may not be exactly identical with yours based on Ruby version, weird caching, etc. but the general results will be similar.)
arr is a collection of ActiveRecord objects.
Benchmark.measure {
100000.times {
Hash[arr.map{ |a| [a.id, a] }]
}
}
Benchmark #real=0.860651, #cstime=0.0, #cutime=0.0, #stime=0.0, #utime=0.8500000000000005, #total=0.8500000000000005
Benchmark.measure {
100000.times {
h = Hash[arr.collect { |v| [v.id, v] }]
}
}
Benchmark #real=0.74612, #cstime=0.0, #cutime=0.0, #stime=0.010000000000000009, #utime=0.740000000000002, #total=0.750000000000002
Benchmark.measure {
100000.times {
hash = {}
arr.each { |a| hash[a.id] = a }
}
}
Benchmark #real=0.627355, #cstime=0.0, #cutime=0.0, #stime=0.010000000000000009, #utime=0.6199999999999974, #total=0.6299999999999975
Benchmark.measure {
100000.times {
arr.each_with_object({}) { |v, h| h[v.id] = v }
}
}
Benchmark #real=1.650568, #cstime=0.0, #cutime=0.0, #stime=0.12999999999999998, #utime=1.51, #total=1.64
In conclusion
Just because Ruby is expressive and dynamic, doesn't mean you should always go for the prettiest solution. The basic each loop was the fastest in creating a hash.
h = arr.each_with_object({}) { |v,h| h[v] = f(v) }
Ruby 2.6.0 enables a shorter syntax by passing a block to the to_h method:
arr.to_h { |a| [a, f(a)] }
This is what I would probably write:
h = Hash[arr.zip(arr.map(&method(:f)))]
Simple, clear, obvious, declarative. What more could you want?
I'm doing it like described in this great article http://robots.thoughtbot.com/iteration-as-an-anti-pattern#build-a-hash-from-an-array
array = ["apples", "bananas", "coconuts", "watermelons"]
hash = array.inject({}) { |h,fruit| h.merge(fruit => f(fruit)) }
More info about inject method: http://ruby-doc.org/core-2.0.0/Enumerable.html#method-i-inject
Another one, slightly clearer IMHO -
Hash[*array.reduce([]) { |memo, fruit| memo << fruit << f(fruit) }]
Using length as f() -
2.1.5 :026 > array = ["apples", "bananas", "coconuts", "watermelons"]
=> ["apples", "bananas", "coconuts", "watermelons"]
2.1.5 :027 > Hash[*array.reduce([]) { |memo, fruit| memo << fruit << fruit.length }]
=> {"apples"=>6, "bananas"=>7, "coconuts"=>8, "watermelons"=>11}
2.1.5 :028 >
in addition to the answer of Vlado Cingel (I cannot add a comment yet, so I added an answer).
Inject can also be used in this way: the block has to return the accumulator. Only the assignment in the block returns the value of the assignment, and an error is reported.
array = ["apples", "bananas", "coconuts", "watermelons"]
hash = array.inject({}) { |h,fruit| h[fruit]= f(fruit); h }