How to compare two text files with value in ruby - ruby

I have two text file, and they have one same field(pet). I want to combine two file and output a new file contain three fields(owner pet buyer). My code is mainly depends on if both file have the same pet(name and number of pets) than I will add the name of buyer to the field of buyer in the new file. It should print out only if the two file have same filed of pet.
But my code did not work as I want, need some help, thanks.
input_file_1 (.txt)
owner pet
Michael dog, cat
John pig, rabbit
Marry dog, cat
input_file_2 (.txt)
buyer pet
Sean cat, dog
Mark cat, dog
Joy dog, mouse
Tina cat, dog
I want the result to look like this:
owner pet buyer
Michael cat, dog Sean, Mark, Tina
Mary cat, dog Sean, Mark, Tina
My code looks like this:
input_file_1 = ARGV[0]
input_file_2 = ARGV[1]
hash_1 = {}
File.readlines(input_file_1, "\n").each do |line|
owner, pet = line.chomp.split("\t")
hash_1[owner] = pet
end
hash_2 = {}
File.readlines(input_file_2, "\n").each do |line|
buyer, pet = line.chomp.split("\t")
hash_2[buyer] = pet
end
hash_1.each do |key, value|
if hash_2.has_value? value
puts "#{key}\t#{value}\t#{hash_2[key]}"
end
end

I would suggest you use the pet as key:
input_file_1 = ARGV[0]
input_file_2 = ARGV[1]
hash_1 = Hash.new([])
File.readlines(input_file_1, "\n").each do |line|
owner, pet = line.chomp.split("\t")
hash_1[pet] += [owner]
end
hash_2 = Hash.new([])
File.readlines(input_file_2, "\n").each do |line|
buyer, pet = line.chomp.split("\t")
hash_2[pet] += [buyer]
end
hash_1.each do |pet, owners|
if hash_2.include? pet
owners.each do |owner|
puts "#{owner}\t#{pet}\t#{hash_2[pet].join(", ")}"
end
end
end

Related

formatting an entire line in ruby given multiple arguments

For my program, I have a list of things that's sort of like a spreadsheet that I'm trying to print to the console. I want it to look something like
garage 1
car: jaguar
price: $134,000
car: mercedes
price: $234,000
garage 2
car: jaguar
price: $134,000
garage is a hash with each key being garage number and cars in a specific garage being pushed to each key, eg garage[0] = [car1, car2]
Class car holds the information printed, and has these definitions
class Car
attr_accessor :car_name, :car_price
def initialization(name, price)
#car_name = name
#car_price = price
end
def name
#car_name
end
def price
#car_price
end
end
I've tried to implement such
garage.each do |x|
print "garage "
print garage.index(x) + 1
puts " "
x.each do |y|
printf("%10s", "Car: ")
puts y.name.rjust(16)
printf("%10s", "Price: $")
puts y.price.to_s.rjust(16)
puts " "
end
end
But I'm not getting the desired output. Depending on the car name, the spaces in between will be too far and the output won't be aligned.
I'm wondering if there's a method where I can format an entire line output? Basically like chaining together strings and other variables such as integers and floats and setting their distances correctly so that I can customize each line that's output to console.
First create the Car class:
class Car
attr_accessor :car_name, :car_price
def initialize(name, price)
#car_name = name
#car_price = price
end
end
Note that the method is initialize, not initialization, and that the class method attr_accessor creates both getter and setter methods for each of the two instance variables, so there is no need for the explicit getter methods.
Now let's create a couple of instances of this class.
jag = Car.new('jaguar', 134000)
#=> #<Car:0x00007f839690d2d0 #car_name="jaguar", #car_price=134000>
mb = Car.new('mercedes', 234000)
#=> #<Car:0x00007f83990b1d90 #car_name="mercedes", #car_price=234000>
Suppose the hash garage is as follows.
garage = { 1=>[jag, mb], 2=>[jag] }
#=> {1=>[#<Car:0x00007f839690d2d0 #car_name="jaguar", #car_price=134000>,
#=> #<Car:0x00007f83990b1d90 #car_name="mercedes", #car_price=234000>],
# 2=>[#<Car:0x00007f839690d2d0 #car_name="jaguar", #car_price=134000>]}
Firstly, we will need a way to display car prices in the correct format, starting with a dollar sign and with thousands separated by commas. One way to do that is as follows.
def convert_price_to_str(car_price)
s = ''
loop do
car_price, three = car_price.divmod(1000)
break "$#{three}#{s}" if car_price.zero?
s = (",%03d" % three) + s
end
end
For example,
convert_price_to_str 34 #=> "$34"
convert_price_to_str 3042 #=> "$3,042"
convert_price_to_str 49621 #=> "$49,621"
convert_price_to_str 1324534 #=> "$1,324,534"
See Integer#divmod, a very handy (and underutilized) method.
To display the values in the desired format it is necessary to determine the length of the longest car name or formatted price.
longest = garage.values.flatten.map do |car|
[car.car_name.size, convert_price_to_str(car.car_price).size].max
end.max
#=> 8
Now let's write another helper method to display car names and prices.
def print_name_and_price(name, price, longest)
puts " car: #{name.rjust(longest)}"
puts " price: #{convert_price_to_str(price).rjust(longest)}"
puts
end
For example,
print_name_and_price('jaguar', 134000, longest)
displays the three lines (the last being empty):
car: jaguar
price: $134,000
See String#rjust.
We may now put all this together.
garage.each do |g,cars|
puts "garage #{g}"
cars.each do |car|
print_name_and_price(car.car_name, car.car_price, longest)
end
end
displays:
garage 1
car: jaguar
price: $134,000
car: mercedes
price: $234,000
garage 2
car: jaguar
price: $134,000

