Ruby, how to create a hash from two arrays? - ruby

I'm fairly a beginner in Ruby and I'm trying to do the following:
Let's say I have two arrays:
array_1 = ["NY", "SF", "NL", "SY"]
array_2 = ["apple", "banana"]
I want to merge the arrays to a hash so each object in array_1 will be assigned with the objects in array_2
Thanks in advance.

x = [:foo, :bar, :baz]
y = [1, 2, 3]
x.zip(y).to_h # => {:foo=>1, :bar=>2, :baz=>3}

You can use the zip method, like so:
Hash[array_2.zip(array_1)]

h = array_1.product([array_2]).to_h
#=> {"NY"=>["apple", "banana"], "SF"=>["apple", "banana"],
# "NL"=>["apple", "banana"], "SY"=>["apple", "banana"]}
We were given Array#to_h in MRI v2.0. For earlier versions, use Kernel#Hash:
h = Hash[array_1.product([array_2])]
but beware:
array_2[0] = "cat"
array_2
#=> ["cat", "banana"]
h #=> {"NY"=>["cat", "banana"], "SF"=>["cat", "banana"],
# "NL"=>["cat", "banana"], "SY"=>["cat", "banana"]}
You may instead want:
h = array_1.each_with_object({}) { |str,h| h[str] = array_2.dup }
#=> {"NY"=>["apple", "banana"], "SF"=>["apple", "banana"],
# "NL"=>["apple", "banana"], "SY"=>["apple", "banana"]}
array_2[0] = "cat"
h #=> {"NY"=>["apple", "banana"], "SF"=>["apple", "banana"],
# "NL"=>["apple", "banana"], "SY"=>["apple", "banana"]}

Related

Looking to convert information from a file into a hash Ruby

Hello I have been doing some research for sometime on this particular project I have been working on and I am at a loss. What I am looking to do is use information from a file and convert that to a hash using some of those components for my key. Within the file I have:1,Foo,20,Smith,40,John,55
An example of what I am looking for I am looking for an output like so {1 =>[Foo,20], 2 =>[Smith,40] 3 => [John,55]}
Here is what I got.
h = {}
people_file = File.open("people.txt") # I am only looking to read here.
until people_file.eof?
i = products_file.gets.chomp.split(",")
end
people_file.close
FName = 'test'
str = "1,Foo,20,Smith, 40,John,55"
File.write(FName, str)
#=> 26
base, *arr = File.read(FName).
split(/\s*,\s*/)
enum = (base.to_i).step
arr.each_slice(2).
with_object({}) {|pair,h| h[enum.next]=pair}
#=> {1=>["Foo", "20"], 2=>["Smith", "40"],
# 3=>["John", "55"]}
The steps are as follows.
s = File.read(FName)
#=> "1,Foo,20,Smith, 40,John,55"
base, *arr = s.split(/\s*,\s*/)
#=> ["1", "Foo", "20", "Smith", "40", "John", "55"]
base
#=> "1"
arr
#=> ["Foo", "20", "Smith", "40", "John", "55"]
a = base.to_i
#=> 1
I assume the keys are to be sequential integers beginning with a #=> 1.
enum = a.step
#=> (1.step)
enum.next
#=> 1
enum.next
#=> 2
enum.next
#=> 3
Continuing,
enum = a.step
b = arr.each_slice(2)
#=> #<Enumerator: ["Foo", "20", "Smith", "40", "John", "55"]:each_slice(2)>
Note I needed to redefine enum (or execute enum.rewind) to reinitialize it. We can see the elements that will be generated by this enumerator by converting it to an array.
b.to_a
#=> [["Foo", "20"], ["Smith", "40"], ["John", "55"]]
Continuing,
c = b.with_object({})
#=> #<Enumerator: #<Enumerator: ["Foo", "20", "Smith", "40", "John", "55"]
# :each_slice(2)>:with_object({})>
c.to_a
#=> [[["Foo", "20"], {}], [["Smith", "40"], {}], [["John", "55"], {}]]
The now-empty hashes will be constructed as calculations progress.
c.each {|pair,h| h[enum.next]=pair}
#=> {1=>["Foo", "20"], 2=>["Smith", "40"], 3=>["John", "55"]}
To see how the last step is performed, each initially directs the enumerator c to generate the first value, which it passes to the block. The block variables are assigned to that value, and the block calculation is performed.
enum = a.step
b = arr.each_slice(2)
c = b.with_object({})
pair, h = c.next
#=> [["Foo", "20"], {}]
pair
#=> ["Foo", "20"]
h #=> {}
h[enum.next]=pair
#=> ["Foo", "20"]
Now,
h#=> {1=>["Foo", "20"]}
The calculations are similar for the remaining two elements generated by the enumerator c.
See IO::write, IO::read, Numeric#step, Enumerable#each_slice, Enumerator#with_object, Enumerator#next and Enumerator#rewind. write and read respond to File because File is a subclass of IO (File.superclass #=> IO). split's argument, the regular expression, /\s*,\s*/, causes the string to be split on commas together with any spaces that surround the commas. Converting [["Foo", "20"], {}] to pair and h is a product of Array Decompostion.

