local variable vs instance variable Ruby initialize - ruby

I have a class in Ruby where I pass in a Hash of commodity prices. They are in the form
{"date (string)" => price (float), etc, etc}
and in the initialise method I convert the dates to Dates like so:
#data = change_key_format(dates)
But I notice that that method seems to change the original argument as well. Why is that? Here is the code:
def initialize(commodity_name, data)
puts "creating ...#{commodity_name}"
#commodity_name = commodity_name
#data = change_hash_keys_to_dates(data)
#dates = array_of_hash_keys(data)
puts data ######## UNCHANGED
#data = fix_bloomberg_dates(#data, #dates)
puts data ######## CHANGED -------------------- WHY???
#get_price_data
end
def fix_bloomberg_dates(data, dates)
#Fixes the bad date from bloomberg
data.clone.each do |date, price|
#Looks for obvious wrong date
if date < Date.strptime("1900-01-01")
puts dates[1].class
date_gap = (dates[1] - dates[2]).to_i
last_date_day = dates[1].strftime("%a %d %b")
last_date_day = last_date_day.split(" ")
last_date_day = last_date_day[0].downcase
#Correct the data for either weekly or daily prices
#Provided there are no weekend prices
if date_gap == 7 && last_date_day == "fri"
new_date = dates[1] + 7
data[new_date] = data.delete(date)
elsif date_gap == 1 && last_date_day == "thu"
new_date = dates[1] + 4
data[new_date] = data.delete(date)
else
new_date = dates[1] + 1
data[new_date] = data.delete(date)
end
end
end
return data
end
def change_hash_keys_to_dates(hash)
hash.clone.each do |k,v|
date = Date.strptime(k, "%Y-%m-%d")
#Transforms the keys from strings to dates format
hash[date] = hash.delete(k)
end
return hash
end
def array_of_hash_keys(hash)
keys = hash.map do |date, price|
date
end
return keys
end

Because of these lines:
data[new_date] = data.delete(date)
You're modifying the original data object. If you don't want to do this, create a copy of the object:
data2 = data.clone
and then replace all other references to data with data2 in your method (including return data2).

Related

ruby - access array with hash key

