In Ruby, how do I make a hash from an array? - ruby

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 }

Related

Convert Hash to OpenStruct recursively

Given I have this hash:
h = { a: 'a', b: 'b', c: { d: 'd', e: 'e'} }
And I convert to OpenStruct:
o = OpenStruct.new(h)
=> #<OpenStruct a="a", b="b", c={:d=>"d", :e=>"e"}>
o.a
=> "a"
o.b
=> "b"
o.c
=> {:d=>"d", :e=>"e"}
2.1.2 :006 > o.c.d
NoMethodError: undefined method `d' for {:d=>"d", :e=>"e"}:Hash
I want all the nested keys to be methods as well. So I can access d as such:
o.c.d
=> "d"
How can I achieve this?
You can monkey-patch the Hash class
class Hash
def to_o
JSON.parse to_json, object_class: OpenStruct
end
end
then you can say
h = { a: 'a', b: 'b', c: { d: 'd', e: 'e'} }
o = h.to_o
o.c.d # => 'd'
See Convert a complex nested hash to an object.
I came up with this solution:
h = { a: 'a', b: 'b', c: { d: 'd', e: 'e'} }
json = h.to_json
=> "{\"a\":\"a\",\"b\":\"b\",\"c\":{\"d\":\"d\",\"e\":\"e\"}}"
object = JSON.parse(json, object_class:OpenStruct)
object.c.d
=> "d"
So for this to work, I had to do an extra step: convert it to json.
personally I use the recursive-open-struct gem - it's then as simple as RecursiveOpenStruct.new(<nested_hash>)
But for the sake of recursion practice, I'll show you a fresh solution:
require 'ostruct'
def to_recursive_ostruct(hash)
result = hash.each_with_object({}) do |(key, val), memo|
memo[key] = val.is_a?(Hash) ? to_recursive_ostruct(val) : val
end
OpenStruct.new(result)
end
puts to_recursive_ostruct(a: { b: 1}).a.b
# => 1
edit
Weihang Jian showed a slight improvement to this here https://stackoverflow.com/a/69311716/2981429
def to_recursive_ostruct(hash)
hash.each_with_object(OpenStruct.new) do |(key, val), memo|
memo[key] = val.is_a?(Hash) ? to_recursive_ostruct(val) : val
end
end
Also see https://stackoverflow.com/a/63264908/2981429 which shows how to handle arrays
note
the reason this is better than the JSON-based solutions is because you can lose some data when you convert to JSON. For example if you convert a Time object to JSON and then parse it, it will be a string. There are many other examples of this:
class Foo; end
JSON.parse({obj: Foo.new}.to_json)["obj"]
# => "#<Foo:0x00007fc8720198b0>"
yeah ... not super useful. You've completely lost your reference to the actual instance.
Here's a recursive solution that avoids converting the hash to json:
def to_o(obj)
if obj.is_a?(Hash)
return OpenStruct.new(obj.map{ |key, val| [ key, to_o(val) ] }.to_h)
elsif obj.is_a?(Array)
return obj.map{ |o| to_o(o) }
else # Assumed to be a primitive value
return obj
end
end
My solution is cleaner and faster than #max-pleaner's.
I don't actually know why but I don't instance extra Hash objects:
def dot_access(hash)
hash.each_with_object(OpenStruct.new) do |(key, value), struct|
struct[key] = value.is_a?(Hash) ? dot_access(value) : value
end
end
Here is the benchmark for you reference:
require 'ostruct'
def dot_access(hash)
hash.each_with_object(OpenStruct.new) do |(key, value), struct|
struct[key] = value.is_a?(Hash) ? dot_access(value) : value
end
end
def to_recursive_ostruct(hash)
result = hash.each_with_object({}) do |(key, val), memo|
memo[key] = val.is_a?(Hash) ? to_recursive_ostruct(val) : val
end
OpenStruct.new(result)
end
require 'benchmark/ips'
Benchmark.ips do |x|
hash = { a: 1, b: 2, c: { d: 3 } }
x.report('dot_access') { dot_access(hash) }
x.report('to_recursive_ostruct') { to_recursive_ostruct(hash) }
end
Warming up --------------------------------------
dot_access 4.843k i/100ms
to_recursive_ostruct 5.218k i/100ms
Calculating -------------------------------------
dot_access 51.976k (± 5.0%) i/s - 261.522k in 5.044482s
to_recursive_ostruct 50.122k (± 4.6%) i/s - 250.464k in 5.008116s
My solution, based on max pleaner's answer and similar to Xavi's answer:
require 'ostruct'
def initialize_open_struct_deeply(value)
case value
when Hash
OpenStruct.new(value.transform_values { |hash_value| send __method__, hash_value })
when Array
value.map { |element| send __method__, element }
else
value
end
end
Here is one way to override the initializer so you can do OpenStruct.new({ a: "b", c: { d: "e", f: ["g", "h", "i"] }}).
Further, this class is included when you require 'json', so be sure to do this patch after the require.
class OpenStruct
def initialize(hash = nil)
#table = {}
if hash
hash.each_pair do |k, v|
self[k] = v.is_a?(Hash) ? OpenStruct.new(v) : v
end
end
end
def keys
#table.keys.map{|k| k.to_s}
end
end
Basing a conversion on OpenStruct works fine until it doesn't. For instance, none of the other answers here properly handle these simple hashes:
people = { person1: { display: { first: 'John' } } }
creds = { oauth: { trust: true }, basic: { trust: false } }
The method below works with those hashes, modifying the input hash rather than returning a new object.
def add_indifferent_access!(hash)
hash.each_pair do |k, v|
hash.instance_variable_set("##{k}", v.tap { |v| send(__method__, v) if v.is_a?(Hash) } )
hash.define_singleton_method(k, proc { hash.instance_variable_get("##{k}") } )
end
end
then
add_indifferent_access!(people)
people.person1.display.first # => 'John'
Or if your context calls for a more inline call structure:
creds.yield_self(&method(:add_indifferent_access!)).oauth.trust # => true
Alternatively, you could mix it in:
module HashExtension
def very_indifferent_access!
each_pair do |k, v|
instance_variable_set("##{k}", v.tap { |v| v.extend(HashExtension) && v.send(__method__) if v.is_a?(Hash) } )
define_singleton_method(k, proc { self.instance_variable_get("##{k}") } )
end
end
end
and apply to individual hashes:
favs = { song1: { title: 'John and Marsha', author: 'Stan Freberg' } }
favs.extend(HashExtension).very_indifferent_access!
favs.song1.title
Here is a variation for monkey-patching Hash, should you opt to do so:
class Hash
def with_very_indifferent_access!
each_pair do |k, v|
instance_variable_set("##{k}", v.tap { |v| v.send(__method__) if v.is_a?(Hash) } )
define_singleton_method(k, proc { instance_variable_get("##{k}") } )
end
end
end
# Note the omission of "v.extend(HashExtension)" vs. the mix-in variation.
Comments to other answers expressed a desire to retain class types. This solution accommodates that.
people = { person1: { created_at: Time.now } }
people.with_very_indifferent_access!
people.person1.created_at.class # => Time
Whatever solution you choose, I recommend testing with this hash:
people = { person1: { display: { first: 'John' } }, person2: { display: { last: 'Jingleheimer' } } }
If you are ok with monkey-patching the Hash class, you can do:
require 'ostruct'
module Structurizable
def each_pair(&block)
each do |k, v|
v = OpenStruct.new(v) if v.is_a? Hash
yield k, v
end
end
end
Hash.prepend Structurizable
people = { person1: { display: { first: 'John' } }, person2: { display: { last: 'Jingleheimer' } } }
puts OpenStruct.new(people).person1.display.first
Ideally, instead of pretending this, we should be able to use a Refinement, but for some reason I can't understand it didn't worked for the each_pair method (also, unfortunately Refinements are still pretty limited)

Dump YAML-like key names of Hash

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 :)

Building a hash out of hash

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

ruby string to hash conversion

I have a string like this,
str = "uu#p, xx#m, yy#n, zz#m"
I want to know how to convert the given string into a hash. (i.e my actual requirement is, how many values (before the # symbol) have the m, n and p. I don't want the counting, I need an exact value). The output would be better like this,
{"m" => ["xx", "zz"], "n" => ["yy"], "p" => ["uu"]}
Can help me anyone, please?
Direct copy/past of an IRB session:
>> str.split(/, /).inject(Hash.new{|h,k|h[k]=[]}) do |h, s|
.. v,k = s.split(/#/)
.. h[k] << v
.. h
.. end
=> {"p"=>["uu"], "m"=>["xx", "zz"], "n"=>["yy"]}
Simpler code for a newbie :)
str = "uu#p, xx#m, yy#n, zz#m"
h = {}
str.split(",").each do |x|
v,k = x.split('#')
h[k] ||= []
h[k].push(v)
end
p h
FP style:
grouped = str
.split(", ")
.group_by { |s| s.split("#")[1] }
.transform_values { |ss| ss.map { |x| s.split("#")[0] } }
#=> {"m"=>["xx", "zz"], "n"=>["yy"], "p"=>["uu"]}
This is a pretty common pattern. Using Facets.map_by:
require 'facets'
str.split(", ").map_by { |s| s.split("#", 2).reverse }
#=> {"m"=>["xx", "zz"], "n"=>["yy"], "p"=>["uu"]}

Map an array modifying only elements matching a certain condition

In Ruby, what is the most expressive way to map an array in such a way that certain elements are modified and the others left untouched?
This is a straight-forward way to do it:
old_a = ["a", "b", "c"] # ["a", "b", "c"]
new_a = old_a.map { |x| (x=="b" ? x+"!" : x) } # ["a", "b!", "c"]
Omitting the "leave-alone" case of course if not enough:
new_a = old_a.map { |x| x+"!" if x=="b" } # [nil, "b!", nil]
What I would like is something like this:
new_a = old_a.map_modifying_only_elements_where (Proc.new {|x| x == "b"})
do |y|
y + "!"
end
# ["a", "b!", "c"]
Is there some nice way to do this in Ruby (or maybe Rails has some kind of convenience method that I haven't found yet)?
Thanks everybody for replying. While you collectively convinced me that it's best to just use map with the ternary operator, some of you posted very interesting answers!
Because arrays are pointers, this also works:
a = ["hello", "to", "you", "dude"]
a.select {|i| i.length <= 3 }.each {|i| i << "!" }
puts a.inspect
# => ["hello", "to!", "you!", "dude"]
In the loop, make sure you use a method that alters the object rather than creating a new object. E.g. upcase! compared to upcase.
The exact procedure depends on what exactly you are trying to achieve. It's hard to nail a definite answer with foo-bar examples.
old_a.map! { |a| a == "b" ? a + "!" : a }
gives
=> ["a", "b!", "c"]
map! modifies the receiver in place, so old_a is now that returned array.
I agree that the map statement is good as it is. It's clear and simple,, and would easy
for anyone to maintain.
If you want something more complex, how about this?
module Enumerable
def enum_filter(&filter)
FilteredEnumerator.new(self, &filter)
end
alias :on :enum_filter
class FilteredEnumerator
include Enumerable
def initialize(enum, &filter)
#enum, #filter = enum, filter
if enum.respond_to?(:map!)
def self.map!
#enum.map! { |elt| #filter[elt] ? yield(elt) : elt }
end
end
end
def each
#enum.each { |elt| yield(elt) if #filter[elt] }
end
def each_with_index
#enum.each_with_index { |elt,index| yield(elt, index) if #filter[elt] }
end
def map
#enum.map { |elt| #filter[elt] ? yield(elt) : elt }
end
alias :and :enum_filter
def or
FilteredEnumerator.new(#enum) { |elt| #filter[elt] || yield(elt) }
end
end
end
%w{ a b c }.on { |x| x == 'b' }.map { |x| x + "!" } #=> [ 'a', 'b!', 'c' ]
require 'set'
Set.new(%w{ He likes dogs}).on { |x| x.length % 2 == 0 }.map! { |x| x.reverse } #=> #<Set: {"likes", "eH", "sgod"}>
('a'..'z').on { |x| x[0] % 6 == 0 }.or { |x| 'aeiouy'[x] }.to_a.join #=> "aefiloruxy"
Your map solution is the best one. I'm not sure why you think map_modifying_only_elements_where is somehow better. Using map is cleaner, more concise, and doesn't require multiple blocks.
One liner:
["a", "b", "c"].inject([]) { |cumulative, i| i == "b" ? (cumulative << "#{i}!") : cumulative }
In the code above, you start with [] "cumulative". As you enumerate through an Enumerator (in our case the array, ["a", "b", "c"]), cumulative as well as "the current" item get passed to our block (|cumulative, i|) and the result of our block's execution is assigned to cumulative. What I do above is keep cumulative unchanged when the item isn't "b" and append "b!" to cumulative array and return it when it is a b.
There is an answer above that uses select, which is the easiest way to do (and remember) it.
You can combine select with map in order to achieve what you're looking for:
arr = ["a", "b", "c"].select { |i| i == "b" }.map { |i| "#{i}!" }
=> ["b!"]
Inside the select block, you specify the conditions for an element to be "selected". This will return an array. You can call "map" on the resulting array to append the exclamation mark to it.
Ruby 2.7+
As of 2.7 there's a definitive answer.
Ruby 2.7 is introducing filter_map for this exact purpose. It's idiomatic and performant, and I'd expect it to become the norm very soon.
For example:
numbers = [1, 2, 5, 8, 10, 13]
enum.filter_map { |i| i * 2 if i.even? }
# => [4, 16, 20]
Here's a good read on the subject.
Hope that's useful to someone!
If you don't need the old array, I prefer map! in this case because you can use the ! method to represent you are changing the array in place.
self.answers.map!{ |x| (x=="b" ? x+"!" : x) }
I prefer this over:
new_map = self.old_map{ |x| (x=="b" ? x+"!" : x) }
It's a few lines long, but here's an alternative for the hell of it:
oa = %w| a b c |
na = oa.partition { |a| a == 'b' }
na.first.collect! { |a| a+'!' }
na.flatten! #Add .sort! here if you wish
p na
# >> ["b!", "a", "c"]
The collect with ternary seems best in my opinion.
I've found that the best way to accomplish this is by using tap
arr = [1,2,3,4,5,6]
[].tap do |a|
arr.each { |x| a << x if x%2==0 }
end

Resources