Multiple sub-hashes out of one hash

I have a hash:
hash = {"a_1_a" => "1", "a_1_b" => "2", "a_1_c" => "3", "a_2_a" => "3",
"a_2_b" => "4", "a_2_c" => "4"}
What's the best way to get the following sub-hashes:
[{"a_1_a" => "1", "a_1_b" => "2", "a_1_c" => "3"},
{"a_2_a" => "3", "a_2_b" => "4", "a_2_c" => "4"}]
I want them grouped by the key, based on the regexp /^a_(\d+)/. I'll have 50+ key/value pairs in the original hash, so something dynamic would work best, if anyone has any suggestions.
If you're only concerned about the middle component you can use group_by to get you most of the way there:
hash.group_by do |k,v|
k.split('_')[1]
end.values.map do |list|
Hash[list]
end
# => [{"a_1_a"=>"1", "a_1_b"=>"2", "a_1_c"=>"3"}, {"a_2_a"=>"3", "a_2_b"=>"4", "a_2_c"=>"4"}]
The final step is extracting the grouped lists and combining those back into the required hashes.
Code
def partition_hash(hash)
hash.each_with_object({}) do |(k,v), h|
key = k[/(?<=_).+(?=_)/]
h[key] = (h[key] || {}).merge(k=>v)
end.values
end
Example
hash = {"a_1_a"=>"1", "a_1_b"=>"2", "a_1_c"=>"3", "a_2_a"=>"3", "a_2_b"=>"4", "a_2_c"=>"4"}
partition_hash(hash)
#=> [{"a_1_a"=>"1", "a_1_b"=>"2", "a_1_c"=>"3"},
# {"a_2_a"=>"3", "a_2_b"=>"4", "a_2_c"=>"4"}]
Explanation
The steps are as follows.
enum = hash.each_with_object({})
#=> #<Enumerator: {"a_1_a"=>"1", "a_1_b"=>"2", "a_1_c"=>"3", "a_2_a"=>"3",
# "a_2_b"=>"4", "a_2_c"=>"4"}:each_with_object({})>
The first element of this enumerator is generated and passed to the block, and the block variables are computed using parallel assignment.
(k,v), h = enum.next
#=> [["a_1_a", "1"], {}]
k #=> "a_1_a"
v #=> "1"
h #=> {}
and the block calculation is performed.
key = k[/(?<=_).+(?=_)/]
#=> "1"
h[key] = (h[key] || {}).merge(k=>v)
#=> h["1"] = (h["1"] || {}).merge("a_1_a"=>"1")
#=> h["1"] = (nil || {}).merge("a_1_a"=>"1")
#=> h["1"] = {}.merge("a_1_a"=>"1")
#=> h["1"] = {"a_1_a"=>"1"}
so now
h #=> {"1"=>{"a_1_a"=>"1"}}
The next value of enum is now generated and passed to the block, and the following calculations are performed.
(k,v), h = enum.next
#=> [["a_1_b", "2"], {"1"=>{"a_1_a"=>"1"}}]
k #=> "a_1_b"
v #=> "2"
h #=> {"1"=>{"a_1_a"=>"1"}}
key = k[/(?<=_).+(?=_)/]
#=> "1"
h[key] = (h[key] || {}).merge(k=>v)
#=> h["1"] = (h["1"] || {}).merge("a_1_b"=>"2")
#=> h["1"] = ({"a_1_a"=>"1"}} || {}).merge("a_1_b"=>"2")
#=> h["1"] = {"a_1_a"=>"1"}}.merge("a_1_b"=>"2")
#=> h["1"] = {"a_1_a"=>"1", "a_1_b"=>"2"}
After the remaining four elements of enum have been passed to the block the following has is returned.
h #=> {"1"=>{"a_1_a"=>"1", "a_1_b"=>"2", "a_1_c"=>"3"},
# "2"=>{"a_2_a"=>"3", "a_2_b"=>"4", "a_2_c"=>"4"}}
The final step is simply to extract the values.
h.values
#=> [{"a_1_a"=>"1", "a_1_b"=>"2", "a_1_c"=>"3"},
# {"a_2_a"=>"3", "a_2_b"=>"4", "a_2_c"=>"4"}]

