Pushing Hash onto Array: last Hash overwriting previous array elements - arrays

I have the following Ruby script:
arr = ['bob', 'jack', 'smith']
array_of_hashes = Array.new
hash = Hash.new
arr.each do |item|
hash.clear
hash[:name] = item
array_of_hashes << hash
end
puts array_of_hashes
This returns an array of hashes, whose :name keys are all from the last element.
[
[0] {
:name => "smith"
},
[1] {
:name => "smith"
},
[2] {
:name => "smith"
}
]
I would expect it to return the following, but I'm stuck trying to figure out why the last Hash element is overwriting all previous array elements:
[
[0] {
:name => "bob"
},
[1] {
:name => "jack"
},
[2] {
:name => "smith"
}
]
EDIT: Thank you all for your answers. It's nice to have some different methods laid out of accomplishing the same thing. I've tested each solution and each one is great. I ended up using the one which was most similar to my original code; but my use case is a simple, local script - it's not being used in an industrial-sized app - in which case, I would probably choose a different solution.

Look at your code carefully, you'll see that you've only created a single instance of Hash. Your iterator keeps blowing away earlier efforts with .clear, setting the element to the current iterate, and adding the reference to that one Hash object to different array locations in array_of_hashes. But at the end of the day, they all point to the same Hash object, and that Hash object contains only the last thing you put into it.

Take note of (and understand) the answer by #pjs and the comment by #tadman as they explain why you are getting the results you are getting.
I would actually instead do something like:
names = ['bob', 'jack', 'smith']
# combine the results into an array of Hashes
array_of_hashes =
names.collect do |item|
{ name: item }
end
# output the results
array_of_hashes # [{name: "bob"}, {name: "jack"},{name:"smith"}]
Some notes:
The Array#collect method will return an array, so you don't need to manually add to an array you've initialized your self.
You can just return a new Hash for each item which will be returned in the collect block

You should try something like
arr = ['bob', 'jack', 'smith']
array_of_hashes = Array.new
arr.each { |item|
hash = Hash.new
hash[:name] = item
array_of_hashes << hash
}
puts array_of_hashes
with a new instance being pushed into the array for every new item.

I would write
[:name].product(arr).map { |pair| Hash[[pair]] }
#=> [{:name=>"bob"}, {:name=>"jack"}, {:name=>"smith"}]
or (for Ruby v2.0+)
[:name].product(arr).map { |pair| [pair].to_h }
The steps:
a = [:name].product(arr)
#=> [[:name, "bob"], [:name, "jack"], [:name, "smith"]]
enum = a.map
# => #<Enumerator: [[:name, "bob"], [:name, "jack"], [:name, "smith"]]:map>
Note that by sending the method Enumerator#each to enum we obtain the desired result:
enum.each { |pair| [pair].to_h }
#=> [{:name=>"bob"}, {:name=>"jack"}, {:name=>"smith"}]
(Enumerator#each will invoke Array#each because a.class #=> Array.)
Enumerator#each sets the block variable to the first element of enum:
pair = enum.next
#=> [:name, "bob"]
and the block calculation is performed:
[pair].to_h
#=> [[:name, "bob"]].to_h
#=> {:name=>"bob"}
Next, the second element of enum, [:name, "jack"], is mapped to:
pair = enum.next
#=> [:name, "jack"]
[pair].to_h
#=> [[:name, "jack"]].to_h
#=> {:name=>"jack"}
Lastly,
pair = enum.next
#=> [:name, "smith"]
[pair].to_h
#=> [[:name, "smith"]].to_h
#=> {:name=>"smith"}

You can try this too
>>> names = ['bob', 'jack', 'smith']
>>> names.inject([]){|s,p| s << {name: p}}
=> [{:name=>"bob"}, {:name=>"jack"}, {:name=>"smith"}]

Related

Ruby converting Array of Arrays into Array of Hashes

Please I need a help with this.
In Ruby If I have this array of arrays
array = [["a: 1", "b:2"],["a: 3", "b:4"]]
How can I obtain this array of hashes in ruby
aoh = [{:a => "1", :b => "2"},{:a => "3", :b => "4"}]
Note that, like pointed out in the comments, this is most likely an XY-problem and instead of transforming the array the better option is to build the starting array in a better manner.
Nevertheless, you can do this in the following manner:
aoh = array.map { |array| array.to_h { |string| string.split(':').map(&:strip) } }
# => [{"a"=>"1", "b"=>"2"}, {"a"=>"3", "b"=>"4"}]
The above will give you string keys which is the safer option to go with. You can convert them to symbols, but they should only be used for trusted identifiers. When the data comes from an user or external source I would go for the above.
Converting to symbols can be done by adding the following line:
# note that this line will mutate the aoh contents
aoh.each { |hash| hash.transform_keys!(&:to_sym) }
#=> [{:a=>"1", :b=>"2"}, {:a=>"3", :b=>"4"}]
array = [["a: 1", "b:2"], ["a: 3", "b:4"]]
array.map do |a|
Hash[
*a.flat_map { |s| s.split(/: */) }.
map { |s| s.match?(/\A\d+\z/) ? s : s.to_sym }
]
end
#=> [{:a=>"1", :b=>"2"}, {:a=>"3", :b=>"4"}]
The regular expression /: */ reads, "match a colon followed by zero or more (*) spaces". /\A\d+\z/ reads, "match the beginning of the string (\A) followed by one or more (+) digits (\d), followed by the end of the string (\z).
The steps are as follows. The first is for the element arr[0] to be passed to the block, the block variable a assigned its value and the block calculation performed.
a = array[0]
#=> ["a: 1", "b:2"]
b = a.flat_map { |s| s.split(/: */) }
#=> ["a", "1", "b", "2"]
c = b.map { |s| s.match?(/\A\d+\z/) ? s : s.to_sym }
#=> [:a, "1", :b, "2"]
d = Hash[*c]
#=> {:a=>"1", :b=>"2"}
We see the array ["a: 1", "b:2"] is mapped to {:a=>"1", :b=>"2"}. Next the element arr[1] is passed to the block, the block variable a is assigned its value and the block calculation is performed.
a = array[1]
#=> ["a: 3", "b:4"]
b = a.flat_map { |s| s.split(/: */) }
#=> ["a", "3", "b", "4"]
c = b.map { |s| s.match?(/\d/) ? s : s.to_sym }
#=> [:a, "3", :b, "4"]
d = Hash[*c]
#=> {:a=>"3", :b=>"4"}
The splat operator (*) causes Hash[*c] to be evaluated as:
Hash[:a, "3", :b, "4"]
See Hash::[].
Loop through your items, loop through its items, create a new array:
array.map do |items|
items.map do |item|
k,v = item.split(":", 2)
{ k.to_sym => v }
}
}
Note that we're using map instead of each which will return an array.

