I am going through "Learn Ruby the Hard Way" and I came across the method print_a_line in exercise 20.
input_file = ARGV.first
current_file = open(input_file)
def print_a_line(line_count, f)
puts "#{line_count}, #{f.gets.chomp}"
end
current_line = 1
print_a_line(current_line, current_file)
current_line = current_line + 1
print_a_line(current_line, current_file)
current_line = current_line + 1
print_a_line(current_line, current_file)
This method is about to take the current line count and output to the terminal only the contents of the file from that line. I don't understand how the method knows to print the line of the file that is associated with current_line. When I look at this I would think #{f.gets.chomp) would return the entire contents of they file. How does the method know to look at the current_line and print out the associated line of the file?
The gets method being called in print_a_line reads a single line from the file (not the entire contents). The File object referenced by current_file keeps track of the current position within the file, so each time gets is called, the next line is returned.
Nothing is looking at current_line to determine which line to read.
Related
input_file = ARGV.first
def print_all(f)
puts f.read
end
def rewind(f)
f.seek(0)
end
def print_a_line(line_count, f)
puts "#{line_count}, #{f.gets.chomp}"
end
current_file = open(input_file)
puts "First let's print the whole file:¥n"
print_all(current_file)
puts "Let's rewind kind a like a tape"
rewind(current_file)
puts "Let's print three lines:"
current_line = 1
print_a_line(current_line, current_file)
current_line += 1
print_a_line(current_line, current_file)
I'm sure there is a kinda similar post to this, but my question is a bit different. As seen above, the print_a_line method got two params that are line_count and f.
1) As I understood, line_count argument only serves as a variable which is current_line and it is just an integer. How does it relate to the rewind(f) method because when I run the code, the method print_a_line shows this:
1, Hi
2, I'm a noob
where 1 is the first line and 2 is the second. line_count is just a number, how does ruby know that 1 is line 1 and 2 is line 2?
2) Why use gets.chomp in method print_a_line? If I pass just f like this
def print_a_line(line_count, f)
puts "#{line_count}, #{f}"
end
I'll get a crazy result which is
1, #<File:0x007fccef84c4c0>
2, #<File:0x007fccef84c4c0>
Because IO#gets reads next line from readable I/O stream(in this case it's a file) and returns a String object when reading successfully and not reached end of the file. And, chomp removes carriage return characters from String object.
So when you have a file with content such as:
This is file content.
This file has multiple lines.
The code will print:
1, This is file content.
2, This file has multiple lines.
In the second case, you're passing file object itself and not reading it. Hence you see those objects in output.
I have entered the following code from Zed Shaw's book on "Learning Ruby the Hard Way
input_file = ARGV.first #this takes the file test.txt
def print_all(f) #reading a line
puts f.read
end
def rewind(f)
f.seek(0)
end
def print_a_line(line_count, f)
current_line
puts "#{line_count}, #{f.gets.chomp}"
end
current_file = open(input_file)
puts "First let's print the whole file:\n"
print_all(current_file)
puts "Now let's rewind, kind of like a tape"
rewind(current_file)
puts "Let's print three line:"
current_line = 1
print_a_line(current_line, current_file)
current_line = current_line + 1
print_a_line(current_line, current_file)
current_line = current_line + 1
print_a_line(current_line, current_file)
The error I am getting is 'ex20.rb:12:in print_a_line': undefined method chomp' for nil:NilClass (NoMethodError)
from ex20.rb:31:in `'
Any help would be greatly appreciated. I have followed his example word by word.
You have to add a few more lines to the test.txt file (at least three lines of text for each of the method calls you do at the end).
I ran across the same issue because the lesson isn't exactly clear about it, but since the script prints out three lines in a row, you need 3 lines of text in the file for the script to work.
add more lines to your test.txt file
For this exercise I wrote all the code perfectly and even double checked by literally copying and pasting the code from the book (Learn Ruby the Hard Way). For some reason when I call the print_a_line() function, it won't print out the current_line argument I pass through until the 3rd call where it will print out 3 before the line. Is there some flushing of the IO stream I am missing out on here or some nuance with Powershell?
I am running Ruby 2.0.0p576 (x64) on Windows 8 64 bit machine.
Code:
input_file = ARGV.first
def print_all(f)
puts f.read
end
def rewind(f)
f.seek(0)
end
def print_a_line(line_count, f)
puts "#{line_count}, #{f.gets.chomp}"
end
current_file = open(input_file)
puts "First let's print the whole file:\n"
print_all(current_file)
puts "Now let's rewind, kind of like a tape."
rewind(current_file)
puts "Let's print three lines:"
current_line = 1
print_a_line(current_line, current_file)
current_line = current_line + 1
print_a_line(current_line, current_file)
current_line = current_line + 1
print_a_line(current_line, current_file)
I deleted the test.txt file and recreated it from scratch and this made the code work perfectly. I am not 100% as to why, but I have a hunch that from reusing that file in many different tests and playing around in Powershell I had inadvertently left some strange formatting in the file causing it to behave differently.
I want to write a class, it can find a target string in a txt file and output the line number and the position.
class ReadFile
def find_string(filename, string)
line_num = 0
IO.readlines(filename).each do |line|
line_num += 1
if line.include?(string)
puts line_num
puts line.index(string)
end
end
end
end
a= ReadFile.new
a.find_string('test.txt', "abc")
If the txt file is very large(1 GB, 10GB ...), the performance of this method is very poor.
Is there the better solution?
Use foreach to efficiently read a single line from the file at a time and with_index to track the line number (0-based):
IO.foreach(filename).with_index do |line, index|
if found = line.index(string)
puts "#{index+1}, #{found+1}"
break # skip this if you want to find more than 1 result
end
end
See here for a good explanation of why readlines is giving you performance problems.
This is a variant of #PinnyM's answer. It uses find, which I think is more descriptive than looping and breaking, but does the same thing. This does have a small penalty of having to determine the offset into the line where the string begins after the line is found.
line, index = IO.foreach(filename).with_index.find { |line,index|
line.include?(string) }
if line
puts "'#{string}' found in line #{index}, " +
"beginning in column #{line.index(string)+1}"
else
puts "'#{string}' not found"
end
What does the (0, IO::SEEK_SET) do in method rewind? I understand the rest of the code, but I am honestly stuck at the (0, IO::SEEK_SET).
input_file = ARGV[0]
def print_all(f)
puts f.read()
end
def rewind(f)
f.seek(0, IO::SEEK_SET)
end
def print_a_line(line_count, f)
puts "#{line_count} #{f.readline()}"
end
current_file = File.open(input_file)
puts "First let's print the whole file:"
puts # a blank line
print_all(current_file)
puts "Now let's rewind, kind of like a tape."
rewind(current_file)
puts "Let's print three lines:"
current_line = 1
print_a_line(current_line, current_file)
current_line = current_line + 1
print_a_line(current_line, current_file)
current_line = current_line + 1
print_a_line(current_line, current_file)
seek(amount, whence=IO::SEEK_SET) → 0
Seeks to a given offset amount in the stream according to the value of whence:
:CUR or IO::SEEK_CUR | Seeks to _amount_ plus current position
----------------------+--------------------------------------------------
:END or IO::SEEK_END | Seeks to _amount_ plus end of stream (you
| probably want a negative value for _amount_)
----------------------+--------------------------------------------------
:SET or IO::SEEK_SET | Seeks to the absolute location given by _amount_
http://www.ruby-doc.org/core-2.1.2/IO.html#method-i-seek
When you specify a position to seek to, there are typically three ways to do this
SEEK_SET: from the start of the file (ie: absolute)
SEEK_CUR: from the current position (ie: relative)
SEEK_END: from the end of the file
So in your case the rewind method simply seeks to the beginning of the file.