Making a CSV file into a hash and then grouping by criteria - ruby

In ruby, if I have a CSV file that looks like this:
make,model,color,doors
dodge,charger,black,4
ford,focus,blue,5
nissan,350z,black,2
mazda,miata,white,2
honda,civid,brown,4
corvette,stingray,red,2
ford,fiesta,blue,5
how would I be able to change this to a hash and be able to group them together by amount of doors or another parameter from the header, for example, the program asks the user for the amount of doors, if the user enters "2" then only the lines that have "2" for doors will be outputted (please comment)

I don't believe a hash table is what you want as you would have to make a new one for each attribute. Here's a way of doing it using a Car class
require "csv"
class Car
attr_accessor :make, :model, :color,:door
def initialize(make, model, color, door)
#make, #model, #color, #door = make, model, color, door
end
def to_s
"Make: #{self.make}, Model: #{self.model}, Color: #{self.color}, Door: #{door}"
end
end
cars = CSV.read("so.csv").map{|car| Car.new(car[0], car[1], car[2], car[3])}
attributeWanted = gets.chomp
value = gets.chomp
wantedCars = cars.select{|car| car.instance_variable_get("##{attributeWanted}") == value}
puts(wantedCars)
Result:
door
5
Make: ford, Model: focus, Color: blue, Door: 5
Make: ford, Model: fiesta, Color: blue, Door: 5

Related

Cannot make array unique in Ruby

I want to make ClothingCollection in ruby, one of the tasks of which is to return an array of types of clothes.
I have 17 files (name of clothing, type and for what weather it is made) and 5 types.
require_relative 'clothing'
class ClothingCollection
##types_of_clothes = []
def initialize(clothing)
#type = clothing.type_of_clothing
end
def set_type_to_catalog
unless ##types_of_clothes.include?(#type)
##types_of_clothes << #type
end
end
end
Please, what's wrong with this code?
It returns 10 types
hat
jeans
top
outerwear
shoes
hat
jeans
top
outerwear
shoes
I have also tried the .uniq method and it also does not work

Ruby reads .50 as .5

I have an Item class, and I initialized five variables. I am trying to match the output in my terminal to the value of the expected_summary. I am calling Item.summary in the following code:
class Item
attr_reader :name, :description, :manufacturer, :price, :summary
def initialize (name, manufacturer, price, description=nil, summary=nil)
#name = name
#manufacturer = manufacturer
#price = price
#description = description
if description
#summary = "Name: #{name}
Description: #{description}
Manufacturer: #{manufacturer}
Price: $#{price}"
else
#summary = "Name: #{name}
Manufacturer: #{manufacturer}
Price: $#{price}"
end
end
end
#expected_summary = %q(Name: Spy Notebook
#Manufacturer: Spys-R-Us
#Price: $10.50)
item = Item.new("Spy Notebook", "Spys-R-Us", 10.50)
puts item.summary
When I pass a number 10.50 as the price argument, it returns as 10.5. I cannot figure out why. Why does Ruby read 10.50 as 10.5? Is there a way to correct this?
The answer is in the string format operator which allows you to coerce the float into a string with 2 decimal places. This method will also work to do the rounding of 3 digit numbers, I didn't try it any further, but I'm fairly certain it will work. Here is your original code modified to showcase exactly how it would work.
class Item
attr_reader :name, :description, :manufacturer, :price, :summary
def initialize (name, manufacturer, price, description=nil, summary=nil)
#name = name
#manufacturer = manufacturer
#price = "%.2f" % price
#description = description
if description
#summary = "Name: #{name}
Description: #{description}
Manufacturer: #{manufacturer}
Price: $#{#price}"
else
#summary = "Name: #{name}
Manufacturer: #{manufacturer}
Price: $#{#price}"
end
end
def price
#price
end
end
EDIT: I didn't see #tadman's comment until after posting this, he beat me to the answer.
Maybe you can define your custom method to format the number as string with two decimals:
def two_decimals(number)
number = Float(number)
price_split = number.to_s.split('.')
price_split[1] = price_split.last + '0'*(2-price_split.last.size) if price_split.last.size < 2
price_split.join('.')
end
two_decimals(10.25) #=> "10.25"
two_decimals(10.2) #=> "10.20"
two_decimals(10) #=> "10.00"
Or something better...
The usual way of formatting numbers is:
'%.02f' % number
Where that is the printf-style notation for describing how you want something formatted. This is inherited from C and shows up in a lot of other languages:
'%f' % number # A floating-point number with default precision
'%.02f' % number # A floating-point number rounded to 2 places
Within Rails you also have helper methods like number_with_precision which can handle localization cases where the decimal separator is not a dot:
number_with_precision(number, precision: 2, locale: :fr)
# => 1,50

