Creating a Ruby hash with key:value lines - ruby

Here's another "what's the most elegant way to do X in Ruby" type of questions. Consider the response from a webservice with key:value pairs on each line of the return body, along the lines of
key1:val1
key2:val2
key3:val3
I want to create a Ruby hash with this data, {"key1" => "val1", ...}. Algorithmically I can git 'er done with
hash = {}
body.lines.each do |line|
key, val = line.split(':')
hash[key] = val
end
but I'm guessing there's a one-liner or two-liner that is even more elegant.

Here is my suggestion:
lines = [
'key1:val1',
'key2:val2',
'key3:val3'
]
hash = Hash[lines.map {|it| it.split(':', 2)}]
Explanation
Hash[object] creates a new hash from the object. Where the object is one of:
List of elements
List of pairs
Object that can be converted to hash

if you have pairs of objects, than Hash[] is your friend:
Hash[lines.map {|key_val| key_val.split(":")}]
=> {"key1"=>"val1", "key2"=>"val2", "key3"=>"val3"}
damn, too slow...

There are caret returns in the post, so real topic solution is
hash = Hash[lines.split(/\s/).map {|line| line.split(':', 2)}]

Related

Ruby pushing to a hash

I have a two part question and I apologize in advance if it is confusing at all. I'm trying to put user input into an empty hash. I know with an array you use the << to push the info to it. Is there a hash equivalent to this?
2nd part: Say I was just looping them the same question until a condition is met. The user input is going to be the value. Is there a way/method to make the key automatically change per the user input? So it would look something like:
{str1 => "example string", str2 => "example string2", str3 => "example string3"}
Or is there a way to have ruby assign a key on its own?
Sorry again if the second part is confusing. I know an array would be better but the little challenge I am working is asking for a hash.
Another way to add element to ruby hash store(key, value)
hash = {}
hash.store("first", 42)
hash #=> {"first"=>42}
With an array you use << to push a single element.
With a hash you are tracking not one element but two (both the key and value).
So for example:
my_key = "foo"
my_val = "bar"
my_hash = {}
my_hash[key] = val
Sure, you can do this in a loop.
I would recommend RubyMonk to learn more about this but their website is down. So I can recommend this gist which shows some examples or simply read the Hash section of any ruby tutorial.
Here are the two ways to add to a Hash
hash[str1] = "example string"
hash.merge!(str1 => "example string")
If you don't care about indexing on a key, as a Hash is intrinsically a key/value store, you probably want a Set:
require 'set'
set = Set.new
set << gets.chomp
A set is like a keyless hash, it's an un-ordered collection of things but with the side benefit that lookups for elements in the set are quick and they're also automatically uniqued, adding the same thing twice has no effect.
The alternative here is to put something in the Hash with the value as the key and any other value as a placeholder:
values = { }
values[input.gets] = true
This is like a Set but is probably less efficient to use if you don't care about values.
Ok, it isn't array so '<<' can't be work.
You should use this:
your_hash = {}
hash_key = "x"
hash_value = "y"
your_hash[:hash_key] = hash_value
It's all.

Pushing keys and values into a hash

