2

How do I do scan a file after finding a particular line. The first results do not count. For example (search for 3s after finding a line that says "begin here"):

Sample file
1
3
3
4
begin here
2
3
3
1
4

The expected output would be two 3s

Flethuseo
  • 5,969
  • 11
  • 47
  • 71
  • Can you show us what you tried? It's better for us to modify/fix your code than to write something from scratch that has no bearing on what you've already written, and you have to shoehorn it into place. – the Tin Man Oct 31 '14 at 00:01
  • Take a look at Ruby's `..` (AKA flip-flop) operator. "[What is a flip-flop operator?](http://stackoverflow.com/questions/14456634/what-is-a-flip-flop-operator)" is your friend. – the Tin Man Oct 31 '14 at 00:08
  • I realized the original question wasn't very clear. Honestly I wanted to "scan" after a certain point in the file, without using the entire text file. I wasn't very clear about that. – Flethuseo Oct 31 '14 at 12:39
  • Text files generally aren't treated as random access, unless every line is a fixed length. Instead, they have to be scanned from start to finish in order to read each line and find its end, thereby finding the beginning of the next line. So, you can't scan after a certain point without using the entire file. But how it's read is really a moot point, because line-by-line IO is extremely fast. – the Tin Man Oct 31 '14 at 16:31

2 Answers2

4

Ruby's .. operator is your friend:

DATA.each_line do |line|
  line.chomp!
  next unless (line =~ /^begin here$/) .. false
  puts line if line == '3'
end
__END__
1
3
3
4
begin here
2
3
3
1
4

Save that to a file and run it and you'll see:

3
3

.. (and the very much more obscure ...) come from Perl, and are very useful for just this particular kind of use.

What happens is .. is a two-state operator that works with if or unless to look for a first condition which is line =~ /^begin here$/ in this case. When that condition is met, the operator sticks, i.e., returns true until the second test matches, (returns true). Once that happens, the flip-flop then starts returning false again.

In this code, I'm fooling it into always returning true, because what would be the second test is always false. In other words, the code then reads to the end of the data.

.. (and ...) are REALLY useful when you're scanning a file, looking for blocks of data that occur throughout the file. If the second conditional test found the end of the block, .. would reset and the code would skip through the file until it found the next block start, triggered, and started capturing again.


It's also likely that most people haven't seen __END__ or DATA used. __END__ is a way to tell Ruby that there is no additional code to be executed beyond that point. DATA is an internal file handle that points to the lines following __END__ and can be treated similarly to IO and File objects. It's really useful for supplying data you must have with a script, but don't necessarily want to put into a separate file. In this case I'm pretending that the lines after __END__ are a separate file. Easy-peasy.

the Tin Man
  • 158,662
  • 42
  • 215
  • 303
1

I would write it like this.

Code

def put3s(fname)
  e = IO.foreach(FName)
  until e.next.strip == "begin here"; end
  loop { puts "3" if e.next.strip == "3" }
end

Note that IO#foreach without a block returns an enumerator. foreach is your best friend.

After the last element of the enumerator has been reached, e.next raises a StopIteration exception. Kernel#loop handles the exception by breaking out of the loop.

Example

We first create a file:

text =<<_
1
3
3
4
begin here
2
3
3
1
4
_

FName = "t"
File.write(FName, text)

and then execute the method

put3s(FName)
  # 3
  # 3
Cary Swoveland
  • 106,649
  • 6
  • 63
  • 100