views:

154

answers:

3

I am a Java/C++ programmer and Ruby is my first scripting language. I sometimes find that I am not using it as productively as I could in some areas, like this one for example:

Objective: to parse only certain lines from a file. The pattern I am going with is that there is one very large line with a size greater than 15, the rest are definitely smaller. I want to ignore all the lines before (and including) the large one.

def do_something(str)
   puts str
end


str = 
'ignore me
me too!
LARGE LINE ahahahahha its a line!
target1
target2
target3'

flag1 = nil
str.each_line do |line|
  do_something(line) if flag1
  flag1 = 1 if line.size > 15
end

I wrote this, but I think it could be written a lot better, ie, there must be a better way than setting a flag. Recommendations for how to write beautiful lines of Ruby also welcome.

Note/Clarification: I need to print ALL lines AFTER the first appearance of the LARGE LINE.

+3  A: 
require 'enumerator' # Not needed in Ruby 1.9

str.each_line.inject( false ) do |flag, line|
  do_something( line ) if flag
  flag || line.size > 15
end
Farrel
+1  A: 
lines = str.split($/)
start_index = 1 + lines.find_index {|l| l.size > 15 }
lines[start_index..-1].each do |l|
  do_something(l)
end
mckeed
does this work in 1.8.6 ? I get undefined method 'lines' error
Zombies
No, lines is new in 1.8.7. I'll update the answer to use split.
mckeed
In 1.8.6, strings are already considered an array of lines, therefore you don't need it. In 1.9, however, strings are considered an array of either lines, characters, bytes or codepoints, therefore you have to explicitly say which behavior you want.
Jörg W Mittag
Ah, well mckeed, I'll take both answers! Thanks.
Zombies
Jörg: In 1.8.6 they are only considered lines for the purposes of enumeration, the slicing wouldn't work without converting to an array of lines.
mckeed
Marc-André Lafortune
+10  A: 
str.lines.drop_while {|l| l.length < 15 }.drop(1).each {|l| do_something(l) }

I like this, because if you read it from left to right, it reads almost exactly like your original description:

Split the string in lines and drop lines shorter than 15 characters. Then drop another line (i.e. the first one with more than 14 characters). Then do something with each remaining line.

You don't even need to necessarily understand Ruby, or programming at all to be able to verify whether this is correct.

Jörg W Mittag
You can get rid of that extra drop if you modify the condition to <=. Never knew about drop_while. Thanks!
Farrel
@Farrel: No you can't. If the long line has more than 15 chars, it will act exactly as before. If it has exactly 15 chars, it will just drop the whole string.
sepp2k
Farrel: If the long line is more than 15 characters long (like in the example), you'll still need the extra drop. Also, I must concur that drop_while is pretty neat.
abeger
Ah I see I thought 'flag line' would always be 15 char. Yes then < is correct.
Farrel
I like this answer. It should be noted that you don't need to read the file into a string. You can replace str.lines with the file object, which won't use too much memory if the file is very big.Also, as a style issue, I think the second block should use do...end notation, as it is more of a declarative effect. I generally use {} when the result of the block is being used and do...end when the block is doing something.
mckeed
Nice answer. Note that drop_while is available in 1.8.7+ only, so in 1.8.6: require "backports"
Marc-André Lafortune