I am parsing lines of code that look like Key: Value, and am inserting the Key and Value elements into an array. Then I'm iterating through that array and attempting to store these values into a hash. I want it to be like: "Host => "localhost" and "Content-Length" => "17".
I tried to follow this example:
def parse_headers
#headers = {}
while ! (line = next_line).empty?
header_elements = line.split(': ')
header_elements.each do |key, val|
#headers[key] = val
end
parse_header(line)
end
puts #headers
end
I think my syntax is wrong. When I print the hash, there are only keys, and all values are nil.
If anyone could help, I'd greatly appreciate it.
Did you notice that your resulting hash also contains keys which should be values? That's because header_elements is an array and you're trying to iterate it as a hash (which it isn't). In fact, you don't need to iterate it.
key, val = line.split(': ')
#headers[key] = val
The point is the same as Sergio's answer, but you don't have to use two variables for it. And for safeness, you can add an argument 2.
#headers.store(*line.split(': ', 2))

How to save an array of information coming from a hash in Ruby

I am new to ruby and don't have much experience with hashes, I have a variable named tweets and it is a hash as such:
{"statuses"=>[{"metadata"=>{"result_type"=>"recent", "iso_language_code"=>"tl"}, "lang"=>"tl"}]}
I would like to save the array of information as a separate variable in an array. How would I go about this?
Hash's have 2 very nice methods,
hash.values
hash.keys
in your case -
h = {"statuses"=>[{"metadata"=>{"result_type"=>"recent", "iso_language_code"=>"tl"}, "lang"=>"tl"}]}
p h.values
p.keys
These output arrays of each type. This might be what you want.
Also, this question will very well be closed. 1 Google search reported several Hash to Array SO questions.
Ruby Hash to array of values
Converting Ruby hashes to arrays
If you have a Hash like so:
hash = {:numbers => [1,2,3,4]}
And you need to capture the array into a new variable. You can just access the key and assign it to a new variable like so:
one_to_five = hash[:numbers]
However, note that the new variable actually holds the array that is in the hash. So altering the hash's array alters the new variable's array.
hash[:numbers] << 6
puts one_to_five #=> [1,2,3,4,5,6]
If you use dup, it will create a copy of the array so it will be two separate arrays.
one_to_five = hash[:numbers].dup
hash[:numbers] << 6
puts one_to_five #=> [1,2,3,4,5]
So, in your case:
hash = {'statuses' => [{"metadata"=>{"result_type"=>"recent", "iso_language_code"=>"tl"}, "lang"=>"tl"}]}
new_array = hash['statuses'].dup
However, it would be interesting to see what it is you are wishing to accomplish with your code, or at least get a little more context, because this may not be the best approach for your final goal. There are a great many things you can do with Arrays and Hashes (and Enumerable) and I would encourage you to read through the documentation on them.

Why does hash.keys.class return Arrays?

New to Ruby, I'm just missing something basic here. Are the keys in a Hash considered an Array unto themselves?
Yes, Hash#keys returns the hash's keys as a new array, i.e., the hash and the array returned by Hash#keys are completely independent of each other:
a = {}
b = a.keys
c = a.keys
b << :foo
a # still {}
b # [:foo]
c # still []
a[:bar] = :baz
a # {:bar => :baz}
b # still [:foo]
c # still []
From the documentation of hash.keys:
Returns a new array populated with the keys from this hash. See also Hash#values.
So the class is Array because the return value is an array.
About your question "Are the keys in a Hash considered an Array unto themselves?", they "kind" of are, hashes in Ruby are implemented as struct (st_table) which contains a list of pointers to each of its entries(st_table_entry),the st_table_entry contains the key and its value, so I guess what the keys method does is just transversing that list taking out each of the keys.
You can read this article of Ilya Grigorik where he explains much better Hashes in Ruby http://www.igvita.com/2009/02/04/ruby-19-internals-ordered-hash/
Do you think there's something paradoxical about this? Keep in mind that hashes aren't arrays in Ruby.

Most efficient way to map a specific string into a hash in Ruby

I'm new to Ruby and am working on a CLI application that parses some reports of mine. I would like to figure out the most efficient way to achieve the following with this line:
MAXCONN: 2000, MAXSSL_CONN: 500, PLAINCONN: 34, AVAILCONN: 1966, IDLECONN: 28, SSLCONN: 0, AVAILSSL: 500
I would like to map this into a hash accordingly:
{ :maxconn => 2000, :maxssl_conn => 500, :plainconn => 34, :availconn => 1966, :idleconn => 28, :sslconn => 0, :availssl => 500 }
The only way I can think to do this is to split at the comma and then again at the semi-colon and map them.
I have a sneaking suspicion there may be some Ruby magic to achieve this in a more efficient and less cumbersome way.
Any input and or tricks / tips would be appreciated as I have a feeling I'll be approaching problems like this relatively often.
We combine the technique for converting a bunch of key-value pairs into a hash
Hash[[[k1, v1], [k2, v2], ...]] #=> { k1 => v1, k2 => v2, ... }
with the regular expression method String#scan, which passes through a string and collects matches in an array, to get
Hash[reports.scan(/(\w+): (\w+)/).map { |(first, second)| [first.downcase.to_sym, second.to_i] }]
This also uses a block with Enumerable#map that interprets arrays as pairs of (first, second) elements in the argument list, extracts these into new elements, and applies conversions to them so as to tailor the resulting hash to your example's specifications (otherwise, you just get a hash of strings mapping to strings).
hash = {}
input.scan /(\w)+\:(\d+)\,/ do |key, val|
hash[key.downcase.to_sym] = val.to_i
end
Seems like the most obvious.
You could do (with some caveats) this:
hash = Hash[*eval(string).map{|k,v| [k.downcase.to_sym, v]}.flatten]
But that's really forcing a one-liner where it shouldn't be.
Of course splitting it isn't a horrible idea:
hash = input.split(",").each_with_object({}) do |str, h|
k,v = str.split(":")
h[k.downcase.to_sym] = v.to_i
end
If you are using ruby1.9, you do not even need to parse that text because ruby1.9 accepts hash literals in the form {symbol: value} when the key is a symbol. Simply, do this:
eval("{#{your_string.downcase}}")
Write the code first, then optimize it
If your code causes a bottleneck on your implementation of Ruby, the first thing you'd have to do is work out if it's the string splitting, or the hash creation, or adding your new hash to whatever array it's in (this is the step I'd be most worried about!), that's causing the bottleneck.
Write functional programming style code - it's usually cleaner, and sometimes faster
With regards to adding your new hash to the array it's in, generally you shouldn't do
array_of_hashes = []
lines.each do |line|
hash = create_hash(line)
array_of_hashes << hash
end
instead, do
array_of_hashes = lines.map do |line|
hash = create_hash(line)
hash
end
The latter is generally better performing, but regardless of that, it's cleaner code, so it's not a premature optimization.

Resources