Asking a user multiple questions using Ruby gets.chomp method

I'm very new to Ruby and practicing with user input. I have coded the following which allows the user to input names of students continuously until they hit return twice. After each input the program returns how many students are in the school, and when they are finished inputting for good, it prints out a list of the students and the cohort they are in.
At the moment, the cohort is hardcoded, and I want to modify this so that I can ask for both the name and the cohort, with the program continuing to ask for this info until the user hits return twice. Any help would really be appreciated - thanks :)
puts "Please enter the names of the students"
puts "To finish, just hit return twice"
students = []
name = gets.chomp
while !name.empty? do
students << {name: name, cohort: cohort}
puts "Now we have #{students.count} students"
name = gets.chomp
end
students
end
def print_header
puts "The students of this Academy".center(50)
puts "-----------".center(50)
end
def print(students)
students.each do |student, index|
puts "#{student[:name]} #{student[:cohort]} cohort"
end
end
end
def print_footer(names)
puts "Overall, we have #{names.count} great students".center(50)
end
students = input_students
print_header
print(students)
print_footer(students)
Instead of a while loop, I'd suggest to use loop do and break in case the name is empty (or also cohort):
loop do
puts "Please enter the names of the students"
name = gets.chomp
break if name.empty?
puts "Please enter the cohort"
cohort = gets.chomp
# break if cohort.empty?
students << {name: name, cohort: cohort}
end

Find specific word in array in order