Make an array from list where first element of the array is the first element in list

I am very new to ruby so excuse me if this is a basic question.
I have a list that looks like this:
[name, max, john, raj, sam]
And i want to make a new array that will look like this:
[[name, max], [name, john], [name, raj], [name, sam]]
Here is what i am trying to do:
row.xpath('td').each_with_index do |cell, index|
if index == 0
tarray['name'] << cell.text
else
tarray['values'] << cell.text
end
I know i am doing it wrong because when i have ['name'] it will not be logically possible to have ['values']
Please advice me on how i can achieve this using the best method.
You can take the first value (:name) and get its product with every remaning element by using Array#product:
array = [:name, :max, :john, :raj, :sam]
p [array[0]].product(array[1..-1])
# [[:name, :max], [:name, :john], [:name, :raj], [:name, :sam]]
Array#product is better, but you can also use Array#zip:
ary = [:a, :b, :c, :d]
([ary.first]*(ary.size-1)).zip ary[1..]
#=> [[:a, :b], [:a, :c], [:a, :d]]
Or also:
ary.then { |a, *rest| ([a] * rest.size).zip rest }
But yes, product is cleaner:
ary.then { |a, *rest| [a].product rest }

From a hash with array elements, how can I find the key for a particular array value?

Every value of my hash is an array:
tab_details = {
'ex1' => ['1', '2']
}
I want to pass an element and get the key that corresponds to an array that has the element.
This is one way to do so:
tab_details.each{|k, v|
return key = k if tab_details[k].include? '1'
}
Is there any other optimal way to handle this?
One (not so direct) way of doing this can be to invert the Hash by expanding each array to make each element a key:
tab_details = {
'foo' => [2, 3, 5],
'bar' => [1, 4, 6]
}
# => {"foo"=>[2, 3, 5], "bar"=>[1, 4, 6]}
inverted_hash = tab_details.each_with_object({}) do |(k, ary), out|
ary.each { |elem| out[elem] = k }
end
# => {2=>"foo", 3=>"foo", 5=>"foo", 1=>"bar", 4=>"bar", 6=>"bar"}
input = 1
inverted_hash[input]
# => "bar"
Now, of course, it assumes that the input and array elements are of same type i.e. Integer. You can convert them acc. to what is given in your question:
# If all the array elements are strings
inverted_hash[input.to_s]
# => "bar"
Another assumption it makes is, that no element is present in more than one array.
tab_details.find { |_,v| v.include?("1") }&.first
#=> "ex1"
tab_details.find { |_,v| v.include?("cat") }&.first
#=> nil
& is the Safe Navigation Operator. In the second example above the block returns nil so, because of the SNO, first is not executed and nil is returned.
You can use detect method
k, v = tab_details.detect{ |_,v| v.include?("1") }
p k
hash.key(value) => key
In your case,
irb(main):012:0> tab_details.key(['1', '2'])
=> "ex1"
> tab_details = {"ex1"=>["1", "2"], "ex2"=>["3", "4", "5"], "ex3"=>["2", "1", "9"]}
> tab_details.select{|k,v| v.include? "1"}.keys
#=> ["ex1", "ex3"]
to check against integer convert array elements to integer using map(&:to_i)
> tab_details.select{|k,v| v.map(&:to_i).include? 1}.keys
#=> ["ex1", "ex3"]
use select if same element can be in multiple arrays

