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))
Related
I have a CSV file where one column is a primary key. When I do this:
CSV.read(ARGV[0], headers: true).group_by {|r| r['myKey']}
I get a hash table from key to a list of rows, where the list is always length 1.
Is there a version of group_by which asserts that there's only a single value per key, and creates a hash from key to that single value?
Failing that, is there something like .first which asserts that there's exactly one element in the array/enumerable? I like my scripts to fail when my assumptions are wrong, rather than silently return the wrong thing.
If you use Rails you can use index_by method.
If you know the values r['myKey'] are unique, there's no point in using group_by. As I understand the question, you could do this:
rows = CSV.read(ARGV[0], headers: true)
Hash[rows.map { |r| r['myKey'] }.zip(rows)]
In Ruby 2.0+ the second row could be written:
rows.map { |r| r['myKey'] }.zip(rows).to_h
No. I don't believe there is. But you can solve your problem with each_with_object like so:
CSV.
read(ARGV[0], headers: true).
each_with_object({}) do |r, hash|
key = r['myKey']
value = r
hash[key] = value
end
It's a shame Ruby doesn't have this. Here's what I decided to go on, based on Humza's answer:
module Enumerable
def group_by_uniq
each_with_object({}) do |value, hash|
key = yield value
raise "Multiple values for key \"{key}\"!" unless ! hash.key?(key)
hash[key] = value
end
end
end
If you use your code in you first example you can run this code to check that all hashes are of length 1:
raise 'multiple entries per key!' unless my_hash.values.any?{|val| val.size!=1}
IF you can get the keys into an array you can check that they do not iclude duplicates by:
raise 'multiple entries per key!' unless my_keys.uniq.size == my_keys.size
I have a set of word strings which I am turning into a hash, grouped by the size of the string. I am doing this by:
hash = set.group_by(&:size)
resulting in
hash = {5=>[apple, andys, throw, balls], 7=>[bananas, oranges]}
I want to further group the hash values by first letter, so the the end results looks like:
hash = {5=>{a=>[apple, andys],b=>[balls],t=>[throw]}, 7=>{b=>[bananas], o=>[oranges]}}
I tried putting
hash.each_value do | value |
value = value.group_by(&:chr)
end
after the first group_by but that only seems to return the original hash. I am admittedly a ruby beginner so I'm not sure if I could do this in one fell swoop, or exactly how (&:size) notation works, if I were asked to write it out. Thoughts?
To update your hash you need to do like this
hash.each do |key, value|
hash[key] = value.group_by(&:chr)
end
I'd keep the whole computation functional:
>> Hash[set.group_by(&:size).map { |k, vs| [k, vs.group_by(&:chr)] }]
=> {5=>{"a"=>["apple", "andys"], "t"=>["throw"], "b"=>["balls"]},
7=>{"b"=>["bananas"], "o"=>["oranges"]}}
Hi i am little struggle to make this hash and sort it by created of and key , value pair.
Here is my code
hash_answers = {}
unless answers.blank?
answers.each_with_index do |ans ,index|
voted_up_users = ans.votes_up_by_all_users(ans)
voted_down_users = ans.votes_down_by_all_users(ans)
hash_answers[ans.id] = voted_up_users.count -voted_down_users.count #line one
hash_answers[index+1] = ans.created_at # line 2
end
end
if i have line 1 only in code not line 2 then this below code work fine for me
#answers = hash_answers.sort_by { |key, value| value }.reverse
but i also want to sort it by craeted_at
How i can achive this or make hash in another way
Any help will be most appreciated
Thanks
answers.sort_by do |ans|
[ans.net_votes, ans.created_at]
end
Then in your Answers class
def net_votes
votes_up_by_all_users - votes_down_by_all_users
end
You shouldn't have to pass an object to itself as a variable as in ans.votes_up_by_all_users(ans). Objects always know about themselves.
Usually you can sort on a number of things by creating an array of these things and use that as your sort key:
#answers = hash_answers.sort_by { |k, v| [ v[:created_at], v[:count] }
This is dependent on having a sortable structure to start with. You're jamming two entirely different things into the same hash. A better approach might be:
hash_answers[ans.id] = {
:id => ans.id,
:count => voted_up_users.count -voted_down_users.count,
:created_at => ans.created_at
}
You can adjust the order of the elements in the array to sort in the correct order.
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)}]
I'm trying to read some JSON data from the Tumblr API.
I'm using the Hashie gem to read the values as object properties. This should make reading easier/cleaner.
it turns something like this:
data['post']['title']
into this:
data.post.title
Unfortunately there are some keys showing up with a '-' as divider between like this:
regular-title: Mijn eerste post
format: html
regular-body: <p>post</p>
therefore i cannot use post.regular-title. Is there a way to replace all the minus(-) symbols into underscores(_)?
This will do it:
def convert_object(data)
case data
when Hash
data.inject({}) do |h,(k,v)|
h[(k.respond_to?(:tr) ? k.tr('-', '_') : k)] = convert_object(v)
h
end
when Array
data.map { |i| convert_object(i) }
else
data
end
end
You can use it like this:
convert_object(JSON.parse('{"something-here":"value","otherkey":{"other-key":"value-value"}}'))
Karaszi Istvan helped me a lot with the solution. I added the check for an array in the hash. This way hashes in arrays in the hash will get underscored too.
def convert_hash(hash)
case hash
when Hash
hash.inject({}) do |h,(k,v)|
h[k.tr('-', '_')] = convert_hash(v)
h
end
when Array
array = hash
number = 0
array.each do
array[number] = convert_hash(array[number])
number += 1
end
array
else
hash
end
end
I don't know why i added the 'number' as iterator. Somehow hash.each didn't work.