How to stub/mock multiple options depending on user input with Rspec 3.4

I am completely new to Rspec, and it's my first time trying to test outside of the rails framework. I am simply trying to understand how I can possibly mock behavior of my app when the implementation is pretty complex.
I want to be able to mimic the behavior of calling customize_gender inputting a choice and checking that when 1 is entered the result is 'Male', when 2 is entered the result is 'Female', etc.
I also want to be able to check if the instance variable of #gender was correctly set, which is why I added the attr_reader :gender in the first place. I have been trying a few things, but I guess I do not understand how mocks in general work to be able to find a solution. I have looked at similar questions but they do not seem to work for my scenario. Any insight is greatly appreciated!
Main file (person.rb)
class Person
attr_reader :gender
GENDER = { male: 'Male', female: 'Female', other: 'Other'}
def initialize
puts customize_gender
end
def customize_gender
display_hash_option GENDER, 'What is your gender? '
choice = gets.chomp.to_i
#gender =
case choice
when 1
GENDER[:male]
when 2
GENDER[:female]
when 3
print 'Enter your preferred gender: '
gets.chomp.downcase
else
puts 'Error: Person -> customize_gender()'
end
end
private
def display_hash_option(hash, saying = '')
print saying
hash.each_with_index { |(key, _value), index| print "#{index.next}) #{key} " }
end
end
Rspec File (spec/person_spec.rb)
require_relative "../person"
describe Person do
let(:person) { Person.new }
allow(Person).to receive(:gets).and_return(1,2,3)
person.customize_gender
expect(person.gender).to eq 'Male'
# allow(person).to receive(:customize_gender).and_return('Male')
# expect(Person).to receive(:puts).with('What is your gender?')
# allow(Person).to receive(:gets) { 1 }
# expect(person.gender).to eq 'Male'
end
Here's how you could do it, the only thing mocked here is that gets is set to '1' (remember it's a string in this case as gets input is always a string)
RSpec.describe Person do
subject { Person.new }
it 'returns male as gender when male is chosen' do
allow(subject).to receive(:gets).and_return('1')
subject.customize_gender
expect(subject.gender).to eq('Male')
end
end
For when 3 you could use the following.
RSpec.describe Person do
subject { Person.new }
it 'returns other as gender when other has been entered' do
allow(subject).to receive(:gets).and_return('3', 'other')
subject.customize_gender
expect(subject.gender).to eq('other')
end
end

How to dump a class into another class to create a save state