Working with Transpose functions result in error

consider the following array
arr = [["Locator", "Test1", "string1","string2","string3","string4"],
["$LogicalName", "Create Individual Contact","value1","value2"]]
Desired result:
[Test1=>{"string1"=>"value1","string2"=>"value2","string3"=>"","string4"=>""}]
When I do transpose, it gives me the error by saying second element of the array is not the length of the first element in the array,
Uncaught exception: element size differs (2 should be 4)
so is there any to add empty string in the place where there is no element and can perform the transpose and then create the hash as I have given above? The array may consist of many elements with different length but according to the size of the first element in the array, every other inner array has to change by inserting empty string and then I can do the transpose. Is there any way?
It sounds like you might want Enumerable#zip:
headers, *data_rows = input_data
headers.zip(*data_rows)
# => [["Locator", "$LogicalName"], ["Test1", "Create Individual Contact"],
# ["string1", "value1"], ["string2", "value2"], ["string3", nil], ["string4", nil]]
If you wish to transpose an array of arrays, each element of the array must be the same size. Here you would need to do something like the following.
arr = [["Locator", "Test1", "string1","string2","string3","string4"],
["$LogicalName", "Create Individual Contact","value1","value2"]]
keys, vals = arr
#=> [["Locator", "Test1", "string1", "string2", "string3", "string4"],
# ["$LogicalName", "Create Individual Contact", "value1", "value2"]]
idx = keys.index("Test1") + 1
#=> 2
{ "Test1" => [keys[idx..-1],
vals[idx..-1].
concat(['']*(keys.size - vals.size))].
transpose.
to_h }
#=> {"Test1"=>{"string1"=>"value1", "string2"=>"value2", "string3"=>"", "string4"=>""}}
It is not strictly necessary to define the variables keys and vals, but that avoids the need to create those arrays multiple times. It reads better as well, in my opinion.
The steps are as follows. Note keys.size #=> 6 and vals.size #=> 4.
a = vals[idx..-1]
#=> vals[2..-1]
#=> ["value1", "value2"]
b = [""]*(keys.size - vals.size)
#=> [""]*(4 - 2)
#=> ["", ""]
c = a.concat(b)
#=> ["value1", "value2", "", ""]
d = keys[idx..-1]
#=> ["string1", "string2", "string3", "string4"]
e = [d, c].transpose
#=> [["string1", "value1"], ["string2", "value2"], ["string3", ""], ["string4", ""]]
f = e.to_h
#=> {"string1"=>"value1", "string2"=>"value2", "string3"=>"", "string4"=>""}
f = e.to_h
#=> { "Test1" => f }
Find the longest Element in your Array and make sure every other element has the same length - loop and add maxLength - element(i).length amount of "" elements.

ruby how to add a hash element into an existing json array

I have code that outputs the following:
car_id = ["123"]
model = [:model].product(car_id).map { |k,v| {k=>v} }
model = [{:model=>"123"}]
I would like to then add a new hash :make into the json like this:
model_with_make = [{:model=>"123", :make => "acura"}]
How do I do this?
Unfortunately, every solution I find produces this:
[{:model=>"123"}, {:make => "acura"}] and that is not what I want.
Ruby convention for showing result of a calculation
I assume you mean that
model = [:model].product(car_id).map { |k,v| {k=>v} }
produces an array containing a single hash:
[{:model=>"123"}]
and that you are not then executing the statement:
model = [{:model=>"123"}]
which would overwrite the the value of model from the previous statement, rendering the previous statement meaningless. If so, the normal way to write that is as follows.
model = [:model].product(car_id).map { |k,v| {k=>v} }
#=> [{:model=>"123"}]
Computing the array
Next,
arr = [:model].product(car_id)
#=> [[:model, "123"]]
But why use Array#product when the arrays [:model] and car_id both contain a single element? It's simpler to just write
arr = [[:model, car_id.first]]
#=> [[:model, "123"]]
Converting the array to a hash
Since arr contains only one element, there's not much point to mapping it; just convert it to a hash:
Hash[arr]
#=> {:model=>"123"}
or (for Ruby versions 1.9+):
arr.to_h
#=> {:model=>"123"}
Add a key-value pair to the hash
If you wish to add the key-value pair :make => "acura" to
h = {:model=>"123"}
you can simply write
h[:make] = "acura"
#=> "acura"
h #=> {:model=>"123", :make=>"acura"}
or, in one line,
(h[:make] = "acura") && h
#=> {:model=>"123", :make=>"acura"}
Wrapping up
Putting this together, you could write
h = [[:model, car_id.first]].to_h
#=> {:model=>"123"}
(h[:make] = "acura") && h
#=> {:model=>"123", :make=>"acura"}
model = [{:model=>"123"}]
model_with_make = model.map { |m| m.merge(make: 'acura') }
#⇒ [{:model=>"123", :make => "acura"}]
If you are concerned that there is the only element in the array model, you might modify it inplace:
model.first.merge!(make: 'acura')
model
#⇒ [{:model=>"123", :make => "acura"}]

Resources