Move elements of an array to a different array in Ruby

Simple ruby question. Lets say I have an array of 10 strings and I want to move elements at array[3] and array[5] into a totally new array. The new array would then only have the two elements I moved from the first array, AND the first array would then only have 8 elements since two of them have been moved out.
Use Array#slice! to remove the elements from the first array, and append them to the second array with Array#<<:
arr1 = ['Foo', 'Bar', 'Baz', 'Qux']
arr2 = []
arr2 << arr1.slice!(1)
arr2 << arr1.slice!(2)
puts arr1.inspect
puts arr2.inspect
Output:
["Foo", "Baz"]
["Bar", "Qux"]
Depending on your exact situation, you may find other methods on array to be even more useful, such as Enumerable#partition:
arr = ['Foo', 'Bar', 'Baz', 'Qux']
starts_with_b, does_not_start_with_b = arr.partition{|word| word[0] == 'B'}
puts starts_with_b.inspect
puts does_not_start_with_b.inspect
Output:
["Bar", "Baz"]
["Foo", "Qux"]
a = (0..9).map { |i| "el##{i}" }
x = [3, 5].sort_by { |i| -i }.map { |i| a.delete_at(i) }
puts x.inspect
# => ["el#5", "el#3"]
puts a.inspect
# => ["el#0", "el#1", "el#2", "el#4", "el#6", "el#7", "el#8", "el#9"]
As noted in comments, there is some magic to make indices stay in place. This can be avoided by first getting all the desired elements using a.values_at(*indices), then deleting them as above.
Code:
arr = ["null","one","two","three","four","five","six","seven","eight","nine"]
p "Array: #{arr}"
third_el = arr.delete_at(3)
fifth_el = arr.delete_at(4)
first_arr = arr
p "First array: #{first_arr}"
concat_el = third_el + "," + fifth_el
second_arr = concat_el.split(",")
p "Second array: #{second_arr}"
Output:
c:\temp>C:\case.rb
"Array: [\"null\", \"one\", \"two\", \"three\", \"four\", \"five\", \"six\", \"s
even\", \"eight\", \"nine\"]"
"First array: [\"null\", \"one\", \"two\", \"four\", \"six\", \"seven\", \"eight
\", \"nine\"]"
"Second array: [\"three\", \"five\"]"
Why not start deleting from the highest index.
arr = ['Foo', 'Bar', 'Baz', 'Qux']
index_array = [2, 1]
new_ary = index_array.map { |index| arr.delete_at(index) }
new_ary # => ["Baz", "Bar"]
arr # => ["Foo", "Qux"]
Here's one way:
vals = arr.values_at *pulls
arr = arr.values_at *([*(0...arr.size)] - pulls)
Try it.
arr = %w[Now is the time for all Rubyists to code]
pulls = [3,5]
vals = arr.values_at *pulls
#=> ["time", "all"]
arr = arr.values_at *([*(0...arr.size)] - pulls)
#=> ["Now", "is", "the", "for", "Rubyists", "to", "code"]
arr = %w[Now is the time for all Rubyists to code]
pulls = [5,3]
vals = arr.values_at *pulls
#=> ["all", "time"]
arr = arr.values_at *([*(0...arr.size)] - pulls)
#=> ["Now", "is", "the", "for", "Rubyists", "to", "code"]

How do I create a hash from this array?

