How to stop outer block from inner block - ruby

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

Related

.include? is not triggered when reading a txt file

I am under the impression that the following code should return either the search item word or the message no match - as indicated by the ternary operator. I can't diagnose where/why include? method doesn't work.
class Foo
def initialize(word)
#word=word
end
def file_open
IO.foreach('some_file.txt') do |line|
line.include?(#word) ? "#{#word}" : "no match"
end
end
end
print "search for: "
input = gets.chomp.downcase
x = Foo.new(input)
puts x.file_open
The input exists in some_file.txt. My ternary operator syntax is also correct. IO reads the text fine (I also tried File.open() and had the same problem). So I must be making a mistake with my include? method.
You need to control the returned value. file_open defined above will always return nil. The ternary will be executed properly, but nothing is done with its value. Instead you can do the following:
class Foo
def initialize(word)
#word=word
end
def file_open
IO.foreach('some_file.txt') do |line|
return line if line.include?(#word)
end
return "no match"
end
end

How to convert this if condition to unless?

if array.present?
puts "hello"
end
There is no else part to this.
How to write the above if condition using unless.
I'm asking this question because of this lint error:
Use a guard clause instead of wrapping the code inside a conditional expression
Regarding your comment:
I'm asking this question because of this lint error
Use a guard clause instead of wrapping the code inside a conditional expression
This means that instead of:
def foo(array)
if array.present?
puts "hello"
end
end
You are supposed to use:
def foo(array)
return unless array.present?
puts "hello"
end
See https://github.com/bbatsov/ruby-style-guide#no-nested-conditionals
If this is a Rails question (is it?), you can also use blank?:
def foo(array)
return if array.blank?
puts "hello"
end
There's no reason to.
Remember: unless is the inverse of if (or !if if you rather), and is only intended to make your code easier to read.
Using unless with your expression would be incredibly awkward, because you're now moving the actual body of work to an else statement...
unless array.present?
return
else
puts "hello"
end
...which doesn't make your code any easier to read if you had stuck with a negated if:
if !array.present?
return
else
puts "hello"
end
Don't use unless here. You lose readability in exchange for virtually nothing.
One-liner:
puts "hello" unless !array.present?
However, I would recommend:
puts "hello" if array.present?
unless array.present?
return
else
puts "hello"
end
OP requested one-liner modification:
Pseudocode:
something unless condition
Therefore:
puts "hello" unless !array.present?

Repeating a block of code until the block itself returns false?

I want to:
pass a block to a method call, and then
pass that entire method call as the condition of a while loop,
even though I don't need to put any logic inside the loop itself.
Specifically, I have an array that I'd like to #reject! certain elements from based on rather complicated logic. Subsequent calls to #reject! may remove elements that were not removed on a previous pass. When #reject! finally stops finding elements to reject, it will return nil. At this point, I would like the loop to stop and the program to proceed.
I thought I could do the following:
while array.reject! do |element|
...
end
end
I haven't actually tried it yet, but this construction throws vim's ruby syntax highlighter for a loop (i.e., it thinks the first do is for the while statement, and thinks the second end is actually the end of the encapsulating method). I also tried rewriting this as an inline while modifier attached to a begin...end block,
begin; end while array.reject! do |element|
...
end
but it still screws up the highlighting in the same way. In any case, it feels like an abuse of the while loop.
The only way I could think of to accomplish this is by assigning the method call as a proc:
proc = Proc.new do
array.reject! do |element|
...
end
end
while proc.call do; end
which works but feels kludgy, especially with the trailing do; end.
Is there any elegant way to accomplish this??
It's not just vim, while array.reject! do |element| is invalid syntax:
$ ruby -c -e 'while array.reject! do |element| end'
-e:1: syntax error, unexpected '|'
while array.reject! do |element| end
^
You could use { ... } instead of do ... end:
while array.reject! { |element|
# ...
}
end
or loop and break:
loop do
break unless array.reject! do |element|
# ...
end
end
a little more explicit:
loop do
r = array.reject! do |element|
# ...
end
break unless r
end
Ruby lets you move your condition to the end of the loop statement. This makes it easy to store a result inside of the loop and check it against the conditional:
begin
any_rejected = arr.reject! { … }
end while any_rejected
This would work the same as doing end while arr.reject! { … }, but it's much clearer here what's happening, especially with a complicated reject!.
You're right that the Ruby parser thinks that do belongs to while, and doesn't understand where the second end is coming from. It's a precedence problem.
This code is just to show that it can be done. For how it should be done, see Stefan's answer :
array = (1..1000).to_a
while (array.reject! do |element|
rand < 0.5
end)
p array.size
end
It outputs :
473
238
113
47
30
18
8
1
0
My personal preference in situations where I need to call a method until the return value is what I want is:
:keep_going while my_method
Or more tersely I sometimes use:
:go while my_method
It's one line, and you can use the contents of the symbol to help document what's going on. With your block, I'd personally create a proc/lambda out of it and pass that to reject for clarity.
# Harder to follow, IMHO
:keep_going while array.reject! do |...|
more_code
end
# Easier to follow, IMHO
simplify = ->(...){ ... }
:keep_simplifying while array.reject!(&simplify)

Elegant way to loop in ascending and descending order

I have the following code that parses HTML text and trims (or strips) the paragraphs that are empty. It's similar to .strip on a String object.
doc = Nokogiri::HTML::DocumentFragment.parse(html)
# repetition that I want to collapse
doc.css('p').each do |p|
if all_children_are_blank?(p)
p.remove
else
break
end
end
# repetition that I want to collapse
doc.css('p').reverse_each do |p|
if all_children_are_blank?(p)
p.remove
else
break
end
end
doc.to_s.strip
Is there a more elegant way to prevent code that I've labelled with comments to be duplicated and adhere to principles of code-reuse?
Here is what I've come up with but I'm not happy with it yet and wanted to see if there is something better:
doc = Nokogiri::HTML::DocumentFragment.parse(html)
doc.css('p').each do |p|
if stop(p) then break end
end
doc.css('p').reverse_each do |p|
if stop(p) then break end
end
doc.to_s.strip
def self.stop(p)
if all_children_are_blank?(p)
p.remove
false
else
true
end
end
If I understand what you're looking for, you would like a simpler way to iterate over the elements you're looking at, in order to remove blank p elements.
Here is a straightforward way to collapse what you've written, without doing a whole lot different:
doc.tap do |d|
[:each, :reverse_each].each do |sym|
d.css("p").public_send(sym) do |p|
if blank_children?(p)
p.remove
else
break
end
end
end
end.to_s.strip
I have not tested this out, so you might need to tweak it a little. If this were production code, I would probably decompose it into one or more method calls in order to keep things clear.
Maybe something like:
puts "removing a top p" until stop(doc.at('p'))
puts "removing a bottom p" until stop(doc.search('p').last)
or just:
puts "removing a p" until stop(doc.at('p')) && stop(doc.search('p').last)
How about:
[*doc.css('p'), *doc.css('p').reverse].each do |p|
if stop(p) then break end
end
In this case, the splat operator ("*") expands both lists into one array, with the elements in ascending, then descending order. Then you just iterate over the whole group.
Edit:
This won't work properly because of the break statement skipping to the end of everything. So the proper way of doing this, IMHO, would be to assign the block to a variable. And you might as well eliminate the stop function since you are eliminating the duplication of code anyway:
remover = lambda do |p|
if all_children_are_blank? p
p.remove
else
break
end
end
doc.css('p').to_a.each(&remover).reverse_each(&remover)
Hope this helps.

Case Statements in Ruby (line.scan)

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

Resources