I am struggling to understand how I can access an array with a hash key. In my code, I create a hash with keys and values. Now, I want to set the values in a Car class. Whenever I try to instantiate the Car, the argument expects Integer and not a String.
I am getting the following error: TypeError (no implicit conversion of String into Integer)
Here is my code:
class Car_maker
attr_accessor :car_maker
def initialize(car_maker)
#car_maker = car_maker
end
end
class Car_model < Car_maker
attr_accessor :km, :type, :transmission, :stock, :drivetrain, :status,
:fuel, :car_maker, :model, :year, :trim, :features
#total number of instances & array with car objects
##totalCars = 0
##catalogue = []
def initialize(km, type, transmission, stock, drivetrain, status, fuel, car_maker, model, year, trim, features)
super(car_maker)
#km = km
#type = type
#transmission = transmission
#stock = stock
#drivetrain = drivetrain
#status = status
#fuel = fuel
#model = model
#year = year
#trim = trim
#features = features
##totalCars += 1
end
def self.convertListings2Catalogue(line)
#Initialise arrays and use them to compare
type = ["Sedan", "coupe", "hatchback", "station", "SUV"]
transmission = ["auto", "manual", "steptronic"]
drivetrain = ["FWD", "RWD", "AWD"]
status = ["new", "used"]
car_maker = ["honda", "toyota", "mercedes", "bmw", "lexus"]
hash = Hash.new
#In this part, we hash the set of features using regex
copyOfLine = line
regex = Regexp.new(/{(.*?)}/)
match_array = copyOfLine.scan(regex)
match_array.each do |line|
hash["features"] = line
end
#Now, we split every comma and start matching fields
newStr = line[0...line.index('{')] + line[line.index('}')+1...line.length]
arrayOfElements = newStr.split(',')
arrayOfElements.each do |value|
if value.include?("km") and !value.include?("/")
hash["km"] = value
elsif type.include?(value)
hash["type"] = value
elsif transmission.include?(value.downcase)
hash["transmission"] = value
elsif value.include?("/") and value.include?("km")
hash["fuel economy"] = value
elsif drivetrain.include?(value)
hash["drivetrain"] = value
elsif status.include?(value.downcase)
hash["status"] = value
elsif /(?=.*[a-zA-Z])(?=.*[0-9])/.match(value) and !value.include?("km")
hash["stock"] = value
elsif car_maker.include?(value.downcase)
hash["carmaker"] = value
elsif /^\d{4}$/.match(value)
hash["year"] = value
elsif value.length == 2
hash["trim"] = value
else
if value.length > 2
hash["model"] = value
end
end
end
end
end
textFile = File.open('cars.txt', 'r')
textFile.each_line{|line|
if line.length > 2
result = Car_model.convertListings2Catalogue(line)
puts "Hash: #{result}"
carObj = Car_model.new(result["km"], result["type"], result["transmission"], result["stock"], result["drivetrain"],
result["status"], result["fuel"], result["carmaker"], result["model"], result["year"], result["trim"], result["features"])
###catalogue.push (carObj)
end
}
This line
result = Car_model.convertListings2Catalogue(line)
Doesn't return the hash object. It returns arrayOfElements since that's what the each method actually returns and the each method is the last method executed in the method (although there are hash assignments within it, it's only the last value that's returned unless you use an explicit return statement.
Just use the variable hash in the last line of the convertListing2Catalog method
if value.length > 2
hash["model"] = value
end
end
end
hash # < this is the last line of the method so it's the value that will be returned
end
end
If you think about it, there were several variables created in the method. There's no reason to expect that the contents of any specific variable such as hash would be returned, and ruby methods by default return the last executed command.

Calculate elements in CSV file

I am new to Ruby and now I have an issue while I try to calculate some elements.
I've got 6 CSV files with the same headers and the question is how to find the total amount of payments for each payed month.
01-test.csv
Payment date,Payable month,House,Apartment,Amount of payment
2014-09-14,2014-08,Panel,84,5839.77
2014-09-14,2014-08,Brick,118,4251.63
2014-09-14,2014-08,Brick,97,471.5
2014-09-14,2014-08,Panel,53,236.22
2014-09-14,2014-08,Panel,83,4220.77
.......
02-test.csv
Payment date,Payable month,House,Apartment,Amount of payment
2014-10-01,2014-08,Brick,34,1522.59
2014-10-01,2014-08,Brick,117,1285.57
2014-10-01,2014-08,Brick,136,1925.97
2014-10-01,2014-08,Brick,24,1032.95
2014-10-01,2014-08,Brick,113,957.01
.......
Here is my code:
def create_month_array(payments)
months = []
months = payments.uniq { |a| a[:payed_for]
months
end
def payed_for_each_month(payments, months)
sums = Array.new(months.length){|a| a = 0}
months.each{|a|
if(a[:payed_for] == payments.each{|x| x[:payed_for]})
.....
end
}
p sum
sum.round(2)
end
Thanks for any hints.
Suppose the data were read from files into strings.
str1 =<<_
2014-09-14,2014-08,Panel,84,5839.77
2014-09-14,2014-08,Brick,118,4251.63
2014-09-14,2014-09,Brick,97,471.5
2014-09-14,2014-10,Panel,53,236.22
2014-09-14,2014-10,Panel,83,4220.77
_
str2 =<<_
2014-10-01,2014-08,Brick,34,1522.59
2014-10-01,2014-09,Brick,117,1285.57
2014-10-01,2014-09,Brick,136,1925.97
2014-10-01,2014-10,Brick,24,1032.95
2014-10-01,2014-11,Brick,113,957.01
_
We can then combine the strings into a single string, convert it an array of lines and then use a counting hash to aggregate values for each payable month, which I assume to be the values of the second field. See Hash::new, specifically when new is assigned an argument equal to the default value (here 0).
(str1 + str2).lines.each_with_object(Hash.new(0)) do |line,h|
_, payable_month, _, _, amount = line.split(',')
h[payable_month] += amount.to_f
end
#=> {"2014-08"=>11613.990000000002, (5839.77 + 4251.63 + 1522.59)
# "2014-09"=>3683.04, ( 471.5 + 1285.57 + 1925.97)
# "2014-10"=>5489.9400000000005, ( 236.22 + 4220.77 + 1032.95)
# "2014-11"=>957.01} ( 957.01)
If a hash h is defined
h = Hash.new(0)
Ruby expands h[payable_month] += amount.to_f to
h[payable_month] = h[payable_month] + amount.to_f
If h has no key payable_month, h[payable_month] on the right of the equality sign returns the default value. Hence,
h[payable_month] = 0 + amount.to_f
#=> amount.to_f
Note we could have alternatively written
(str1.lines + str2.lines).each_with_object(Hash.new(0))...
or we could have read each file line-by-line and written all those lines to one file.
To combine all CSV data across multiple files use the following:
csv_files = ["01-test.csv", "02-test.csv", "03-test.csv", "04-test.csv", "05-test.csv", "06-test.csv"]
csv_data = CSV.generate(headers: :first_row) do |csv|
csv << CSV.open(csv_files.first).readline
csv_files.each do |csv_file|
CSV.read(csv_file)[1..-1].each { |row| csv << row }
end
end
To then to the calculate the sum of each "Payable month" (or "Payment date",
it was not clear which was the payed month), you do the following
Interpret the data, using Ruby's CSV library
data = CSV.parse(csv_data, headers: true)
Group the data by the payed month
month_array = data.group_by { |row| row["Payable month"] }
# month_array = data.group_by { |row| row["Payment date"][0..6] }
Chose either line and comment out the other
For each month get the sum/reduce of all the "Amount of payment" into a
total for that month within our collection of totals
payed_for_each_month = month_array.each_with_object({}) do |(month, rows), totals|
totals[month] = rows.reduce(0.0) { |sum, row| sum + row["Amount of payment"].to_f }
end
This produces the final result with the presented data
payed_for_each_month
# => {"2014-08"=>21743.98}
If "Payment date" month was used instead the totals would produce the following:
month_array = data.group_by { |row| row["Payment date"][0..6] }
# ...
payed_for_each_month
# => {"2014-09"=>15019.890000000001,
# "2014-10"=>6724.09}
All the code together:
data = CSV.parse(csv_data, headers: true)
month_array = data.group_by { |row| row["Payable month"] }
# month_array = data.group_by { |row| row["Payment date"][0..6] }
payed_for_each_month = month_array.each_with_object({}) do |(month, rows), totals|
totals[month] = rows.reduce(0.0) { |sum, row| sum + row["Amount of payment"].to_f }
end
payed_for_each_month
# => {"2014-08"=>21743.98}
References:
group_by
reduce
each_with_object

Modifying reference to hash value in Ruby

I'm new to Ruby and I have a JSON data set that I am de-identifying using stympy's Faker. I would prefer to change the values in the Hash by reference.
I've tried changing the assignments eg. key['v] = namea[1] to data['cachedBook']['rows'][key][value] = namea[1] but I get a no implicit conversion of Array into String error. Which makes sense since each is an array in itself, but I'm unsure as to how proceed on this.
A single row e.g. data['cachedBook']['rows'] looks like this:
[{"v":"Sijpkes_PreviewUser","c":"LN","uid":"9######","iuid":"3####7","avail":true,"sortval":"Sijpkes_PreviewUser"},
{"v":"Paul","c":"FN","sortval":"Paul"},
{"v":"#####_previewuser","c":"UN"},
{"v":"","c":"SI"},{"v":"30 June 2016","c":"LA","sortval":1467261918000},
{"v":"Available","c":"AV"},[],[],[],[],[],[],
{"v":"-","tv":"","numAtt":"0","c":"374595"},[],[],
{"v":"-","tv":"","numAtt":"0","c":"374596"},[],[],[],
{"v":0,"tv":"0.0","mp":840,"or":"y","c":"362275"},
{"v":0,"tv":"0.0","mp":99.99999,"or":"y","c":"389721"}]
The key and value are interpreted as the first two entries.
Sensitive data has been removed with ####s.
Ruby code:
data['cachedBook']['rows'].each do |key, value|
fullname = Faker::Name.name
namea = fullname.split(' ')
str = "OLD: " + String(key['v']) + " " + String(value['v']) +"\n";
puts str
if ["Ms.", "Mr.", "Dr.", "Miss", "Mrs."].any? { |needle| fullname.include? needle }
key['v'] = namea[2]
value['v'] = namea[1]
value['sortval'] = namea[1]
else
key['v'] = namea[1]
value['v'] = namea[0]
value['sortval'] = namea[1]
end
str = "\nNEW: \nFullname: "+String(fullname)+"\nConverted surname: "+ String(key['v']) + "\n\t firstname: " + String(value['v'])
puts str
end
puts data
OK, this has been an excellent learning exercise!
The problem I was having was in two parts:
the JSON output from JSON.parse was a Hash, but the Hash was storing Arrays, so my code was breaking. Looking at the sample data rows above, it includes some empty arrays: ... [],[],[] ....
I misunderstood how each was working with a Hash, I assumed key, value (similar to jquery each) but the key, value in the original each statement actually evaluated to the first two array elements.
So here is my amended code:
data['cachedBook']['rows'].map! { |row|
fullname = Faker::Name.name
namea = fullname.split(' ')
row.each { |val|
if val.class == Hash
newval = val.clone
if ["Ms.", "Mr.", "Dr.", "Miss", "Mrs."].any? { |needle| fullname.include? needle }
if val.key?("c") && val["c"] == "LN"
newval["v"] = namea[1]
newval["sortval"] = namea[1]
end
if val.key?("c") && val["c"] == "FN"
newval["v"] = namea[2]
newval["sortval"] = namea[2]
end
else
if val.key?("c") && val["c"] == "LN"
newval["v"] = namea[0]
newval["sortval"] = namea[0]
end
if val.key?("c") && val["c"] == "FN"
newval["v"] = namea[1]
newval["sortval"] = namea[1]
end
end
val.merge!(newval)
end
}
}

Ruby Array Variable Reference Lost During Loop

I am writing a parsing routine in Ruby 2.1 for a spreadsheet. The code works properly through the first array of pricing data. Unfortunately, on the fifth loop through datatable, while processing the second set of pricing data, the variable termtable is not set, even though #tmptermtables is modified by the shift method in this statement on line 72: termtable = #tmptermtables.shift if termtable.empty? This is possibly a scope problem and I am hoping someone can explain to me why the reference is lost.
Below is a copy of the code. Thank you in advance for lending me your brain.
def sp_parser()
begin
pricing_date = Date.new(2014,2,7)
tz = DateTime.parse(Time.now.to_s).strftime('%z')
expires = DateTime.new(pricing_date.year,pricing_date.mon,pricing_date.mday,17,00,00,tz)
datatable = Array.new
datatable << ["Zone","Business - Low Load Factor",nil,nil,nil,"Business - Medium Load Factor",nil,nil,nil,"Business - High Load Factor",nil,nil,nil]
datatable << [nil,6,9,12,15,6,9,12,15,6,9,12,15]
datatable << [nil,"Daily Pricing",nil,nil,nil,"Daily Pricing",nil,nil,nil,"Daily Pricing",nil,nil,nil]
datatable << ["COAST",6.41,6.55,6.19,6.01,6.07,6.18,5.88,5.74,5.63,5.71,5.48,5.37]
datatable << ["NORTH",6.58,6.74,6.35,6.15,6.02,6.13,5.85,5.68,5.61,5.68,5.47,5.33]
datatable << [nil,3/1/2014,nil,nil,nil,3/1/2014,nil,nil,nil,3/1/2014,nil,nil,nil]
datatable << ["COAST",7.08,6.53,6.20,6.00,6.63,6.17,5.89,5.73,6.06,5.69,5.49,5.36]
datatable << ["NORTH",7.34,6.72,6.36,6.13,6.60,6.10,5.86,5.66,6.06,5.65,5.48,5.31]
loadprofiles = Array.new
termtables = Array.new
pvalue = 0
load_factor_found = false
daily_pricing_found = false
dataset = []
datatable.each_index {|row|
record = datatable[row]
termtable = Array.new
#tmptermtables = Array.new(termtables)
#tmploadprofiles = Array.new(loadprofiles)
record.each_index {|col|
val = record[col]
## Build the load profile table
loadprofiles << "LOW" if val.to_s.downcase.match(/ low/)
loadprofiles << "MEDIUM" if val.to_s.downcase.match(/medium/)
loadprofiles << "HIGH" if val.to_s.downcase.match(/high/)
load_factor_found = true if val.to_s.downcase.match(/load factor/)
daily_pricing_found = true if val.to_s.downcase.match(/daily pricing/)
## Build the term tables for each load profile
if load_factor_found and !daily_pricing_found
isinteger = val.is_a? Integer
if isinteger
cvalue = val
if cvalue > pvalue
termtable << cvalue
pvalue = cvalue
termtables << termtable if col == record.length - 1
else
unless termtable.empty?
termtables << termtable
termtable = []
termtable << cvalue
pvalue = cvalue
end
end
else
cvalue = 0
end
end
if daily_pricing_found
#start_date = pricing_date if val.to_s.downcase.match(/daily pricing/)
#start_date = val if val.is_a? Date
#zone = "CenterPoint" if val.to_s.downcase.match(/coast/)
#zone = "Oncor" if val.to_s.downcase.match(/north/)
if val.is_a? Float
#load = #tmploadprofiles.shift if termtable.empty?
# Here is where it breaks
termtable = #tmptermtables.shift if termtable.empty?
term = termtable.shift unless termtable.empty?
price = (val/100).round(4)
r = {
:loaded => Time.now,
:start => #start_date,
:load => #load,
:term => term,
:zone => #zone,
:price => price,
:expiration => expires,
:product => "Fixed"
}
dataset << r
end
end
}
}
return dataset
rescue => err
puts "\n" + DateTime.parse(Time.now.to_s).strftime("%Y-%m-%d %r") + " Exception: #{__callee__} in #{__FILE__} generated an error: #{err}\n"
err
end
end
x = sp_parser()

Ruby output formats differently on dynamic v. static input

FYI: Fist time I've written Ruby.
We have to write an Address Book program. I have many parts working at this point but have run into something I don't get.
I add contacts to the address book statically before the program runs. Then I try to have a user add a contact dynamically. When I print this address book the contacts added statically are printed according to formatting I expect, but the ones added dynamically are all over the place.
I've added all my code here. It's sort of a big chunk but not too bad...
# Address class
class Address
attr_accessor :street, :city, :state, :zip
# default constructor
def initialize(*args)
if (args.size == 0 )
#street = #city = #state = #zip = ""
elsif (args.size == 4)
#street = args[0]
#city = args[1]
#state = args[2]
#zip = args[3]
else
puts('Constructor takes 0 or 4 arguments. No address information has been set.')
end
end
# string representation of address
def to_s
" " + #street + "\n" + \
" " + #city + " " + #state + ", " + #zip
end
end
# Person class which holds full name, phone, and
# address as an object
class Person
attr_accessor :fname, :lname, :phone, :address
def initialize(*args)
if (args.size == 0 )
#fname = #lname = #phone = ""
#address = Address.new
elsif (args.size == 4)
#fname = args[0]
#lname = args[1]
#phone = args[2]
#address = args[3]
else
puts('Incorrect number of arguments.')
end
end
# returns full name
def full_name
#lname + ", " + #fname
end
def to_s
" " + full_name + "\n" + \
#address.to_s + "\n" + \
" " + #phone
end
end
# AddressBook class to hold addresses
class AddressBook
def initialize
# empty array
#persons = []
end
# adds a person to the address book
def add(person)
#persons += [person]
#persons = #persons.sort{|a,b| by_name(a,b)}
end
def by_name(a,b)
if a.lname == b.lname
a.fname <=> b.fname
else
a.lname <=> b.lname
end
end
# removes a person from the address book
def remove(person)
# Use Array#delete
#persons.delete(person)
end
def to_s
add_book = ""
for p in #persons do
add_book += p.to_s + "\n\n"
end
return add_book
end
end
#
# Here I add three contacts in two different ways
#
#
# FIRST ONE
#
sandy_addr = Address.new
sandy_addr.street = "324 Campus Dr."
sandy_addr.city = "College Park"
sandy_addr.state = "OH"
sandy_addr.zip = "55555"
sandy = Person.new
sandy.fname = "Sandy"
sandy.lname = "Koh"
sandy.phone = "651-442-5710"
sandy.address = sandy_addr
#
# SECOND ONE
#
bill_addr = Address.new('536 Green Rd.', "Saint Paul", "MN", "56545")
bill = Person.new('William', 'Perry', '675-778-6754', bill_addr)
#
# THIRD ONE
#
angela_addr = Address.new('3390 Crookston Rd.', "Miami", "FL", "78654")
angela = Person.new('Angela', 'Anderson', '345-748-1754', angela_addr)
# Contacts added to the Address Book
#addressBook = AddressBook.new
#addressBook.add(sandy)
#addressBook.add(angela)
#addressBook.add(bill)
# Main method loop that runs the program
# Allows you to enter a contact, print a list of
# contacts and exit the program
def loop(addBook)
selection = 0
until(selection == 5)
puts("Wlecome to the Address Book\n")
puts("1. Add a Contact\n")
puts("2. Delete a Contact\n")
puts("3. Retrieve a Contact\n")
puts("4. Print all Contacts\n")
puts("5. Exit Address Book\n")
selection = gets().to_i
if(selection == 1)
addContact(addBook)
elsif(selection == 2)
#delete_contact
elsif(selection == 3)
#retrieve_contact
elsif(selection == 4)
puts(addBook)
end
end
end
def addContact(addBook)
print("Enter First Name: ")
fname = gets()
print("Enter Last Name: ")
lname = gets()
print("Enter Phone Number: ")
phone = gets()
print("Enter Street Address: ")
street = gets()
print("Enter City: ")
city = gets()
print("Enter State: ")
state = gets()
print("Enter Zip Code ")
zip = gets()1
newAddress = Address.new(street, city, state, zip)
newPerson = Person.new(fname, lname, phone, newAddress)
addBook.add(newPerson)
out = %q/#{fname} #{lname} has been added to the Address Book./
puts(out)
end
loop(#addressBook)
The gets method includes the newline so your fname in addContact will be, for example, "Bob\n" rather than the "Bob" that you're expecting. Have a look at chomp.
You problem is probably caused because gets() returns the entire line including the newline character at the end. Try replacing every gets() with gets.chomp.

Resources