Case Statements in Ruby (line.scan) - ruby

I'm using named rexeg capture groups and my case statement works with match, but it gives me data that I don't want. When I run the code below it only works to match one statement. Where am I going wrong?
File::open(file).lines do |line|
case
when line.scan(regex1) then puts line.scan(regex1)
when line.scan(regex2) then puts line.scan(regex2)
when line.scan(regex3) then puts line.scan(regex3)
end
end
end

caseexecutes the first true-expresion.
If you have multiple checks, where each check can be true, you should use mutliple if-statements.
File::open(file).lines do |line|
puts line.scan(regex1) if line.scan(regex1)
puts line.scan(regex2) if line.scan(regex2)
puts line.scan(regex3) if line.scan(regex3)
end
I think the following version is a bit more flexible and efficient:
File::open(file).lines do |line|
[ regex1, regex2, regex3] do |regex|
if result = line.scan(regex)
puts result
end
end
end

Related

List in Ruby gem cli

I am making a ruby cli that outputs a list of game deals scraped from a site.
The list prints out promptly using
def games_sales
Deal.all.each_with_index do |deal, index|
puts "#{index + 1}. #{deal.title}"
end
puts "What game do you want to see?"
input = gets.strip
game_selection(input.to_i)
end
My problem comes when asking the user to select an item from the list.
def game_selection(input)
deal = Deal.find_by_index(input)
#binding.pry
deal.each do |deal|
puts "#{deal.index}"
puts " Name: #{deal.title}"
puts " Price: #{deal.price}"
puts " Store: #{deal.store}"
puts " Expiration: #{deal.expiration}"
end
deal
end
It returns the int input but only the first item on the list every time.
I forgot my find_by_index method:
def self.find_by_index(input)
all.select do |deal|
end
end
which is incomplete
Not 100% sure if I got your question right and if you're using Rails, but Deals.all let me think of this.
I had to replace Deals.all with DEALS for testing as I haven't got a rails app running. So I used an Array of OpenStructs to fake your Model result.
# this fakes Deals.all
require 'ostruct'
DEALS = [
# add any more properties the same way as title, separated by comma
OpenStruct.new(title: 123),
OpenStruct.new(title: 456)
]
def games_sales
DEALS.each_with_index do |deal, index|
puts "#{index + 1}. #{deal.title}"
end
puts "What game do you want to see?"
input = gets.strip
game_selection(input.to_i)
end
def game_selection(input)
deal = DEALS.at(input-1)
p deal[:title]
end
def self.find_by_index(input)
all.select do |deal|
deal.index == input
end
end
games_sales
Result when choosing 1 is 123, choosing 2 you'll get 456, due to p deal[:title] above in the code.
I think your find_by_index need to get the right index and in my example I had to use at(index) as at(input-1) in order to get the right result.
I really hope this helps somehow and I suggest that you add the expected result to your question, in case my answer does not help you.

Resigning an Array in an if block that is never ran makes it nil