I have:
ary = [
'ahorseride', 'amparkeetjump', 'acatlive', 'adogwish', 'bmparkeetjump',
'bcatlive', 'bdogwish', 'bhorseride', 'brabuffallo', 'chorseride7679',
'ceelionking5454', 'crabuffallokjkj4', 'dgiraffeoiu9-0', 'chorseride767',
'ccatlive', 'dcatlive', 'ddogwish', 'emparkeetjump', 'emouse', 'eeelionking',
'erabuffallo', 'ffhorseride7679', 'fgeelionking5454', 'fhcrabuffallokjkj4a',
'fkcgiraffeoiu9087*s',
]
big_animal = ['horse', 'lion', 'buffallo', 'giraffe']
For each element in big_animal, I would like to find which elements of ary include that as a substring, and present them in a specific order. I would like to achieve this result:
horse in the chorseride7679
lion in the ceelionking5454
buffallo in the crabuffallokjkj4
giraffe in the dgiraffeoiu9-0
and/or
horse in the ffhorseride7679
lion in the fgeelionking5454
buffallo in the fhcrabuffallokjkj4a
giraffe in the fkcgiraffeoiu9087*s
How do I do this? My attempt is:
horse = big_animal[0]
ary.each do |e|
puts "#{horse} in the house of #{e}" if e.include?(horse)
end
whose result is:
horse in the house of ahorseride
horse in the house of bhorseride
horse in the house of chorseride7679
horse in the house of chorseride767
horse in the house of ffhorseride7679
It appears that you wish to find four consecutive elements of ary that respectively include the strings given as elements of big_animal. If that is true all such consecutive elements from ary could be obtained as follows.
ary.each_cons(big_animal.size).select do |words|
big_animal.each_index.all? { |i| words[i].include?(big_animal[i]) }
end
#=> [["chorseride7679", "ceelionking5454", "crabuffallokjkj4", "dgiraffeoiu9-0"],
# ["ffhorseride7679", "fgeelionking5454", "fhcrabuffallokjkj4a",
# "fkcgiraffeoiu9087*s"]]
See Enumerable#each_cons.
First of all, I have to admit that this is the very first time I'm using Ruby which is why my code might be a bit messy.
On a second note, I've seen the answer from nPn getting the output you want to have with a regex. However, this is not completely solving your problem because his solution will only retrieve values containing the big_animal and ending with a digit. This solution does not care about the right order of the houses.
As far as I've understood your problem, you want all the houses that contain the big_animal but only if they are in the same order. This is why I've come up with this piece of code:
ary = ['ahorseride', 'amparkeetjump', 'acatlive', 'adogwish', 'bmparkeetjump', 'bcatlive', 'bdogwish', 'bhorseride', 'brabuffallo', 'chorseride7679', 'ceelionking5454', 'crabuffallokjkj4', 'dgiraffeoiu9-0', 'chorseride767', 'ccatlive', 'dcatlive', 'ddogwish', 'emparkeetjump', 'emouse', 'eeelionking', 'erabuffallo', 'ffhorseride7679', 'fgeelionking5454', 'fhcrabuffallokjkj4', 'fkcgiraffeoiu9087', ]
big_animal = ['horse', 'lion', 'buffallo', 'giraffe']
count = 0
houses = Array.new(big_animal.length)
while count < ary.length do
animals = 0
if ary[count].include?(big_animal[animals])
while animals < big_animal.length do
if ary[count+animals].include?(big_animal[animals])
houses[animals] = ary[count+animals]
if animals == big_animal.length-1
puts houses
end
else
houses = Array.new(big_animal.length)
end
animals = animals + 1
end
end
count = count + 1
end
The above code gives me the following output:
chorseride7679
ceelionking5454
crabuffallokjkj4
dgiraffeoiu9-0
ffhorseride7679
fgeelionking5454
fhcrabuffallokjkj4
fkcgiraffeoiu9087
You can also try it online here and change the input array on that website to test different scenarios. I'd be happy to know if this is what you were searching for.
Just guessing that you are looking for the first animal with the matching name , ending in a digit.
ary = ['ahorseride', 'amparkeetjump', 'acatlive', 'adogwish', 'bmparkeetjump',
'bcatlive', 'bdogwish', 'bhorseride', 'brabuffallo', 'chorseride7679',
'ceelionking5454', 'crabuffallokjkj4', 'dgiraffeoiu9-0', 'chorseride767',
'ccatlive', 'dcatlive', 'ddogwish', 'emparkeetjump', 'emouse', 'eeelionking',
'erabuffallo', 'ffhorseride7679', 'fgeelionking5454', 'fhcrabuffallokjkj4', 'fkcgiraffeoiu9087' ]
big_animal = ['horse', 'lion', 'buffallo', 'giraffe']
big_animal.each do |ba|
rs = "#{ba}.*\\d$"
rexp = Regexp.new rs
entry = ary.find { |i| i.match(rs) }
puts "#{ba} in the house of #{entry}"
end
#horse in the house of chorseride7679
#lion in the house of ceelionking5454
#buffallo in the house of crabuffallokjkj4
#giraffe in the house of dgiraffeoiu9-0
https://repl.it/NxGr
By brazenly overriding String method. Better do not use this approach -))
class String
def in_house? other
(other.is_a? String) && (other.include? self)
end
def in_house_message_for other
puts "#{self} in house of #{other}" if self.in_house? other
return
end
end
then
ary.each do |house|
big_animal.each do |animal|
animal.in_house_message_for(house)
end
end
or that way to print in same order with big_animal array
big_animal.each do |animal|
ary.each do |house|
animal.in_house_message_for(house)
end
end
the second one will print
horse in house of ahorseride
horse in house of bhorseride
horse in house of chorseride7679
horse in house of chorseride767
horse in house of ffhorseride7679
lion in house of ceelionking5454
lion in house of eeelionking
lion in house of fgeelionking5454
buffallo in house of brabuffallo
buffallo in house of crabuffallokjkj4
buffallo in house of erabuffallo
buffallo in house of fhcrabuffallokjkj4
giraffe in house of dgiraffeoiu9-0
giraffe in house of fkcgiraffeoiu9087

