Ruby-How to build a multivalued hash? - ruby

Here is my code snippet:
something_1.each do |i|
something_2.each do |j|
Data.each do |data|
date = data.attribute('TIME_PERIOD').text
value = data.attribute('OBS_VALUE').text
date_value_hash[date] = value
end
end
end
I want to capture all the values in a single date. date is the key of my hash and it may have multiple values for a single date. How can I accomplish that here? When I am using this line:
date_value_hash[date] = value
values are getting replaced each time the loop iterates. But, I want to accumulate all the values in my date_value_hash for each dates i.e. I want to build the values dynamically.
Currently I am getting this:
{"1990"=>"1", "1994"=>"2", "1998"=>"0"}
But, I want something like this:
{"1990"=>"1,2,3,4,5,6", "1994"=>"1,2,3,4,5,6", "1998"=>"1,2,3,4,5,6"}
Anyone have any idea how can I accomplish that?

Like this
magic = Hash.new{|h,k|h[k]=[]}
magic["1990"] << "A"
magic["1990"] << "B"
magic["1994"] << "C"
magic["1998"] << "D"
magic["1994"] << "F"
after which magic is
{"1998"=>["D"], "1994"=>["C", "F"], "1990"=>["A", "B"]}
and if you need the values as comma separated string (as indicated by your sample data), you'll just access them as
magic['1990'].join(',')
which yields
"A,B"
if later you want to pass magic around and preventing it from automagically creating keys, just wrap it as follows
hash = Hash.new.update(magic)
Hope that helps!

Another approach of building multi-valued hash in Ruby:
h = {}
(h[:key] ||= []) << "value 1"
(h[:key] ||= []) << "value 2"
puts h

Related

Combining data parsed from within the same hash in Ruby

I'm trying to combine large data sets that I've filtered out from a single hash. I've tried various things such as merge, but don't seem to be able to get the data to combine the way I'm envisioning. Here are the things I'm trying to combine:
puts '','=========GET INFO'
print_data = targetprocess.comments_with_ids #get the hash
puts print_data #show the hash for verification
puts '','=========GET IDs'
story_ids = print_data['Comments']['Comment'].map {|entry| entry['General']} #filter for story ids and story name
puts story_ids
puts '','=========GET COMMENTS'
comment_description = print_data['Comments']['Comment'].map {|words| words['Description']} #get all comments, these are in the same order as the story ids
puts comment_description
Ultimately what I would like it to look like is:
story_id 1 + comment_description 1
story_id 2 + comment_description 2
etc.
Any help would be greatly appreciated.
I ended up realizing that the hash had some other nested structures I could use. In this example I use a nested hash, then store it as an array (I ultimately need this for other work) and then output.
puts '','=========GET INFO'
print_data = targetprocess.comments_with_ids #get the hash
puts print_data #show the hash for verification
puts '=========COMPLETE', ''
#=========HASH OF USEFUL DATA
results = {}
print_data['Comments']['Comment'].each{|entry|
results[entry['Id'].chomp] = {:parent_id => entry['General']['Id'].chomp, :description => entry['Description'].chomp}}
#=========STORE HASH AS AN ARRAY
csv_array = []
results.each{|key,value|
csv_array << [key, value[:parent_id], value[:description]]
#=======FRIENDLY OUTPUT
puts "Story_Id #{value[:parent_id]}, Comment_Id #{key}, Comment #{value[:description]}"}

Simple Ruby Input Scraper