Ok, so I have a function, which if a variable ends up not being an array,
makes it an array, with the value of the original variable as the first and
only element
iv written the code as following
variable = userInputOfSorts()
puts JSON.pretty_generate(variable) # Prints expected output, array with 2 values
if (!variable.is_a?(Array)
variable = [variable]
end
puts variable # nil
Now, I have put debug statements inside this if block(puts statements) and can confirm that it is not running.
With the debug statements inside the if block, which I am certain is not running, I tried to comment out the reassignment statement
# variable = [variable])
shouldn't matter, that statement is never ran anyway. But by god, it worked. Obviously, I need that statement there for when the user input is not an array.
So my problem is, the reassignment statement that is never ran, makes the variable null.
I have an example written, that one can run to verity this. It seems to have something to do with classes/attr_reader, as it worked when just writing it outside a class, and running it.
require 'JSON'
class MainClass
attr_reader :mock_input
def initialize(input)
puts "hello"
#mock_input = input
# Print with JSON or puts
puts "-------"
if (mock_input.is_a?(Array))
puts JSON.pretty_generate mock_input
else
puts mock_input
end
puts "-------"
if (!mock_input.is_a?(Array))
puts "Converting to array..."
mock_input = [mock_input]
end
# Print with JSON or puts
puts "-------"
if (mock_input.is_a?(Array))
puts JSON.pretty_generate mock_input
else
puts mock_input
end
puts "-------"
end
end
MainClass.new ["hello", "world"]
A slightly shorter implementation to convert input to an array if it isn't already one:
def input_to_array(input)
[*input]
end
Using your original code:
variable = userInputOfSorts()
puts JSON.pretty_generate(variable) # Prints expected output, array with 2 values
variable = [*variable]
puts variable # nil
I filed a bug report, and got an answer from one of the developers. Apparently, in ruby, commenting out code in if blocks that are evaluated to false should be able to affect your program. AKA, this is not a bug, its a "feature"
But,
I found a solution
So, the original problem is
require 'JSON'
class MainClass
attr_reader :mock_input
def initialize(input)
puts "hello"
#mock_input = input
# Print with JSON or puts
puts "-------"
if (mock_input.is_a?(Array))
puts JSON.pretty_generate mock_input
else
puts mock_input
end
puts "-------"
if (!mock_input.is_a?(Array))
puts "Converting to array..."
mock_input = [mock_input]
end
# Print with JSON or puts
puts "-------"
if (mock_input.is_a?(Array))
puts JSON.pretty_generate mock_input
else
puts mock_input
end
puts "-------"
end
end
MainClass.new ["hello", "world"]
changing the line
mock_input = [mock_input]
to
#mock_input = [#mock_input]
Fixes it
PS: as #Wayne Conrad pointed out, I could have just used Array(mock_input) instead of the if statement entirely.

How to stop outer block from inner block

I try to implement search function which looks for occurrence for particular keyword, but if --max options is provided it will print only some particular number of lines.
def search_in_file(path_to_file, keyword)
seen = false
File::open(path_to_file) do |f|
f.each_with_index do |line, i|
if line.include? keyword
# print path to file before only if there occurence of keyword in a file
unless seen
puts path_to_file.to_s.blue
seen = true
end
# print colored line
puts "#{i+1}:".bold.gray + "#{line}".sub(keyword, keyword.bg_red)
break if i == #opt[:max] # PROBLEM WITH THIS!!!
end
end
end
puts "" if seen
end
I try to use break statement, but when it's within if ... end block I can't break out from outer each_with_index block.
If I move break outside if ... end it works, but it's not what I want.
How I can deal with this?
Thanks in advance.
I'm not sure how to implement it in your code as I'm still learning Ruby, but you can try catch and throw to solve this.
def search_in_file(path_to_file, keyword)
seen = false
catch :limit_reached do
#put your code to look in file here...
throw :limit_reached if i == #opt[:max] #this will break and take you to the end of catch block
Something like this already exist here

Catch and throw not working in ruby

I am trying to make a number guessing game in Ruby but the program exits after I type in yes when I want to play again. I tried using the catch and throw but it would not work. Could I please get some help.
Here is my code.
class Game
def Play
catch (:start) do
$a=rand(11)
puts ($a)
until $g==$a
puts "Guess the number between 0-10."
$g=gets.to_i
if $g>$a
puts "The number you guessed is too high."
elsif $g==$a
puts "Correct you won!!!"
puts "Would you like to play again?"
$s=gets()
if $s=="yes"
$c=true
end
if $c==true
throw (:start)
end
elsif $g<$a
puts "The number you guessed is too low."
end
end
end
end
end
Game.new.Play
Edit: Here's my new code after trying suggestions:
class Game
def Play
catch (:start) do
$a=rand(11)
puts ($a)
while $s=="yes"
until $g==$a
puts "Guess the number between 0-10."
$g=gets.chomp.to_i
if $g>$a
puts "The number you guessed is too high."
elsif $g==$a
puts "Correct you won!!!"
puts "Would you like to play again?"
$s=gets.chomp
if $s=="yes"
throw (:start)
end
elsif $g<$a
puts "The number you guessed is too low."
end
end
end
end
end
end
Game.new.Play
Your first problem is here:
$s=gets()
if $s=="yes"
$c=true
end
The gets method will read the next line including the new line character '\n', and you compare it to only "yes":
> gets
=> "yes\n"
The idiomatic way to fix this in Ruby is the chomp method:
> gets.chomp
=> "yes"
That said, your code has two other deficiencies.
You may come from a language such as PHP, Perl, or even just Bash scripting, but Ruby doesn't require the dollar sign before variables. Using a $ gives a variable global scope, which is likely not what you want. In fact, you almost never want a variable to have global scope.
Ruby uses three types of symbol prefixes to indicate scope - # for instance, ## for class, and $ for global. However the most common type of variable is just local which doesn't need any prefix, and what I would suggest for your code.
I have always been told that it is very bad practice to use exceptions for control structure. Your code would be better served with a while/break structure.
When you do gets(), it retrieves the full line with a '\n' in the end. You need to trim the new line character by using:
$g=gets.chomp.to_i
Same for other gets
Based on your updated code (where you fixed the newline problem shown by others), your new problem is that you have wrapped all your game inside while $s=="true". The very first time your code is run, $s is nil (it has never been set), and so you never get to play. If you used local variables instead of global variables (s instead of $s) this would have become more obvious, because the code would not even have run.
Here's one working way that I would re-write your game.
class Game
def play
keep_playing = true
while keep_playing
answer = rand(11) # Make a new answer each time
puts answer if $DEBUG # we don't normally let the user cheat
loop do # keep going until I break from the loop
puts "Guess the number between 0-10."
guess = gets.to_i # no need for chomp here
if guess>answer
puts "The number you guessed is too high."
elsif guess<answer
puts "The number you guessed is too low."
else
puts "Correct you won!!!",
"Would you like to play again?"
keep_playing = gets.chomp.downcase=="yes"
break
end
end
end
end
end
Game.new.play
I know this doesn't really answer your question about why your code isn't working, but after seeing the code you posted I just had to refactor it. Here you go:
class Game
def initialize
#answer = rand(11)
end
def play
loop do
guess = get_guess
display_feedback guess
break if guess == #answer
end
end
def self.play_loop
loop do
Game.new.play
break unless play_again?
end
end
private
def get_guess
puts "Guess the number between 0-10."
return gets.chomp.to_i
end
def display_feedback(guess)
if guess > #answer
puts "The number you guessed is too high."
elsif guess < #answer
puts "The number you guessed is too low."
elsif guess == #answer
puts "Correct you won!!!"
end
end
def self.play_again?
puts "Would you like to play again?"
return gets.chomp == "yes"
end
end
Game.play_loop

Skipping the first line when reading in a file in 1.9.3

I'm using ruby's File to open and read in a text file inside of a rake
task. Is there a setting where I can specify that I want the first line of
the file skipped?
Here's my code so far:
desc "Import users."
task :import_users => :environment do
File.open("users.txt", "r", '\r').each do |line|
id, name, age, email = line.strip.split(',')
u = User.new(:id => id, :name => name, :age => age, :email => email)
u.save
end
end
I tried line.lineno and also doing File.open("users.txt", "r", '\r').each do |line, index| and next if index == 0 but have not had any luck.
Change each to each_with_index do |line, index| and next if index == 0 will work.
function drop(n) will remove n lines from the beginning:
File.readlines(filename).drop(1).each do |line|
puts line
end
It will read the whole file into an array and remove first n lines. If you are reading whole file anyway it's probably the most elegant solution.
When reading larger files foreach will be more efficient, as it doesn't read all data into memory:
File.foreach(filename).with_index do |line, line_num|
next if line_num == 0
puts line
end
File.open("users.txt", "r", '\r') do |file|
lines = file.lines # an enumerator
lines.next #skips first line
lines.each do |line|
puts line # do work
end
end
Making use of an enumerator, which 'remembers' where it is.
You probably really want to use csv:
CSV.foreach("users.txt", :headers, :header_converters => :symbol, :col_sep => ',') do |row|
User.new(row).save
end
File.readlines('users.txt')[1..-1].join()
Works good too.
if you want to keep the file as IO the whole time (no array conversions) and you plan on using the data in the first line:
f = File.open('users.txt', 'r')
first_line = f.gets
body = f.readlines
More likely though, what you want is handled by CSV or FasterCSV as others have pointed out. My favorite way to handle files with a header line is to do:
FasterCSV.table('users.txt')
Since a few answers (no longer ?) work in Ruby 1.9.3, here a working sample of the three best methods
# this line must be dropped
puts "using drop"
File.readlines(__FILE__).drop(1).each do |line|
puts line
end
puts ""
puts "using a range"
File.readlines(__FILE__)[1..-1].each do |line|
puts line
end
puts ""
puts "using enumerator"
File.readlines(__FILE__).each do |file, w|
lines = file.lines # an enumerator
lines.next #skips first line
lines.each do |line|
puts line
end
end
The OP said lineno didn't work for them, but I'm guessing it wasn't applied in the correct way. There's lots of ways achieve what the OP is asking for, but using lineno might help you shorten up your code without having to use readlines, which is sometimes too memory intensive.
From the 1.9.3 docs
f = File.new("testfile")
f.each {|line| puts "#{f.lineno}: #{line}" }
produces:
1: This is line one
2: This is line two
3: This is line three
4: And so on...
Note that is a method you can call from the file object, but not the object yielded to the block.
2: require 'pry'; binding.pry
=> 3: f.each {|line| puts line.lineno }
[1] pry(#<SomeFile>)> line.lineno
NoMethodError: undefined method `lineno' for #<String:0x00007fa7d682b920>
You can find the same example with identical code in docs for the latest stable version of Ruby today (2.5.1).
So going off the example, the code might look like
f = File.new("testfile")
o = File.open("output.txt", w)
f.each do |line|
next if f.lineno == 1
o << line
end

Resources