Get values from hash in ruby - ruby

I have hash that looks like this:
{"0"=>{"car_addition_id"=>"9"}, "1"=>{"car_addition_id"=>"10"}}
I want to get from this hash only this "10" and "9" values in array. How can I do that?
Thanks in advance!

try this out:
h = {"0"=>{"car_addition_id"=>"9"}, "1"=>{"car_addition_id"=>"10"}}
h.map{|k,v| v.values}.flatten
=> ["9", "10"]
or as per #gotva
h.flat_map{|k,v| v.values}

{"0"=>{"car_addition_id"=>"9"}, "1"=>{"car_addition_id"=>"10"}}
.values.flat_map(&:values)
# => ["9", "10"]

Assuming you want the values associated with the key "car_addition_id" (and not whatever key appears in those locations in the expression), the most straightforward way is:
h.map { |_k,v| v["car_addition_id"] }
#=> ["9", "10"]
This of course works if the inner hashes contain multiple key/value pairs.
For those unfamiliar with the notation, _k and _ are both placeholders for an unused block variable; the former can be used to indicate how the block variable would have been written had it been used.

Related

Ruby Hash: type casting

I’m trying to get a better grasp on writing in Ruby and working with Hash tables and their values.
1. Say you have a hash:
‘FOO’= {‘baz’ => [1,2,3,4,5]}
Goal: convert each value into a string in the ‘Ruby’ way.
I’ve come across multiple examples of using .each eg.
FOO.each = { |k,v| FOO[k] = v.to_s }
However this renders an array encapsulated in a string. Eg. "[1,2,3,4,5]" where it should be ["1", "2", "3", "4", "5"].
2. When type casting is performed on a Hash that’s holds an array of values, is the result a new array? Or simply a change in type of value (eg. 1 becomes “1” when .to_s is applied (say the value was placed through a each enumerator like above).
An explanation is greatly appreciated. New to Ruby.
In the each block, k and v are the key value pair. In your case, 'baz' is key and [1,2,3,4,5] is value. Since you're doing v.to_s, it converts the whole array to string and not the individual values.
You can do something like this to achieve what you want.
foo = { 'baz' => [1,2,3,4,5] }
foo.each { |k, v| foo[k] = v.map(&:to_s) }
You can use Hash#transform_values:
foo = { 'baz' => [1, 2, 3, 4, 5] }
foo.transform_values { |v| v.map(&:to_s) } #=> {"baz"=>["1", "2", "3", "4", "5"]}

Creating a hash where the keys are strings, values are numbers

I have an array:
["Melanie", "149", "Joe", "2", "16", "216", "Sarah"]
I want to create a hash:
{"Melanie"=>[149], "Joe"=>[2, 16, 216] "Sarah"=>nil}
How would I accomplish this when the keys and values are in the same array?
All values would be integers (although they are in string form in the array.) All keys start and end with a letter.
Your expected hash is invalid. Therefore, it is impossible to get what you wrote that you want.
From your issue, it looks reasonable to expect the values to be array. In that case, you can do it like this:
["Melanie", "149", "Joe", "2", "16", "216", "Sarah"]
.slice_before(/[a-z]/i).map{|k, *v| [k, v.map(&:to_i)]}.to_h
# => {"Melanie"=>[149], "Joe"=>[2, 16, 216], "Sarah"=>[]}
With little modification, you can let the value be a number instead of an array when the array length is one, but that is not a good design; it would introduce exceptions.
Try this
def numeric?(x)
x.chars.all? { |y| ('0'..'9').include?(y) }
end
array = ["Melanie", "149", "Joe", "2", "16", "216", "Sarah"]
keys = array.select { |x| not numeric?(x) }
map = {}
keys.each do |k|
from = array.index(k) + 1
to = array.index( keys[keys.index(k) + 1] )
map[k] = to ? array[from...to] : array[from..from]
end
p map
Output:
{"Melanie"=>["149"], "Joe"=>["2", "16", "216"], "Sarah"=>[]}
[Finished in 0.1s]
Here's another way:
arr = ["Melanie", "149", "Joe", "2", "16", "216", "Sarah"]
class String
def integer?
!!(self =~ /^-?\d+$/)
end
end
Hash[*arr.each_with_object([]) { |s,a| s.integer? ? a[-1] << s.to_i : a<<s<<[] }].
tap { |h| h.each_key { |k| h[k] = nil if h[k].empty? } }
#=> {"Melanie"=>[149], "Joe"=>[2, 16, 216], "Sarah"=>nil}
There are three components to your question, and I will try to answer them separately.
Regarding storing a multi-valued mapping, while there are specialized solutions available, the most common recommendation is just to store a hash whose values are arrays. That is, for your use case, your primary data structure is a hash whose keys are strings and whose values are arrays of integers. Depending on your desired behavior for duplicates etc., etc, you may wish to substitute a different data structure for the value structure, possibly a set.
Regarding identifying strings containing numbers and strings not containing numbers, well, that depends on exactly what your non-number-containing strings could instead contain, but a good starting point would be to perform a regular expression match for digits. You didn't specify whether your allowable numeric strings represented integers, floating points, etc. The particular answer to that may affect your overall strategy. Unfortunately, input parsing and validation is a complex and messy topic in the general case.
Regarding the actual conversion process, I would recommend the following strategy. Iterate through your input array. Check each string for whether it is numeric or non-numeric. If it is non-numeric, store that as the current key in a local. Also, in your hash, create a mapping from that key to a new empty array. If, instead, the string is numeric, convert it into a number, and add it to the array under the appropriate key.
I don't know if there's a pretty way to do it. I'd do something like this:
def numeric?(string)
# `!!` converts parsed number to `true`
!!Kernel.Float(string)
rescue TypeError, ArgumentError
false
end
def my_method(input_array)
# associate values with proper key and stores result in output
curr_key = nil
output = {}
input_array.each do |e|
if !numeric?(e)
output[e] = []
curr_key = e
else
# use Float if values may be floating-point
output[curr_key] << Integer(e, 10)
end
end
output.each do |k, v|
output[k] = v.empty? ? nil : v
end
output
end
Source for numeric method.

access hashes of hashes values in ruby

I have a nested hashes in ruby and I need to access a specific value of it. My hash look like below.
hash =
{"list"=>
{"0"=>
{"date"=>"11/03/2014",
"item1"=>"",
"tiem2"=>"News",
"item3"=>"",
"item4"=>"",
"item5"=>"Videos",
"Type"=>"Clip"},
"1"=>
{"date"=>"11/03/2014",
"item1"=>"",
"tiem2"=>"News",
"item3"=>"",
"item4"=>"",
"item5"=>"Videos",
"Type"=>"Program"}
}}
I need to access the value of "Type" of each keys.
I tried with the below code but I am not sure why it didn't work.
hash_type = hash["list"].keys.each {|key| puts key["Type"]}
But it returned the list of keys. i.e 0 and 1
Please help.
hash["list"].map {|_, hash| hash['Type']}
Explanation:
hash = {key: 'value'}
You can loop over a hash using each like this:
hash.each {|pair| puts pair.inspect } #=> [:key, 'value']
or like this
hash.each {|key, value| puts "#{key}: #{value}"} #=> key: value
Since we don't use key anywhere, some of the IDEs will complain about unused local variable key. To prevent this it is ruby convention to use _ for variable name and all the IDEs will not care for it to be unused.
hash['list'].collect { |_, value| value['Type'] }
=> ["Clip", "Program"]
This is following your logic (some answers posted different ways to do this). The reason why you go things wrong, if we go step by step is:
hash_type = hash["list"].keys #=> ["0", "1"]
So everything after that is the same like:
["0", "1"].each {|key| puts key["Type"]}
So you're basically doing puts '1'['Type'] and '0'['Type'] which both evaluate to nil (try it in IRB) . Try replacing the puts with p and you'll get nil printed 2 times. The reason why you're getting hash_type to be ["0", "1"] is because your last expression is keys.each and each ALWAYS return the "receiver", that is the array on which you called each (as we saw earlier, that array is ["0", "1"]).
The key to solving this, following your particular logic, is to put the "keys" (which are '0' and '1' in this instance) in the appropriate context, and putting them in a context would look something like this:
hash_type = hash["list"].keys.each {|key| puts hash["list"][key]["Type"]}`
This will print the keys. However, hash_type will still be ["0", "1"] (remember, each returns the value of the receiver). If you want the actual type values to be stored in hash_types, replace each with map and remove puts:
hash_type = hash["list"].keys.map {|key| hash["list"][key]["Type"]} #=> ["Clip", "Program"]

How do I split a hash into two arrays based on key and value in Ruby?

So I am trying to split a Hash into two Arrays, one with the keys and one with the values.
So far I have:
hash = { Matsumoto: "Ruby", Ritchie: "C", Backus: "Fortran", McCarthy: "Lisp" }
Im able to make an array out of keys or values like so:
hash.map { |creator, proglang| creator }
But I'm unable to make two arrays, one containing the keys and one containing the values. I've played around with a number of methods and I'm at a loss.
Thank you.
keys, values = hash.keys, hash.values
> keys
# => [:Matsumoto, :Ritchie, :Backus, :McCarthy]
> values
# => ["Ruby", "C", "Fortran", "Lisp"]
You can refer to Hash class methods:
hash.keys
hash.values
which return arrays of keys and values respectively
See
http://www.ruby-doc.org/core-2.1.0/Hash.html
for more details
keys, values = hash.to_a.transpose
#=> [[:Matsumoto, :Ritchie, :Backus , :McCarthy],
# ["Ruby" , "C" , "Fortran", "Lisp" ]]
also works, but keys() and values() are provided for this very task.

Creating a hash from two arrays with identical values in Ruby

I'm having issues creating a hash from 2 arrays when values are identical in one of the arrays.
e.g.
names = ["test1", "test2"]
numbers = ["1", "2"]
Hash[names.zip(numbers)]
works perfectly it gives me exactly what I need => {"test1"=>"1", "test2"=>"2"}
However if the values in "names" are identical then it doesn't work correctly
names = ["test1", "test1"]
numbers = ["1", "2"]
Hash[names.zip(numbers)]
shows {"test1"=>"2"} however I expect the result to be {"test1"=>"1", "test1"=>"2"}
Any help is appreciated
Hashes can't have duplicate keys. Ever.
If they were permitted, how would you access "2"? If you write myhash["test1"], which value would you expect?
Rather, if you expect to have several values under one key, make a hash of arrays.
names = ["test1", "test1", "test2"]
numbers = ["1", "2", "3"]
Hash.new.tap { |h| names.zip(numbers).each { |k, v| (h[k] ||= []) << v } }
# => {"test1"=>["1", "2"], "test2"=>["3"]}

Resources