I'm completely new to ruby and wanted to ask for some help with this ruby script.
it's supposed to take in a string and find out which character occurs the most frequently. It does this using a hash, it stores all the characters in a hash and then iterates through it to find the one with greatest value. As of right now it doesn't seem to be working properly and i'm not sure why. It reads the characters in properly as far as i can tell with print statements. Any help is appreciated.
Thanks!
puts "Enter the string you want to search "
input = gets.chomp
charHash = Hash.new
input.split("").each do |i|
if charHash.has_key?(i)
puts "incrementing"
charHash[i]+=1
else
puts"storing"
charHash.store(i, 1)
end
end
goc = ""
max = 0
charHash.each { |key,value| goc = key if value > max }
puts "The character #{goc} occurs the most frequently"
There are two major issues with you code:
As commented by Holger Just, you have to use += 1 instead of ++
charHash.store(:i, 1) stores the symbol :i, you want to store i
Fixing these results in a working code (I'm using snake_case here):
char_hash = Hash.new
input.split("").each do |i|
if char_hash.has_key?(i)
char_hash[i] += 1
else
char_hash.store(i, 1)
end
end
You can omit the condition by using 0 as your default hash value and you can replace split("").each with each_char:
char_hash = Hash.new(0)
input.each_char do |i|
char_hash[i] += 1
end
Finally, you can pass the hash into the loop using Enumerator#with_object:
char_hash = input.each_char.with_object(Hash.new(0)) { |i, h| h[i] += 1 }
I might be missing something but it seems that instead of
charHash.each { |key,value| goc = key if value > max }
you need something like
charHash.each do |key,value|
if value > max then
max = value
goc = key
end
end
Notice the max = value statement. In your current implementation (i.e. without updating the max variable), every character that appears in the text at least once satisfies the condition and you end up getting the last one.

Adding increasing key to Hash

PHP allows to add values to array like this:
array[]='a' # result: arr[0]='a'
array[]='a' # result: arr[1]='a'
...
How to achieve similar result with Ruby?
UPDATED:
forgot to say, that I need to make some extra hashes inside, like
'a'=>{1=>1}...
UPDATE 2:
First update can be a little bit confusing, so there is my full source, which doesn't work. It has to make multiple records of #value hash in session[:items][0], session[:items][1]...
#value = {'id'=>id, 'quantity'=>1, "property_categories"=>params[:property_categories]}
if !session[:items].present?
session[:items] = #value
else
session[:items].push(#value)
end
UPDATE 3:
data should look like:
[0=>{'id'=>id, 'quantity'=>1, "property_categories"=>{1=>1}},
1=>{'id'=>id, 'quantity'=>1, "property_categories"=>{1=>1}}]...
this should work:
arr << 'a'
or this:
arr.push('a')
source: http://www.ruby-doc.org/core-2.1.1/Array.html
In your code, if session[:items] is not present, you are assigning #value(which is a Hash) to it. So next time it will try to push items to Hash.
If you need an Array for session[:items], this should work
if !session[:items].present?
session[:items] = [#value] # Here create an array with #value in it
else
session[:items].push(#value)
end
EDIT
I see you have updated your question. So here is my updated answer
if !session[:items].present?
session[:items] = { 0 => #value }
else
session[:items][ session[:items].keys.max + 1 ] = #value
end
Simply push to the array using <<
array = []
array << 'a' # array == ["a"]
array << 'a' # array == ["a", "a"]

Adding elements of different arrays together

I'm trying to use CSV to calculate the average of three numbers and output it to a separate file. Particularly, open one file, take the first value (name), and then calculate the average of the next three values. Do this multiple times for each person in the file.
Here is my Book1.csv
Tom,90,80,70
Adam,80,85,83
Mike,100,93,89
Dave,100,100,100
Rob,80,70,75
Nick,80,90,70
Justin,100,90,90
Jen,80,90,100
I'm trying to get it to output this:
Tom,80
Adam,83
Mike,94
Dave,100
Rob,75
Nick,80
Justin,93
Jen,90
I have each person in an array and I could get this to work with the basic "pseudo" code I have written, but it does not work.
Here is my code so far:
#!/usr/bin/ruby
require 'csv'
names=[]
grades1=[]
grades2=[]
grades3=[]
average=[]
i = 0
CSV.foreach('Book1.csv') do |students|
names << students.values_at(0)
grades1 << reader.values_at(1)
grades2 << reader.values_at(2)
grades3 << reader.values_at(3)
end
while i<10 do
average[i]= grades1[i] + grades2[i] + grades3[i]
i= i + 1
end
CSV.open('Book2.csv', 'w') do |writer|
rows.each { |record| writer << record }
end
The while loop part is the part that I am most concerned with. Any insight?
If you have an array of values that you want to sum, you can use:
sum = array.inject(:+)
If you change your data structure to:
grades = [ [], [], [] ]
...
grades[0] << reader.values_at(1)
Then you can do:
0.upto(9) do |i|
average[i] = (0..2).map{ |n| grades[n][i] }.inject(:+) / 3
end
There are a variety of ways to improve your data structures, the above being one of the least impactful to your code.
Any time you find yourself writing:
foo1 = ...
foo2 = ...
You should recognize it as code smell, and think of how you could organize your data in better collections.
Here's a rewrite of how I might do this. Notice that it works for any number of scores, not hardcoded to 3:
require 'csv'
averages = CSV.parse(DATA.read).map do |row|
name, *grades = *row
[ name, grades.map(&:to_i).inject(:+) / grades.length ]
end
puts averages.map(&:to_csv)
#=> Tom,80
#=> Adam,82
#=> Mike,94
#=> Dave,100
#=> Rob,75
#=> Nick,80
#=> Justin,93
#=> Jen,90
__END__
Tom,90,80,70
Adam,80,85,83
Mike,100,93,89
Dave,100,100,100
Rob,80,70,75
Nick,80,90,70
Justin,100,90,90
Jen,80,90,100

Implicit return values in Ruby

I am somewhat new to Ruby and although I find it to be a very intuitive language I am having some difficulty understanding how implicit return values behave.
I am working on a small program to grep Tomcat logs and generate pipe-delimited CSV files from the pertinent data. Here is a simplified example that I'm using to generate the lines from a log entry.
class LineMatcher
class << self
def match(line, regex)
output = ""
line.scan(regex).each do |matched|
output << matched.join("|") << "\n"
end
return output
end
end
end
puts LineMatcher.match("00:00:13,207 06/18 INFO stateLogger - TerminationRequest[accountId=AccountId#66679198[accountNumber=0951714636005,srNumber=20]",
/^(\d{2}:\d{2}:\d{2},\d{3}).*?(\d{2}\/\d{2}).*?\[accountNumber=(\d*?),srNumber=(\d*?)\]/)
When I run this code I get back the following, which is what is expected when explicitly returning the value of output.
00:00:13,207|06/18|0951714636005|20
However, if I change LineMatcher to the following and don't explicitly return output:
class LineMatcher
class << self
def match(line, regex)
output = ""
line.scan(regex).each do |matched|
output << matched.join("|") << "\n"
end
end
end
end
Then I get the following result:
00:00:13,207
06/18
0951714636005
20
Obviously, this is not the desired outcome. It feels like I should be able to get rid of the output variable, but it's unclear where the return value is coming from. Also, any other suggestions/improvements for readability are welcome.
Any statement in ruby returns the value of the last evaluated expression.
You need to know the implementation and the behavior of the most used method in order to exactly know how your program will act.
#each returns the collection you iterated on. That said, the following code will return the value of line.scan(regexp).
line.scan(regex).each do |matched|
output << matched.join("|") << "\n"
end
If you want to return the result of the execution, you can use map, which works as each but returns the modified collection.
class LineMatcher
class << self
def match(line, regex)
line.scan(regex).map do |matched|
matched.join("|")
end.join("\n") # remember the final join
end
end
end
There are several useful methods you can use depending on your very specific case. In this one you might want to use inject unless the number of results returned by scan is high (working on arrays then merging them is more efficient than working on a single string).
class LineMatcher
class << self
def match(line, regex)
line.scan(regex).inject("") do |output, matched|
output << matched.join("|") << "\n"
end
end
end
end
In ruby the return value of a method is the value returned by the last statement. You can opt to have an explicit return too.
In your example, the first snippet returns the string output. The second snippet however returns the value returned by the each method (which is now the last stmt), which turns out to be an array of matches.
irb(main):014:0> "StackOverflow Meta".scan(/[aeiou]\w/).each do |match|
irb(main):015:1* s << match
irb(main):016:1> end
=> ["ac", "er", "ow", "et"]
Update: However that still doesn't explain your output on a single line. I think it's a formatting error, it should print each of the matches on a different line because that's how puts prints an array. A little code can explain it better than me..
irb(main):003:0> one_to_three = (1..3).to_a
=> [1, 2, 3]
irb(main):004:0> puts one_to_three
1
2
3
=> nil
Personally I find your method with the explicit return more readable (in this case)

Resources