Printing out one line of a file in a Ruby script - ruby

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

Passing an gets.chomp as an argument

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.

undefined method `chomp' for nil:NilClass (NoMethodError)

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

LRTHW exercise 20 strange output

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.

find the target string from a large file

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 IO do?

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.

Resources