I have a small class which is for a character and we can assign to it from outside the class.
I need to know how I can dump all the information in that class into another that can be used to create a YAML file.
require "yaml"
module Save
filename = "data.yaml"
character = []
sex = []
race = []
stats = [Str=[], Dex=[], Con=[], Int=[], Wis=[], Cha=[]]
inventory = []
saving_throws = [fortitude=[], reflex=[], will=[]]
#Armor Class, Flat footed Armor Class, and Touch armor Class
armor_class = [ac=[], fac=[], tac=[]]
armor_worn = [head=[], eyes=[], neck=[], shoulders=[], body=[], torso=[], arms_wrists=[], hands=[], ring1=[], ring2=[], waist=[], feet=[]]
money = []
god = []
speciality_school = [] #wizard
companion = [] #also used for familirs and psicrystals
skills = []
class_race_traits = []
feats = []
languages = []
program_data = {
character: character,
sex: sex,
race: race,
stats: stats,
inventory: inventory,
saving_throws: saving_throws,
armor_class: armor_class,
armor_worn: armor_worn,
mony: money,
god: god,
speciality_school: speciality_school,
companion: companion,
skills: skills,
class_race_traits: class_race_traits,
feats: feats,
languages: languages
}
File.write filename, YAML.dump(program_data)
end
This is the code I want to use to obtain the user content from the player:
class Character
attr_reader :name, :race, :description
def initialize (name, race, description)
#name = name
#race = race
#description = description
end
end
def prompt
print "Enter Command >"
end
puts "What is your name?"
prompt; name = gets.chomp.downcase
puts "What is your race?"
prompt; race = gets.chomp.downcase
puts "What do you look like?"
prompt; desc = gets.chomp.downcase
player_one = Character.new(name, race, desc)
puts player_one
I'm stuck on how to load it back and refill the character content to make it continue where the player left off.
Meditate on this bit of fictional code:
require 'yaml'
SAVED_STATE_FILE = 'saved_state.yaml'
class User
def initialize(name=nil, address=nil)
#name = name
#address = address
end
def to_h
{
'name' => #name,
'address' => #address
}
end
def save
File.write(SAVED_STATE_FILE, self.to_h.to_yaml)
end
def reload
state = YAML.load_file(SAVED_STATE_FILE)
#name, #address = state.values
end
end
We can create a new user with some properties:
user = User.new('Popeye', '123 E. Main St.')
# => #<User:0x007fe361097058 #name="Popeye", #address="123 E. Main St.">
To write that information to a file you should probably start by using YAML, which results in a very readable output and is readable by many different languages, making the data file reusable. A hash results in a very readable output:
user.to_h
# => {"name"=>"Popeye", "address"=>"123 E. Main St."}
user.to_h.to_yaml
# => "---\nname: Popeye\naddress: 123 E. Main St.\n"
Save the YAML serialized hash:
user.save
Create a new version of the user without any state:
user = User.new
# => #<User:0x007fe361094a88 #name=nil, #address=nil>
Load the saved information from the file back into the blank object:
user.reload
Which results in:
user
# => #<User:0x007fe361094a88 #name="Popeye", #address="123 E. Main St.">
That will give you enough to work from.
Your current code isn't going to work well though; I'd recommend reading some tutorials about Ruby classes and modules, as a Module isn't what you want, at least for your initial code.

objected oriented and parsing csv

i have to parse csv file which has customers and product they have ordered . customers can repeat for different product . i have to get all the unique customers and products they have ordered. Then print out each customer and there product . i have been asked to do in a object oriented way so
1) should i create a customer objects and have a product as there attribute
2) just write a program using foreach and loop through and store customer and product in a hash and print it out .
what throws me off is i have been asked to do it in a object oriented way. if do it by creating objects how can i store a custom object in memory ? so that if i come across a customer second time i have to add the product and at the end i have to loop through all the objects and print it out . sorry i have bad English thanks reading a long question and for the help.
How can you store a custom object in memory? By creating the object and keeping it in a list, hash, or whatever seems appropriate. (Probably a hash, with the key being whatever unique value you have in your CSV, and the value would be a collection of products.)
Being asked to do it in "an object-oriented way" is a little arbitrary, though.
If you are using FasterCSV or Ruby 1.9 you can extend the parser allowing you to map each CSV row to a custom object.
# http://ruby-doc.org/stdlib-1.9.2/libdoc/csv/rdoc/CSV.html#method-c-load
# http://ruby-doc.org/stdlib-1.9.2/libdoc/csv/rdoc/CSV.html#method-c-dump
# https://github.com/JEG2/faster_csv/blob/master/test/tc_serialization.rb
require 'csv'
class Person
attr_accessor :id, :name, :email
def self.csv_load(meta, headers, row)
person = Person.new
headers.each.with_index { |h,i|
person.send "#{h}=", row[i]
}
person
end
def self.parse(csv)
meta = "class,#{self.to_s}\n"
CSV.load("#{meta}#{csv}")
end
def dump
self.class.dump([self])
end
def self.dump(people, io='', options={})
CSV.dump(people, io, options).strip
end
def self.csv_meta
[]
end
def csv_headers
%w(id name email)
end
def csv_dump(headers)
headers.map { |h| self.instance_variable_get "##{h}" }
end
end
CSV_DUMP = <<-CSV
class,Person
id=,name=,email=
1,"First Dude",dude#company.com
2,"Second Dude",2nddude#company.com
3,"Third Dude",3rddude#company.com
CSV
CSV_INPUT = <<-CSV
id,name,email
1,"First Dude",dude#company.com
2,"Second Dude",2nddude#company.com
3,"Third Dude",3rddude#company.com
CSV
CSV_DUMP2 = <<-CSV
class,Person
#{CSV_INPUT}
CSV
people = Person.parse(CSV_INPUT)
puts people.inspect
dumped = Person.dump(people)
puts dumped
puts "----"
puts Person.parse(dumped).inspect

Resources