I have an array that looks like this:
["value1=3", "value2=4", "value3=5"]
I'd like to end up with a hash like:
H['value1'] = 3
H['value2'] = 4
H['value3'] = 5
There's some parsing involved and I was hoping to get pointed in the right direction.
ary = ["value1=3", "value2=4", "value3=5"]
H = Hash[ary.map {|s| s.split('=') }]
This however will set all the values as strings '5' instead of integer. If you are sure they are all integers:
H = Hash[ary.map {|s| key, value = s.split('='); [key, value.to_i] }]
I'd do as #BroiSatse suggests, but here's another way that uses a Regex:
ary = ["value1=3", "value2=4", "value3=5"]
ary.join.scan(/([a-z]+\d+)=(\d+)/).map { |k,v| [k,v.to_i] }.to_h
=> {"value1"=>3, "value2"=>4, "value3"=>5}
Here's what's happening:
str = ary.join
#=> "value1=3value2=4value3=5"
a = str.scan(/([a-z]+\d+)=(\d+)/)
#=> [["value1", "3"], ["value2", "4"], ["value3", "5"]]
b = a.map { |k,v| [k,v.to_i] }
#=> [["value1", 3], ["value2", 4], ["value3", 5]]
b.to_h
#=> {"value1"=>3, "value2"=>4, "value3"=>5}
For Ruby versions < 2.0, the last line must be replaced with
Hash[b]
#=> {"value1"=>3, "value2"=>4, "value3"=>5}

dup gives different results when hash is one vs. two dimensions

dup is shallow copy, so when doing this:
h = {one: {a:'a', b: 'b'}}
h_copy = h.dup
h_copy[:one][:b] = 'new b'
now h and h_copy is same: {:one=>{:a=>"a", :b=>"new b"}}
yes, that right.
But when h is a one dimension hash:
h = {a:'a', b: 'b'}
h_copy = h.dup
h_copy[:b] = 'new b'
h still is: {a:'a', b: 'b'}
h_copy is {a:'a', b: 'new b'}
Why?
You can think about your two-dimensional hash as some kind of container, which conatins another hash container. So you have 2 containers.
When you call dup on h, then dup returns you copy of your outermost container, but any inner containers are not copied, so this is what shallow copy does. Now after dup you have 3 containers: h_copy is your new third container, which :one key just points to h's inner container
As you said, dup is shallow copy.
It appears you want both h_copy and h to refer to the same object.
Then simply do h_copy = h (i.e. no dup).
h = {a:'a', b: 'b'}
h_copy = h.dup
h_copy[:b] = 'new b'
h #=> {a:'a', b: 'new b'}
So after 1 hour of brainstorming..I have come to the conclusion that in the multi dimensional hashes, the dup generates the same object_id for each key which is in turn referring to the hash whereas in the single dimensional hash, the object_ids are similar initially but when we make any changes to the object the Ruby would assign new object_id to the hash keys..
Look at the following code
h = { :a => "a", :b => "b" } # => {:a=>"a", :b=>"b"}
h_clone = h.dup #=> {:a=>"a", :b=>"b"}
h.object_id #=> 73436330
h_clone.object_id #=> 73295920
h[:a].object_id #=> 73436400
h_clone[:a].object_id #=> 73436400
h[:b].object_id #=> 73436380
h_clone[:b].object_id #=> 73436380
h_clone[:b] = "New B" #=> "New B"
h_clone[:b].object_id #=> 74385280
h.object_id #=> 73436330
h_clone.object_id #=> 73295920
h[:a].object_id #=> 73436400
h_clone[:a].object_id #=> 73436400
Look the following code for the multidimensional array
h = { :one => { :a => "a", :b => "b" } } #=> {:one=>{:a=>"a", :b=>"b"}}
h_copy = h.dup #=> {:one=>{:a=>"a", :b=>"b"}}
h_copy.object_id #=> 80410620
h.object_id #=> 80552610
h[:one].object_id #=> 80552620
h_copy[:one].object_id #=> 80552620
h[:one][:a].object_id #=> 80552740
h_copy[:one][:a].object_id #=> 80552740
h[:one][:b].object_id #=> 80552700
h_copy[:one][:b].object_id #=> 80552700
h_copy[:one][:b] = "New B" #=> "New B"
h_copy #=> {:one=>{:a=>"a", :b=>"New B"}}
h #=> {:one=>{:a=>"a", :b=>"New B"}}
h.object_id #=> 80552610
h_copy.object_id #=> 80410620
h[:one].object_id #=> 80552620
h_copy[:one].object_id #=> 80552620
h[:one][:b].object_id #=> 81558770
h_copy[:one][:b].object_id #=> 81558770

Resources