So I am creating a class in ruby where in I will be able to insert my data on a text file and then read, find from it but I am stuck on delete as well update/edit.
Basically I created a method called "find" and I made it as a reference on my "delete" method.
def find(keyword="")
if keyword
person = People.read_people
found = person.select do |pip|
pip.name.downcase.include?(keyword.downcase) ||
pip.age.downcase.include?(keyword.downcase) ||
pip.country.downcase.include?(keyword.downcase)
end
found.each do |person|
puts person.name + " | " + person.age + " | " + person.country
end
else
puts "find using a key phrase eg. 'find sam' \n\n"
end
end
def list
puts "\nListing People \n\n".upcase
people = People.read_people
people.each do |person|
puts person.name + " | " + person.age + " | " + person.country
end
end
def delete(keyword="")
if keyword
person = People.read_people
found = person.select do |pip|
pip.name.downcase.include?(keyword.downcase) ||
pip.age.downcase.include?(keyword.downcase) ||
pip.country.downcase.include?(keyword.downcase)
end
person.delete(found)
else
puts "find using a key phrase eg. 'find josh' \n\n"
end
end
As you can see I was trying to delete the supplied keyword from the array (w/c was save on a text file) via class method called read_people. Here's how it looks like:
def self.read_people
# read the people file
# return instances of people
people = []
if file_usable?
file = File.new(##filepath, 'r')
file.each_line do |line|
people << People.new.import_line(line.chomp)
end
file.close
end
return people
end
def import_line(line)
line_array = line.split("\t")
#name, #age, #country = line_array
return self
end
How can I fix this and delete the found item via keyword?
See the actual codes here: https://repl.it/repls/VastWildFact
Change
person.delete(found)
to
person -= found # Equivalent to person = person - found
It should work as per https://ruby-doc.org/core-2.2.0/Array.html#method-i-2D
ary - other_ary → new_ary
Returns a new array that is a copy of the original array, removing any items that also appear in other_ary. The order is preserved from the original array.
It compares elements using their hash and eql? methods for efficiency.
Example: [ 1, 1, 2, 2, 3, 3, 4, 5 ] - [ 1, 2, 4 ] #=> [ 3, 3, 5 ]
Another solution is to use reject as follows:
person.reject! do |pip|
pip.name.downcase.include?(keyword.downcase) ||
pip.age.downcase.include?(keyword.downcase) ||
pip.country.downcase.include?(keyword.downcase)
end
Basically you're going to want an export_people and write_people method that'll look something like this:
def self.export_people(people)
people.map do |person|
[person.name, person.age, person.country].join("\t")
end
end
def self.write_people(people)
File.new(##filepath, 'w') do |f|
f.write(export_people(people))
end
end
# Usage:
Person.write_people(array_of_people)
With the above code, you'd call the modified delete method as detailed in Tarek's answer, and then Person.write_people(array_of_people) to write back to the file.
Related
I have this array: ['John', 'Michael', 'Siri']. How can I run an each loop on them and add a text to each "part" of the array and at the end "combine" the result of the loops and set/assign the result of all each to a variable?
By that I mean, I do:
array = ['John', 'Michael', 'Siri']
array.each do |a|
text = "#{a} here"
# Results would need be =>
# John is here
# Michael is here
# Siri is here
#new_string = text # => Which would need to be "John is here Michael is here Siri is here"
end
I have done the code above, but #new_string becomes only Siri is here and if I move the #string out of the loop, like below, it becomes John is here, so basically it takes only one of them and "assigns" it to #new_string.
array = ['John', 'Michael', 'Siri']
array.each do |a|
#text = "#{a} here"
end
#new_string = #text
I tested with [0]+[1]+[2] and it kind of worked, but the problem is that I would not know the size of my array. It can be 2 items or it can be 100 items.
This way
array = ["John", "Michael", "Siri"]
your_variable = array.map { |name| "#{name} is here" }.join(" ")
It's basically a transformation, you want to add something to each element of a collection (use map for that). Lastly, join them up.
Can be done by
array.map { |x| x + ' is here' }.join(' ')
More concisely:
%w{ John Michael Siri}.collect{|s| s+" is here"}.join(" ")
Given the array = ['John', 'Michael', 'Siri'], the problem with your code is that the variable has a scope inside of the loop so it is not accessible after the loop ends.
The solution is to declare the variable before.
#new_string = '' # initialize outside the loop
array.each do |a|
text = "#{a} here "
#new_string += text # note +=
end
#new_string #=> "John here Michael here Siri here "
For the second code, the problem is the same:
#new_string = '' # initialize outside the loop
array = ['John', 'Michael', 'Siri']
array.each do |a|
#new_string += "#{a} here " # note +=
end
#new_string #=> "John here Michael here Siri here "
As you can see string ends with a space, to avoid it populate an array then join as showed in previous answers:
#new_string = [] # initialize outside the loop
array = ['John', 'Michael', 'Siri']
array.each do |a|
#new_string << "#{a} here" # note +=
end
p #new_string = #new_string.join(' ') #=> "John here Michael here Siri here"
Side note:
# comments in ruby starts with #
"Attached is a file with people's names and ages.
There will always be a First name and Last name followed by a colon then the age.
So each line with look something like this.
FirstName LastName: Age
Your job is write a ruby program that can read this file and figure out who the oldest person/people are on this list. Your program should print out their name(s) and age(s)."
This is the code I have so far:
File.open('nameage.txt') do |f|
f.each_line do |line|
line.split(":").last.to_i
puts line.split(":").last.to_i
end
end
With this, I am able to separate the name from the age but I don't know how to get the highest value and print out the highest value with name and age.
Please help!
"figure out who the oldest person/people are on this list", so multiple results are possible. Ruby has a group_by method, which groups an enumerable by a common property. What property? The property you specify in the block.
grouped = File.open('nameage.txt') do |f|
f.group_by do |line|
line.split(":").last.to_i # using OP's line
end
end
p grouped # just to see what it looks like
puts grouped.max.last # end result
You could push all the ages into an array. Do array.max or sort the array and do array[-1].
Here's how I would approach it:
oldest_name = nil
oldest_age = 0
For each line in file do
split line at the colon and store the age inside age variable
split line at the colon and store the age inside name variable
if age is greater than oldest_age then
oldest_age = age
oldest_name = name
end
end
finally print the oldest_name and oldest_age
If you're in to one-liners try this
$ cat nameage.txt
John Doe: 34
Tom Jones: 50
Jane Doe: 32
Citizen Kane: 29
$ irb
1.9.3-p551 :001 > IO.read("nameage.txt").split("\n").sort_by { |a| a.split(":")[1].to_i }.last
=> "Tom Jones: 50"
You can try using hash also,
hash = {}
File.open('nameage.txt') do |f|
f.each_line do |line|
data = line.split(":")
hash[data.first] = data.last.strip
end
hash.max_by{|k,v| v}.join(" : ")
end
File.open('nameage.txt') do |handle|
people = handle.each_line.map { |line| line.split(":") }
oldest_age = people.map { |_, age| age.to_i }.max
people.select { |_, age| age.to_i == oldest_age }.each do |name, age|
puts "#{name}, #{age}"
end
end
You are going the right way. Now you just need to store the right things in the right places. I just merged your code and the code proposed by #oystersauce14.
oldest_name = nil
oldest_age = 0
File.open('nameage.txt') do |f|
f.each_line do |line|
data = line.split(":")
curr_name = data[0]
curr_age = data[1].strip.to_i
if (curr_age > oldest_age) then
oldest_name = curr_name
oldest_age = curr_age
end
end
end
puts "The oldest person is #{oldest_name} and he/she is #{oldest_age} years old."
Notice the use of String#strip when acquiring the age. According to the format of the file, this piece of data (the age) has a space before the first number and you need to strip this before converting it using String#to_i.
EDIT:
Since you may have more than one person with the maximum age in the list, you may do it in two passes:
oldest_age = 0
File.open('nameage.txt') do |f|
f.each_line do |line|
curr_age = line.split(":")[1].strip.to_i
if (curr_age > oldest_age) then
oldest_age = curr_age
end
end
end
oldest_people = Array.new
File.open('nameage.txt') do |f|
f.each_line do |line|
data = line.split(":")
curr_name = data[0]
curr_age = data[1].strip.to_i
oldest_people.push(curr_name) if (curr_age == oldest_age)
end
end
oldest_people.each { |person| p "#{person} is #{oldest_age}" }
I believe that now this will give you what you need.
UPDATE: OK, so I implemented your code, but now the indentation is not showing up! Any ideas what might be wrong? I modified the code so that it would attempt to pass my original test (this is only an exercise so in real life I would not be overriding the XmlDocument class) and here is the modified code:
class XmlDocument
attr_reader :indent_depth, :bool
def initialize(bool = false, indent_depth = 0)
#indent_depth = indent_depth
#bool = bool
end
def method_missing(name, *args)
indentation = ' '*indent_depth
attrs = (args[0] || {}).map { |k, v| " #{k}='#{v}'" }.join(' ')
if block_given?
puts indent_depth
opening = "#{indentation}<#{name}#{attrs}>"
contents = yield(XmlDocument.new(true,indent_depth+1))
closing = "#{indentation}</#{name}>"
bool ? opening + "\n" + contents + "\n" + closing : opening + contents + closing
else
"#{indentation}<#{name}#{attrs}/>"
end
end
end
I'm trying to get the method to pass this test:
it "indents" do
#xml = XmlDocument.new(true)
#xml.hello do
#xml.goodbye do
#xml.come_back do
#xml.ok_fine(:be => "that_way")
end
end
end.should ==
"<hello>\n" +
" <goodbye>\n" +
" <come_back>\n" +
" <ok_fine be='that_way'/>\n" +
" </come_back>\n" +
" </goodbye>\n" +
"</hello>\n"
...but I'm unsure as to where to go with my code, below. I was thinking of using a counter to keep track of how far indented we have to go. I tried some code, but then deleted it because it was getting too messy and I have a feeling that the indentation should not be too complicated to implement.
class XmlDocument
def initialize(bool = false)
#bool = bool
end
def send(tag_name)
"<#{tag_name}/>"
end
def method_missing(meth, arg={}, &block)
arbitrary_method = meth.to_s
tag_string = ''
# 1) test for block
# 2) test for arguments
# 3) test for hash
if block_given? # check for #xml.hello do; #xml.goodbye; end
if yield.class == String # base case: #xml.hello do; "yellow"; end
"<#{arbitrary_method}>#{yield}</#{arbitrary_method}>"
else # in the block we do not have a string, we may have another method
method_missing(yield)
end
elsif arg.empty? # no arguments e.g. #xml.hello
send(arbitrary_method)
else # hash as argument e.g. #xml.hello(:name => 'dolly')
send("#{arbitrary_method} #{arg.keys[0]}='#{arg.values[0]}'")
end
end
end
Your code needs a lot of work - some pointers:
Do not override the send method!
Don't call yield over and over - you don't know what side effects you might cause, not to mention a performance hit - call it once, and remember the return value.
You might want to read up on how to write a DSL (here is a blogpost on the subject), to see how it was done correctly in other places.
Ignoring the above, I will try to answer your question regarding indentation.
In a DSL use case, you might want to use a context object which holds the indentation depth as state:
class Indented
attr_reader :indent_depth
def initialize(indent_depth = 0)
#indent_depth = indent_depth
end
def method_missing(name, *args)
indentation = ' ' * indent_depth
attrs = (args[0] || {}).map { |k, v| "#{k}='#{v}'" }.join(' ')
if block_given?
"#{indentation}<#{name} #{attrs}>\n" +
yield(Indented.new(indent_depth + 1)) +
"\n#{indentation}</#{name}>"
else
"#{indentation}<#{name} #{attrs}/>"
end
end
end
xml = Indented.new
puts xml.hello do |x|
x.goodbye do |x|
x.come_back do |x|
x.ok_fine(:be => "that_way")
end
end
end
# => <hello >
# => <goodbye >
# => <come_back >
# => <ok_fine be='that_way'/>
# => </come_back>
# => </goodbye>
# => </hello>
I am trying to create a program where by the user can enter multiple names. those names are then displayed under each other in alphabetical order, and print(display) every second name backwards. i have gone through several tutorials this is my second day using ruby.. here is what i have so far.
name_list = {}
puts 'please enter names seperated by a space:'
name_list = gets.chomp
names = name_list.split(" ")
to grab names...
names.sort do |a,b| a.upcase <=> b.upcase end
display = "#{names}"
for ss in 0...display.length
print ss, ": ", display[ss], "\n"
end
to arrange them alphabetically and under each other.
i am really struggling to mesh it all together i think i have got at least half a dozen errors in here...if i am on the wrong path could someone guide me to some info so i can start again??
EDIT
i also had this idea of using a class.
but i would have to program the names in i wanted the user to be able to add info via the consol.
class A
def initialize(name)
#name = name
end
def to_s
#name.reverse
end
end
>> a = [A.new("greg"),A.new("pete"),A.new("paul")]
>> puts a
Problems in your code:
name_list defined as an empty hash at the top but not used.
split(" ") -> split
sort { |a, b| a.method <=> b.method } -> sort_by { |x| x.method } -> sort_by(&:method)
sort is not an in-place operation, assign the result (or directly use it).
display = "#{names}" -> display = names
for ss in 0...display.length -> enumerable.each_with_index { |item, index| ... }
don't write do/end in one-liners, use { ... }
I'd write:
puts 'Please enter names separated by spaces'
gets.split.sort_by(&:upcase).each_with_index do |name, index|
puts "%s: %s" % [index, (index % 2).zero? ? name : name.reverse]
end
A few pointers then:
names.sort do |a,b| a.upcase <=> b.upcase end # Will not modify the "names" array, but will return a sorted array.
names.sort! do |a,b| a.upcase <=> b.upcase end # Will modify the "names" array.
To display your names:
names.each_with_index do |name, index|
if index % 2 == 0
puts name
else
puts name.reverse
end
end
puts 'please enter names seperated by a space`enter code here` :'
names = gets.chomp.split(" ")
names.sort! {|a,b| a.upcase <=> b.upcase } # For a single line use {..} instead of do..end
names.each_with_index do |n,i|
if i % 2 == 0
p n
else
p n.reverse
end
end
You can also use a ternary operator, I used the full if else block for readability in this case.
names.each_with_index do |n,i|
p (i % 2 == 0) ? n : n.reverse
end
EDIT
command = ""
names = []
while command != "exit"
puts 'please enter names seperated by a space`enter code here` :'
command = gets.chomp!
if command == "display"
names.sort! {|a,b| a.upcase <=> b.upcase } # For a single line use {..} instead of do..end
names.each_with_index do |n,i|
if i % 2 == 0
p n
else
p n.reverse
end
end
else
names << command
end
end
Hi Im getting an core error which is really standard I suppose in Ruby but dont know what to make of it. I have a program that I have written. Its purpose is to register guests at a camping. You have a menu with 5 different options. 1. Checkin. When i do this I get a undefined method generateParkingLot' for #<Camping:0x10030a768> (NoMethodError)
When I choose Checkout I get a undefined local variable or methoddeparture' for Menu:Class (NameError).
So please i someone has a clue to my problem it would be great. I will here paste all my code. The code is separated in different files and I have used require for the different files. Here though I will paste all the code in one trace. Thankful for all help.
-Sebastien
require 'guest'
require 'parking_lot'
class Camping
attr_accessor :current_guests, :parking_lots, :all_guests, :staticGuests
def initialize(current_guests, parking_lots, all_guests, staticGuests)
#current_guests = Array.new()
# initiera husvagnsplatserna
#parking_lots = Array.new(32)
32.times do |nr|
#parking_lots[nr] = Parking_Lot.new(nr)
#staticGuests = Array[
Guest.new("Logan Howlett", "Tokyo", "07484822",1, #parking_lots[0]),
Guest.new("Scott Summers", "Chicago", "8908332", 2, #parking_lots[1]),
Guest.new("Hank Moody", "Boston", "908490590", 3, #parking_lots[2]),
Guest.new("Jean Grey", "Detroit", "48058221", 4, #parking_lots[3]),
Guest.new("Charles Xavier","Washington DC", "019204822",5, #parking_lots[4])
]
end
#all_guests = []
#staticGuests.each do |guest|
#current_guests[guest.plot.nr] = guest
#all_guests.push(guest)
end
end
def to_s
# creates an empty string
list = " "
# loop from 1 to 32
(1..32).each do |n|
if (!#current_guests[n-1].nil?)
list += #current_guests[n-1].to_s
else
# else adds the text "Vacant"
list += n.to_s + ": Vacant!\n"
end
return list
end
def generateParkingLot
randomNr = 1+rand(32)
# exists a guest at the (0-based) position?
if (!#current_guests[randomNr-1].nil?)
# if so generate a new figure
generateEmpty(array)
else
# returns the generated number
return randomNr
end
end
end
end
class Guest
attr_accessor :firstname, :lastname, :address, :phone, :departure
attr_reader :arrived, :plot
def initialize (firstName, lastName, address, phone, plot)
#firstName = firstName
#lastName = lastName
#address = address
#phone = phone
#arrived = arrived
#plot = plot
end
def to_s
"Personal information:
(#{#firstName}, #{#lastName}, #{#address}, #{#phone}, #{#arrived}, #{#departure}, #{#plot})"
end
end
require 'ruby_camping'
require 'camping_guests'
class Main
if __FILE__ == $0
$camping = Camping.new(#current_guests, #all_guests, #parking_lots,#staticGuests)
puts "\n"
puts "Welcome to Ruby Camping!"
while (true)
Menu.menu
end
end
end
require 'date'
require 'camping_guests'
require 'guest'
class Menu
def initialize(guests = [])
#camping = Camping.new(guests)
end
def self.menu
puts "---------------------------"
puts " Menu"
puts " 1. Checkin"
puts " 2. Checkout"
puts " 3. List current guests"
puts " 4. List all guests"
puts " 5. Exit\n"
puts ""
puts " What do you want to do?"
puts "---------------------------"
print ": "
action = get_input
do_action(action)
end
# fetches menu choice and returns chosen alternativ
def self.get_input
input = gets.chomp.to_i
while (input > 5 || input < 1) do
puts "Ooups, please try again."
input = gets.chomp.to_i
end
return input
end
def self.do_action(action)
case action
when 1:
check_in
when 2:
check_out
when 3:
puts $camping.current_guests
when 4:
puts $camping.all_guests
when 5:
puts "You are now leaving the camping, welcome back!"
exit
end
end
def self.check_in
puts "Welcome to the checkin"
puts "Please state your first name: "
firstName = gets.chomp
puts "Please state your last name:"
lastName = gets.chomp
puts "Write your address: "
address = gets.chomp
puts "and your phone number: "
phone = gets.chomp
puts "finally, your arrival date!"
arrived = gets.chomp
newPLot = $camping.generateParkingLot
newGuest = Guest.new(firstName, lastName, address, phone,arrived,$camping.parking_lots[newPLot-1])
$camping.current_guests[newPLot-1] = newGuest
#all_guests.push(newGuest)
puts "The registration was a success!! You have received the " + newPLot.to_s + "."
end
def self.check_out
puts "Welcome to checkout!"
puts $camping.all_guests
puts "State plot of the person to checkout!"
plot = gets.chomp.to_i
puts "Ange utcheckningsdatum: "
departureDate = gets.chomp.to_i
guest = $camping.current_guests[plot-1]
#departure = departure
guest.departure = departureDate
guestStayedDays = departureDate - guest.arrived
guest.plot.increase(guestStayedDays)
puts guest
$camping.current_guests[plot-1] = nil
end
end
class Parking_Lot
attr_accessor :nr
attr_reader :electricity_meter
def initialize (nr)
#nr = nr
#electricity_meter = 4000-rand(2000)
end
def increase_meter(days)
generatedUse = (10+rand(70))*days
puts "Increases the meter with " + generatedUse.to_s + " kWh."
#electricity_meter += generatedUse
end
def to_s
"Plot #{#nr+1} Electricity meter: #{#electricity_meter} kWh"
end
end
It looks (although I haven't tried this out) like some of your your 'end's are wrong.
The one which is causing your first error (generateParkingLot undefined) is that generateParkingLot is actually defined inside to_s, so you need an extra 'end' at the end of your to_s method.
As for the second error (departure not recognised), the folowing line in self.check_out is at fault:
#departure = departure
because there is no 'departure' variable. (Perhaps you meant DepartureDate?). I suspect there may be a few other issues with this code, but I'm afraid I don't really have time to check now.
One I noticed was that when you have
32.times do |nr|
#parking_lots[nr] = Parking_Lot.new(nr)
I think you might want to end that, either with an 'end' or curly brackets, e.g.
32.times do |nr|
#parking_lots[nr] = Parking_Lot.new(nr)
end
Although that would make your other blocks not match.. In general, just try and make sure your blocks are all defined properly (e.g. everything has a matching end).