ruby: undefined local variable or method 'car' -- fixing syntax errors

I have a few syntax errors on my program that are really bothering me. I can't seem to figure out how to fix them as I am new to ruby. The first error is on the title, I have a few more I'm sure. The purpose of the program is to create cars with make, model, and year and have user input how many cars they want then display all of them at the end.
Can someone point me to the right direction?
Here is my code:
class Car
def initialize(make, model, year)
#make = make
#model = model
#year = year
end
print "How many cars do you want to create? "
array_of_cars = Array.new
num_cars = gets.to_i
c = car.new
for i in 1..num_cars
end
puts
print "Enter make for car #{i}: "
make = gets.chomp
print "Enter model for car #{i}: "
model = gets.chomp
print "Enter year for car #{i}: "
year = gets.to_i
c.set_make(make)
c.set_model(model)
c.set_year(year)
array_of_cars << c
end
puts
puts "You have the following cars: "
for car in array_of_cars
puts "#{car.get_year} #{car.get_make} #{car.get_model}"
end
In ruby, class names are constants, so should start with a capital letter, as in class Car. When creating a new object of that class, you call new on the class itself. So you would change car.new into Car.new.
You will also need to define your set_* and get_* methods inside the class, but since this is a common pattern, ruby has attr_accessor available. See this answer for a full explanation of attr_accessor.
Consider that your Car does not do anything, it contains only data and has no methods. When this happens, consider making it a Struct instead of a class. A Struct generates a reader and writer method automatically without even specifying attr_reader.
Car = Struct.new(:make, :model, :year)
array_of_cars = Array.new
while true
puts
print "Enter make for car ('x' to exit): "
make = gets.chomp
break if make == 'x'
print "Enter model for car: "
model = gets.chomp
print "Enter year for car: "
year = gets.to_i
array_of_cars << Car.new(make, model, year)
end
puts
puts 'You have the following cars:' # sorted by year for fun
array_of_cars.sort_by{ | car | car.year }.each do | car |
puts "#{car.year} #{car.make} #{car.model}"
end
A few pieces of advice.
Run Ruby with the -w option :
$ ruby -w cars.rb
cars.rb:17: warning: mismatched indentations at 'end' with 'for' at 16
cars.rb:34: warning: mismatched indentations at 'end' with 'class' at 1
cars.rb:41: warning: mismatched indentations at 'end' with 'for' at 39
and eliminate the cause of warnings.
$ ruby -w cars.rb
How many cars do you want to create? 2
cars.rb:2:in `initialize': wrong number of arguments (given 0, expected 3) (ArgumentError)
from cars.rb:13:in `new'
from cars.rb:13:in `<main>'
new calls initialize, so new must have the same number of arguments
as parameters in initialize. Hence a car can be created only after you have asked all the information.
Don't work in the class. As written, your code is executed when Ruby reads
the class definition. For this exercise, you can leave it in the main level outside the class definition, or put it into a method.
for i in 1..num_cars
end
This loop is empty and does nothing. And prefer powerful iterators instead of this C, Perl, Java style (for, while, etc).
I define strings with apostrophes and keep double quotes when interpolation is needed (even if it's a question of nano seconds and personal choice). See here and there.
If you want to be comfortable with Ruby programming, I recommend The Pickaxe.
There are many ways of doing things in Ruby. The following is one solution.
class Car
attr_reader :make, :model, :year
def initialize(make, model, year)
#make = make
#model = model
#year = year
end
def self.make_car # class method (more precisely : singleton method)
print 'How many cars do you want to create? '
array_of_cars = Array.new
num_cars = gets.to_i
num_cars.times do | i |
real_index = i + 1
puts
print "Enter make for car #{real_index}: "
make = gets.chomp
print "Enter model for car #{real_index}: "
model = gets.chomp
print "Enter year for car #{real_index}: "
year = gets.to_i
=begin
c = Car.new(make, model, year)
array_of_cars << c
=end
# some will tell you to avoid unnecessary variables ...
array_of_cars << Car.new(make, model, year)
end
puts
puts 'You have the following cars:' # sorted by year for fun
array_of_cars.sort_by{ | car | car.year }.each do | car |
puts "#{car.year} #{car.make} #{car.model}"
end
end
end # class Car
Car.make_car

How to Add Values to a Hash in Ruby

I have done much research on this topic, but in every circumstance I attempt, the values appear to be replaced in the hash. After the person opts to enter a new ID, I would like the next person's name and age to be added to the hash. Could someone explain to me why the keys and values are being replaced?
class Person
def initialize(name, age)
if name != nil || age != nil
if #people != nil
#people[name.__id__] = age.__id__
else
#people = {name => age}
end
else
puts "Invalid credentials."
end
end
attr_reader :people
end
class MainInit
def initialize()
options = ["y", "yes", "n", "no"]
choice = "y"
while choice.downcase == "y" || choice.downcase == "yes"
p "Enter Name:"
inputname = gets.chomp
p inputname
p "Enter Age:"
inputage = gets.chomp
p inputage
person = Person.new(inputname, inputage)
p person.people
p "Enter another ID?"
choice = gets.chomp
until options.include? choice.downcase
p "Invalid Choice"
p "Enter another ID?"
choice = gets.chomp
end
end
end
end
MainInit.new
I think the reason the key-value pairs are being replaced is this:
The statement in your initialize method
if #people != nil
will always evaluate to false. initialize is called when you create a new object, so by default #people has not been defined or set yet, so each time you call
person = Person.new(inputname, inputage)
it creates a new Person rather than adding the new person to an exiting Hash (which is what I think you are trying to do).
It might work if you make people a class variable (##people), but it seems like you just want to create a Hash in your main program and then add the new entries in there.
So something like this
people = Hash.new # Or even just people = {}
Then when you have a new name / age entry to add
people[name] = age
I have not tried it, but I think your entire program should be reduced to something like this:
people = Hash.new
options = ["y", "yes", "n", "no"]
choice = "y"
while choice.downcase == "y" || choice.downcase == "yes"
p "Enter Name:"
inputname = gets.chomp
p inputname
p "Enter Age:"
inputage = gets.chomp
p inputage
#person = Person.new(inputname, inputage)
people[inputname] = inputage
person = people[inputname]
p person.people
p "Enter another ID?"
choice = gets.chomp
until options.include? choice.downcase
p "Invalid Choice"
p "Enter another ID?"
choice = gets.chomp
end
Let me both explain why you are having the problem you describe and also offer some suggestions for how you might change your code.
class Person
In class Person, you need to save your list of persons at the class level, which means the use of either a class instance variable (e.g., #people) or a class variable (e.g., ##people). I am with the majority of Rubiests in prefering the former. (The reasons are beyond the scope of this answer, but you will find a lot written on the subject by simply Googling, "Ruby 'class instance variables' versus 'class variables'". The inner quotes--the only ones you enter--help narrow the search.)
To define a class instance variable, #people, we just enter it as follows:
class Person
#people = {}
class << self
attr_accessor :people
end
def initialize(name, age)
self.class.people[name] = age
end
end
The # means it is an instance variable. As soon as Ruby reads class Person, it sets self to Person. It then reads #people = {} and makes that an instance variable of Person. By contrast, if you were to initialize #person within, say, an initialize method, self would at that time be an instance of Person, so #person would be a normal instance variable. (Aside: we could have both a class instance variable #person and an instance variable #person, and Ruby would treat them as differently as it would #night and #day.)
In order for objects to access #people we define an accessor. If we just entered attr_accessor :person, Ruby would create an accessor for a regular instance variable #person. Instead we enter class << self, which directs Ruby to associate what follows, until end is reached, with the class.
Each time a new instance of Person is created, for a given name and age,
self.class.people[name] = age
adds an element to the hash #person, since self.class is Person and people is the accessor.
Now look at the class MainInit
class MainInit
class MainInit
def initialize
loop do
name = nil
loop do
print 'Enter Name: '
name = gets.strip
break unless name.empty?
end
puts "name is #{name}"
age = nil
loop do
print 'Enter Age: '
age = gets.strip
case age
when /^\d+$/ && ('10'..'120')
break
else
puts 'age must be between 10 and 120'
end
end
puts "age is #{age}"
person = Person.new(name, age)
puts "people is now #{Person.people}"
loop do
print "Enter another ID? "
case gets.chomp.downcase
when 'n', 'no'
return
when 'y', 'yes'
break
else
puts 'Invalid choice'
end
end
end
end
end
loop do...end
You see that in several places I have used loop do...end with break to exit a loop. I'm a big fan of this construct, as compared to loop while... or or until..., in part because it avoids the need to enter a starting condition to get into the loop and then repeat the same condition withing the loop. I also just think it looks cleaner.
Any variables created within the loop cease to exist when you leave the loop, so if you want a variable's value (e.g., name and age), you must reference the variable outside of the beginning of the loops. That is why (and the only reason) I have name = nil and age = nil. It didn't have to be nil; I could have initialized them to anything.
Use of case statement
The loop for getting age uses this case statement:
case age
when /^\d+$/ && ('10'..'120')
...
end
This requires some explanation. The case statement uses String#===, rather than String#== to obtain truthy values. Therefore when /^\d+$/ is equivalent to:
/^\d+$/ === age
which is the same as
/^\d+$/ =~ age
The regex simply ensures that all characters of age are digits (e.g., "39).
Similarly,
('10'..'120') === age
is the same as
('10'..'120').cover?(age)
Odds and Ends
I used String#strip in place of String#chomp. Both remove ending newline characters, but strip also removes spaces the user may have entered at the beginning or end of the input string.
For strings, I mostly used single quotes, but double-quotes are needed for string interpolation. For example, I initially wrote puts 'name is #{name}'. That printed name is #{name}. After changing that to puts "name is #{name}", it correctly printed name is Debra.
Example
MainInit.new
Enter Name: Debra
name is Debra
Enter Age: 29
age is 29
people is now {"Debra"=>"29"}
Enter another ID? y
Enter Name: Billy-Bob
name is Billy-Bob
Enter Age: 58
age is 58
people is now {"Debra"=>"29", "Billy-Bob"=>"58"}
Enter another ID? u
Invalid choice
Enter another ID? n

Resources