I have an array: #costumer_request = ['regular', '12/03/2013', '14/03/2013'].
I need to verify if the first item is 'regular' or 'rewards', then verify if each date of the rest of the array is a weekend.
I did something like this:
#costumer_request.each_with_index do |item, index|
if index[0] == 'regular:'
if DateTime.parse(index).to_date.saturday? or DateTime.parse(index).to_date.sunday?
print "It's a weekend"
else
print "It's not a weekend"
end
end
end
require 'date'
module HotelReservation
class Hotel
HOTELS = {
:RIDGEWOOD => 'RidgeWood',
:LAKEWOOD => 'LakeWood',
:BRIDGEWOOD => 'BridgeWood'
}
def weekend?(date)
datetime = DateTime.parse(date)
datetime.saturday? || datetime.sunday?
end
def find_the_cheapest_hotel(text_file)
#weekends_for_regular = 0
#weekdays_for_regular = 0
#weekends_for_rewards = 0
#weekdays_for_rewards = 0
File.open(text_file).each_line do |line|
#costumer_request = line.delete!(':').split
#costumer_request = line.delete!(',').split
#Here I want to process something in each array
#but if I do something like bellow, it will
#store the result of the two arrays in the same variable
#I want to store the result of the first array, process something
#and then do another thing with the second one, and so on.
if(#costumer_request.first == 'regular')
#costumer_request[1..-1].each do |date|
if (weekend?(date))
#weekends_for_regular +=1
else
#weekdays_for_regular +=1
end
end
else
if(#costumer_request.first == 'rewards')
#costumer_request[1..-1].each do |date|
if (weekend?(date))
#weekends_for_rewards +=1
else
#weekdays_for_rewards +=1
end
end
end
end
end
end
end
end
The find_the_cheapest_hotel method should output the cheapest hotel based on the given data.
require 'time'
require 'date'
#costumer_request = ['regular', '28/03/2013', '14/03/2013']
if #costumer_request.first == 'regular'
if #costumer_request[1...-1].all?{|item| Time.local(item).saturday? || Time.local(item).sunday? }
print "It's a weekend"
else
print "It's not a weekend"
end
end
output:
It's a weekend
Here's a really clean way of doing it:
def is_weekend?(dt)
dt.saturday? || dt.sunday?
end
type, *dates = #costumer_request
if(type == 'regular')
dates.each do |date|
if(weekend?(Date.parse(date))
#do processing here
end
end
end
require 'time'
def weekend?(date)
datetime = DateTime.parse(date)
datetime.saturday? || datetime.sunday?
end
#costumer_request = ['regular', '28/03/2013', '14/03/2013']
type = #costumer_request.shift
if type == 'regular'
#costumer_request.each do |date|
if weekend?(date)
puts "#{date} a weekend"
else
puts "#{date} not a weekend"
end
end
end
You may as well add weekend? directly to DateTime:
class DateTime
def weekend?
saturday? || sunday?
end
end
Now you can convert all but the first element to DateTime objects, then check that all of them are on the weekend.
if #customer_request.first == 'regular'
dates = #customer_request[1..-1].map { |date_string| DateTime.parse(date_string) }
if dates.all?(&:weekend?)
puts 'all dates on a weekend'
else
puts 'at least one date is not on a weekend'
end
else
puts 'not a regular customer request'
end
#customer_request = ['regular', '12/03/2013', '14/03/2013']
def val
#customer_request.first == 'regular' &&
#customer_request.drop(1).inject(true) do |m, e|
wd = Time.local(*e.split('/').reverse.map(&:to_i))
m &&= (wd.saturday? || wd.sunday?)
end
end
p val
p val ? 'all are weekends' : 'something isn\'t a weekend'
Related
Open-uri and nokogiri are slow to scrape the site I want hence the Net::OpenTimeout execution expired error. I attempted to code a custom error with rescue however I do not know what condition I can look for to raise that custom error.
I attempted to few if else statements however I really just guessed how to check if I was gonna get that error. I hard coded a condition that failed and thus rescued the error. I am very new to ruby and custom errors. In fact this is my first.
class Scrape
Base = 'http://www.wine.com'
##menu = []
##pages = []
def self.index
index_url = Base + "/list/wine/7155?sortBy=savings&pricemax=90"
#below is where I need to check for the condition to
raise the error
if doc = Nokogiri::HTML(open(index_url))
container = doc.css('.prodList')
wines = container.css('.prodItem')
wines.each do |wine|
##menu << {
:link => wine.css('.prodItemInfo_link').attribute('href').value,
:name => wine.css('.prodItemInfo_name').text,
:rating => (wine.css('.averageRating_average').text.to_i) > 0 ?
(wine.css('.averageRating_average').text) : 'no rating',
:price => wine.css('.productPrice_price-saleWhole').text.strip
}
end
##menu.each do |item|
Bottle.new.create(item)
end
else
begin
raise Custom_error
rescue Custom_error => error
puts error.message
end
end
end
def self.scrape_page(wine_obj)
wine_link = wine_obj.link
individual_page = Base + wine_link
docu = Nokogiri::HTML(open(individual_page))
y = docu.css('.viewMoreModule_text')
more = docu.css('.viewMoreModule_text')
##pages << {
:obj => wine_obj,
:name => docu.css('.pipName').text,
:alcohol_percent => y
x = docu.css('.mobileProdAttrs').css('.prodAlcoholPercent')
y = x.css('.prodAlcoholPercent_percent').text,
:price => docu.css('span.productPrice_price-saleWhole').text,
:origin => docu.css('span.prodItemInfo_originText a').text,
:winemaker_notes => docu.css('.viewMoreModule_text').first.text,
:more => y[2].text,
:rating => docu.css('span.averageRating_average').first.text
}
Page.create_find_by_name( ##pages.last )
end
def self.pages
##pages
end
end
class Cli
def run
puts 'loading from cyberspace'
Scrape.index
Bottle.make_list
controller
end
def controller
input = ''
response = ''
puts ' '
view
while input != 11
response = gets.chomp.to_i
input = "#{response}11".to_i
if input == 111
menu
elsif input == 11
exit
elsif input > 0 && input < 26
find_by_input(input)
elsif input != 0 && input != 111
error_1
end
end
end
def view
puts "welcome to the wine bar"
puts "================="
puts " W I N E "
puts " B A R "
puts "================="
puts " "
puts "type 1 for list of wine"
puts " "
puts "type 0 to exit "
end
def menu
wines = Bottle.list
second_input = ''
while second_input != 0
puts "<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>"
puts "type the corresponding number to view more wine info"
puts
"__________________________________________________________________"
wines.each do |wine|
puts "#{wine.index}) #{wine.name} #{wine.price}"
end
second_input = gets.chomp.to_i
if second_input > 0 && second_input < 26
find_by_input(second_input)
elsif second_input == 0
exit
second_input = 0
elsif second_input > 25 && second_input != 101
error_1
end
end
puts <<-DOC
the the wine number again
for winemaker notes
DOC
end
def find_by_input(input)
while input != 0
selection = Bottle.find_by_input(input)
puts "NAME: #{selection.name}"
puts "PRICE: $#{selection.price}"
puts "RATING: #{selection.rating}"
puts "________________________________________"
puts " type #{input} again "
puts " for more info "
puts " provided by the winemaker "
# reseting input and extending user control functionality
third_input = ''
third_input = gets.chomp.to_i
if third_input == selection.index
response = Scrape.scrape_page(selection)
view_2(response, third_input)
elsif input == 0
exit
end
end
end
def view_2(response, old_input)
next_input = ''
while next_input != 0
puts "Alcohol Percent: #{response.alcohol_percent}"
puts "Winemaker Notes: #{response.winemaker_notes}"
puts " "
puts "Type #{old_input} again for more!!"
next_input = gets.chomp.to_i
if next_input == old_input
input = 0
next_input = 0
# refacort as it puts out 88 again and should not. Also 0 is not
exiting with correct behavior
# refactor so looking for "#{input}"1 to prevent the recall of
input
more(response)
end
end
end
def more(response)
puts response.more
puts menu
end
def error_1
puts " WHOA coder "
puts "type a better number!"
end
def exit
puts <<-DOC
well that was fun
Thank you for checking out
my first cli program
DOC
end
end ```
```class Page
attr_accessor :alcohol_percent, :price, :name, :origin, :winemaker_notes,
:rating, :more, :obj
##web_pages = []
def self.create_find_by_name(hash)
if answer = ##web_pages.find{ |obj| obj.name == hash[:name]}
answer
else
self.new.create(hash)
end
end
def create(hash)
hash.each do |key, value|
self.send(("#{key}="), value)
end
save
view_more
end
def view_more
##web_pages.last
end
def save
##web_pages << self
end
end
attr_accessor :link, :name, :price, :rating, :index
##bottles = []
def create(hash)
hash.each do |key, words|
self.send(("#{key}="), words )
end
save
end
def save
##bottles << self
end
def self.make_list
##numbered_list = ##bottles.sort{ |x,y| x.price <=>
y.price}.map.with_index(1) do
|w,i| w.index = i
w
end
end
def self.list
##numbered_list
end
def self.find_by_input(input)
a = ##numbered_list.find{ |wine| wine.index == input}
# puts "#{a.name} $#{a.price} rating: #{a.rating}"
# puts "type #{input} again for winemaker notes"
# more = ''
# while more != 0
# more = gets.chomp.to_i
# (input == more) ? (Scrape.scrape_page(a.link)) : (self.list)
# end
end
end
class Scrape
Base = 'http://www.wine.com'
##menu = []
##pages = []
def self.index
index_url = Base + "/list/wine/7155?sortBy=savings&pricemax=90"
if doc = Nokogiri::HTML(open(index_url))
container = doc.css('.prodList')
wines = container.css('.prodItem')
wines.each do |wine|
##menu << {
:link => wine.css('.prodItemInfo_link').attribute('href').value,
:name => wine.css('.prodItemInfo_name').text,
:rating => (wine.css('.averageRating_average').text.to_i) > 0 ?
(wine.css('.averageRating_average').text) : 'no rating',
:price => wine.css('.productPrice_price-saleWhole').text.strip
}
end
##menu.each do |item|
Bottle.new.create(item)
end
else
begin
raise Custom_error
rescue Custom_error => error
puts error.message
end
end
end
def self.scrape_page(wine_obj)
wine_link = wine_obj.link
individual_page = Base + wine_link
docu = Nokogiri::HTML(open(individual_page))
y = docu.css('.viewMoreModule_text')
more = docu.css('.viewMoreModule_text')
##pages << {
:obj => wine_obj,
:name => docu.css('.pipName').text,
alcholo = docu.css('.mobileProdAttrs').css('.prodAlcoholPercent'),
:alcohol_percent => alcholo.css('.prodAlcoholPercent_percent').text,
:price => docu.css('span.productPrice_price-saleWhole').text,
:origin => docu.css('span.prodItemInfo_originText a').text,
:winemaker_notes => docu.css('.viewMoreModule_text').first.text,
:more => y[2].text,
:rating => docu.css('span.averageRating_average').first.text
}
Page.create_find_by_name( ##pages.last )
end
def self.pages
##pages
end
end
When the internet connection is down/too slow the custom error is raised.
When an exception is thrown, the program stops its normal flow. You need to surround the part of the code that can throw an exception with a begin..rescue clause, and attempt to handle it, re-raise it, or raise another exception instead.
In your example, that would be:
begin
Nokogiri::HTML(open(url))
rescue Net::OpenTimeoutError => e
# log the error message if needed, raise your CustomError instead
raise CustomError, e.message
end
You can omit the begin, and put a rescue clause at the end of the method, ruby will interpret this as if the entire method body was wrapped in a begin..rescue block, something like this:
def open_page(url)
return Nokogiri::HTML(open(url))
rescue Net::OpenTimeoutError => e
raise CustomError, e.message
end
I'm having difficulty in adding underscores in my program. Also what is troubling me is that I can't seem to figure out how to remove the underscores when the user inputs the correct letter.
class Game
attr_reader :guess_count, :is_over, :word_length
def initialize (secret_word)
#secret_word = secret_word
#guess_count = 0
#is_over = false
#word_length = secret_word.length
end
def check_word(guess)
#guess_count += 1
if #secret_word == guess
puts "Congratulations!"
#is_over = true
else
#is_over = false
puts "Sorry, try again!"
end
end
def subtract_guess_count
counter = #word_length - #guess_count
end
end
puts "User 1, What is your secret word?"
secret_word = gets.chomp
anything = Game.new(secret_word)
while !anything.is_over
puts "User 2, Guess the secret word"
guess = gets.chomp
anything.check_word(guess)
if anything.subtract_guess_count == 0
puts "You lose! the correct word was #{secret_word}"
exit!
end
if anything.is_over == false
puts "You have #{anything.subtract_guess_count} left!"
end
end
Major Edit
To create a string of underscores you can do this:
word = ""
1.upto(#secret_word.length) do
word << "_"
end
To replace a character with an underscore (and vice versa) in a ruby string you can use the syntax:
word[i] = "_"
To print a word with spaces between each letter you can use the following:
array = word.split(//)
array.join(" ")
For your code a check_letter function could be useful:
def check_letter (guess)
if (#secret_word[guess])
for i in 0..((#secret_word.length) -1 )
if #secret_word[i] == guess
#progress[i] = guess
end
end
end
show_progress
check_word(#progress)
end
Working code
#! /usr/bin/env ruby
#
class Game
attr_reader :guess_count, :is_over, :word_length, :progress
def initialize (secret_word)
#secret_word = secret_word
#guess_count = 0
#is_over = false
#word_length = secret_word.length
#progress = ""
1.upto(secret_word.length) do
#progress << "_"
end
end
def check_word(guess)
if #secret_word == guess
puts "Congratulations!"
#is_over = true
else
#is_over = false
puts "Sorry, try again!"
end
end
def check_letter (guess)
if (#secret_word[guess])
for i in 0..((#secret_word.length) -1 )
if #secret_word[i] == guess
#progress[i] = guess
end
end
end
show_progress
check_word(#progress)
end
def show_progress
array = #progress.split(//)
word = ""
array.each do |letter|
word << letter + " "
end
puts word
end
def one_less_guess
#guess_count += 1
end
def subtract_guess_count
counter = #word_length - #guess_count
end
end
puts "User 1, What is your secret word?"
secret_word = gets.chomp
anything = Game.new(secret_word)
while !anything.is_over
puts "User 2, Guess the secret word"
guess = gets.chomp
anything.one_less_guess
if guess.length == 1
anything.check_letter(guess)
else
anything.check_word(guess)
end
if anything.subtract_guess_count == 0
puts "You lose! the correct word was #{secret_word}"
exit!
end
if anything.is_over == false
puts "You have #{anything.subtract_guess_count} left!"
end
end
I am trying to get this Ruby game I made to loop after the first round, and end with a "game over" if the player hits a mine. I have it to where it will build the board, which is a pre-built layout in a .txt file, and it will loop, but the game doesn't end. Here is my code
def load_board
mine_field = []
File.open("mines.txt", 'r') do |f|
f.gets
f.each_line do |line|
mine_field.push line.chomp.split('')
end
end
return mine_field
end
def create_hint_board(board)
hint = Array.new
for i in (0..board.size-1)
hint[i] = []
for j in (0..board[i].size-1)
hint
if board[i][j] == '*'
hint[i].push '*'
else
bombs = 0
for x in (-1..1)
for y in (-1..1)
if i-x >= 0 &&
i-x < board.size &&
j-y >= 0 &&
j-y < board[i].size &&
board [i-x][j-y] == '*'
bombs += 1
end
end
end
hint[i][j] = bombs
end
end
end
return hint
end
def print_board (board)
board.each do |row|
p row
end
end
def pp_board (board)
puts Array.new(board[0].size*2+1, '-').join('')
board.each do |row|
puts "|" + row.join('|') + "|"
puts Array.new(row.size*2+1, '-').join('')
end
end
def copy_to_blank(board)
blank = Array.new
for i in 0..(board.size-1)
blank << Array.new(board[i].size, '.')
end
blank
end
def play(board)
puts "Welcome to Minesweeper!"
puts
puts
puts
hint = create_hint_board(board)
game = copy_to_blank(board)
puts "Starting"
pp_board game
#copy coords user puts in
coords = gets.chomp.split(' ')
i = coords[0].to_i
j = coords[1].to_i
game[i][j] =
hint[i][j]
pp_board game
puts
puts "Hint"
pp_board hint
end
end
#start the game
play load_board
Also, mines.txt looks like this...
4 3
*...
..*.
....
I would appreciate any feedback you can give.
This will get you looping:
# start while loop
while true
coords = gets.chomp.split
i = coords[0].to_i
j = coords[1].to_i
if hint[i][j] == "*"
puts "you lose"
break
end
game[i][j] = hint[i][j]
pp_board game
puts "\nHint"
pp_board hint
end
#end while loop
Ctrl-c to bust out. You can also use break to exit the loop:
break if game_over?
This is really driving me up a wall.
I have an instance method I'm trying to debug, but I'm running into an issue where my puts and gets aren't showing up inside the instance method.
Code:
#! /usr/bin/env ruby
class Calculator
def evaluate(string)
ops = string.split(' ')
ops.map! do |item|
if item.is_a? Numeric
return item.to_i
else
return item.to_sym
end
end
puts "Got #{string}" #Doesn't output
puts "Converted to #{ops}" #This too
opscopy = ops.clone
ops.each.with_index do |item, index|
if item == :* || item == :/
opscopy[index] = ops[index-1].send(item, ops[index+1])
opscopy[index-1] = opscopy[index+1] = nil
end
end
ops = opscopy.compact
puts "After multi/div #{ops}"
ops.each.with_index do |item, index|
if item == :+ || item == :-
opscopy[index] = ops[index-1].send(item, ops[index+1])
opscopy[index-1] = opscopy[index+1] = nil
end
end
puts "After +/- #{opscopy.compact}"
opscopy.compact.first
end
end
item = Calculator.new.evaluate "4 * 2"
puts "#{item} == 8" #Prints :(
Output:
action#X:~/workspace/ruby$ ./calculator.rb
4 == 8
That return in your map! block is where the problem is.
ops.map! do |item|
if item.is_a? Numeric
return item.to_i # returns from method
else
return item.to_sym # returns from method
end
end
You are returning the method in your map! block before the puts is called.
Change the map! block to:
ops.map! do |item|
item.send(item.is_a?(Numeric) ? :to_i : :to_sym)
end
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>