The syntax about While loop in Ruby - ruby

I'm practicing the While Loop in Ruby and got a basic example as below
i = 3
while i > 0 do
print i
i -= 1
end
My question is why I can't interchange do..end with {} as if I rewrite the above code as below it doesn't work anymore
i = 3
while i > 0 {
print i
i -= 1
}
However, it seems to work without the first "do"
i = 3
while i > 0
print i
i -= 1
end
Could anyone explain the rule or redirect me to the right resource? Thx!

As you said do is optional for while loop. While keyword is enough to define a block which is finished with end like any other block in ruby. In addition, end is mandatory for while block.
If you want to use while on just one line you can do such as below:
i = 0
i += 1 while i < 10

While mandatory needs end in Ruby.
Syntax example
do is optional and can be omitted.
So, it is not the case where pair do - end can be replaced with {}

Related

How to create a custom while block

I know that in ruby we can use a while loop, but I want to know if I can create a custom one so I can make something like this:
custom_while i < 5 do
puts i
i += 1
end
I currently have this code:
def custom_while(condition)
loop do
break if not condition
yield
end
end
i = 0
custom_while i < 5 do
puts i
i += 1
end
However, when condition is evaluated, it always get true (because it considers the first evaluation of i < 5 = true only.
Any help will be appreciated!
Note: This is for educational purposes only.
You almost had it. So, your problem is that the condition is only evaluated once? Well, what construct do we know that we can evaluate as often as we want? That's right: functions! So, let's make condition a function (or a Proc in Ruby lingo):
def custom_while(condition)
loop do
break unless condition.()
yield
end
end
i = 0
custom_while -> { i < 5 } do
puts i
i += 1
end
# 0
# 1
# 2
# 3
# 4
This is unfortunately not as nice looking as in other languages. Ruby's syntax and semantics are aggressively optimized for methods that take exactly 1 "function" as an argument. Ruby has a special syntactically and semantically light-weight construct for that, namely blocks. As soon as you have more than one, though, you're out of luck.
Compare this with languages that have proper block literals, like Smalltalk, for example. In Smalltalk, you could write a method while:do:, and call it like this:
i := 0.
while: [i < 5] do: [Transcript write: i. i := i + 1].
In fact, in Smalltalk, the syntax for blocks is so lightweight that there are no control structures at all in the language. if/then/else is simply an instance method of Boolean, for example:
i % 2 == 0 ifTrue: [Transcript write: "even"] ifFalse: [Transcript write: "odd"].
And while is actually an instance method of Block, so in reality, your example would look like this:
i := 0.
[i < 5] whileTrue: [Transcript write: i. i := i + 1]
Note: I make no guarantees for the Smalltalk code, I didn't test it.
The problem is, the condition is being evaluated before it's passed in, so it will never change.
Make the condition a function that you evaluate inside the loop, or use a macro to make it cleaner.

longest_palindrome, expected 0, got nil, where should if-else statement?

Taking on the CodeWar Challenge. Struggling to fix my code so that if the length of the string input is 0, it would show 0 instead of nil.
Code is below: (suggestions?)
def longest_palindrome(string)
i = 0
a = []
while !string[i..-1].empty?
j = -1
while !string[i..j].empty?
s = string[i..j]
if s.reverse == s
a << s.length
if s.length == nil
a.max = 0
end
end
j -= 1
end
i += 1
end
a.max
end
First, I'd like to point out a couple of issues with the code you posted.
The body of the innermost if statement is never executed because
0 != nil
This means that even if s.length evaluates to zero
s.length == nil
will still be false.
Another issue I'd like to point out is that
a.max = 0
will throw an error that looks like this:
undefined method max=' for []:Array (repl):17:inlongest_palindrome'
(repl):1:in `initialize'
You can't set the max value directly. The reason you never run into this error with the code you posted is because of the first issue I outlined above.
Now to answer your question. There are a lot of ways to do what you are asking. For example, you could just check whether the input string is empty
at the beginning of the code and immediately return 0 if it is therefore
never executing the while loops at all. Maybe with something like
return 0 if string.empty?
at the beginning of the code.
But from your question, I think what you are looking is something more like the following:
def longest_palindrome(string)
i = 0
a = []
while !string[i..-1].empty?
j = -1
while !string[i..j].empty?
s = string[i..j]
if s.reverse == s
a << s.length
end
j -= 1
end
i += 1
end
a.max.to_i
end
Of interest here is the second last line which makes sure a.max is converted
to an integer using the to_i method. Calling this method on nil converts it to 0.
Also, please note I have changed the code to fix the issues I had highlighted earlier.

Ruby exercise using ARGV

I have to create the following pattern:
*
**
***
****
*****
Using a ARGV and this is what I have so far:
height = ARGV[0]
output = ""
height.to_i.times do |i|
output << "*" * i
output << "\n"
end
puts output
But every time I run ruby pyramid.rb 5 on my mac terminal, I get the following output:
$ ruby pyramid.rb 5
*
**
***
****
Why is it giving me 4 when it needs to have 5 of them?
Integer#times yields values from 0 to n-1, so you're getting lines of 0 up to 4 stars.
You mentioned this was an exercise, so I'll leave the fix up to you.
In general, to troubleshoot these kinds of problems, it's best to use a debugger or add diagnostic printing (e.g. puts statements) to trace the values of variables and ensure they're what you expected.
For example, in this case, you could add puts i inside your loop to see what the value of i is at every iteration. You would see something like 0 1 2 3 4 printed, which isn't what you expected. From there, you can look at your code, documentation, or add more diagnostic output to determine why you get those values.
I know this thread is old but I felt it's better late than never. Chris hinted at the solution. If i is starting from zero, you just have to make it so that i starts from 1 by simple adding i += 1. see example below:
height = ARGV[0]
output = ""
height.to_i.times do |i|
i += 1
output << "*" * i
output << "\n"
end
puts output
Analysis
The problem in your code is the use of Integer#times. Per the documentation, the #times method:
[i]terates the given block int times, passing in values from zero to int - 1.
So, here is your given code, with comments:
height.to_i.times do |i| # when `height = 5`, equivalent to `(0..4).each`
output << "*" * i # will be "" on the first pass when i == 0
output << "\n"
end
Solution
In this case, you should use the Integer#upto method instead of Integer#times, with 1 as your starting value. Consider the following example, rewritten as a one-liner you can run from the command prompt:
$ ruby -e '1.upto(Integer(ARGV[0])) { |i| puts "*" * i }' 5
*
**
***
****
*****
In addition to giving the expected results on standard output, it also does away with string mutation, declaring an output variable, and other non-essentials. The use of Kernel#Integer will also raise an exception if ARGV[0] can't be cast as an integer, rather introducing subtle bugs such as when ARGV[0] == "".

Ruby: enumerator can't be coerced to Fixnum; struggling with Project Euler #5

The challenge is to find the smallest integer foo for which:
foo % 1..20 == 0
My current attempt is brute force
until foo % 1.upto(20) == 0 do
foo++
end
This outputs the error unexpected keyword end. But if I don't put the end keyword into irb the code never runs, because the block isn't closed.
I made an empty test case to see where my error lays
until foo % 1.upto(20) == 0 do
end
This throws a new error: enumerator can't be coerced to a fixnum. I imagine this means you can't directly perform modulus upon a range and expect a neat boolean result for the whole range. But I don't know where to go from here.
My first attempts forewent brute force in favor of an attempt at something more efficient/elegant/to-the-point and were as follows:
foo = 1
1.upto(20) {|bar| foo *= bar unless foo % i == 0}
gave the wrong answer. I don't understand why, but I'm also interested in why
foo = 1
20.downto(1) {|bar| foo *= bar unless foo % i == 0}
outputs a different answer.
EDIT: I would have used for loops (I got my feet wet with programming in ActionScript) but they do not work how I expect in ruby.
Your first solution is wrong because 1.upto(20) is an enumerator, that is, essentially an iterator over the values 1 to 20, and it cannot be used as a number for modulo or comparison to another number, since it itself isn't a number.
You really need two "loops" here:
foo = 1
foo += 1 until (1..20).all? { |i| foo % i == 0 }
the first loop is the until, and then the all? is another loop of sorts, in that it ensures that the block ({ |i| foo % i == 0 }) is true for each element in the range it is called on ((1..20)). Note that I'm using the one-line "backwards" syntax (which also works for if, unless, while, …)—the above is equivalent to:
foo = 1
until (1..20).all? { |i| foo % i == 0 } do
foo += 1
end
# foo => 232792560
Also, this is incredibly inefficient, Project Euler often involves a bit more math than programming, and a non-brute-force solution will likely involve more math but be far faster.
Try this:
until 1.upto(20).reject{|i| foo % i == 0 }.empty? do
foo += 1
end
I know it's not directly the OP question, but this is way easier to achieve with just:
puts (1..20).reduce(:lcm)
It's so simple that it seems like isn't fair to solve it this way, but that's precisely why Ruby is my language of choice for Project Euler.
See also this question
If this were me, I'd define a function to test the condition:
def mod_test(num)
test = (1..20).map {|i| num % i == 0}
test.all? # all are true
end
and then a loop to try different values:
foo = 20
until mod_test(foo) do
foo += 20
end
(Thanks to Dylan for the += 20 speedup.)
I'm sure that there's a clever way to use the knowledge of foo % 10 == 0 to also imply that foo % 5 == 0 and foo % 2 == 0, and perform only tests on prime numbers between 1 and 20, and probably even use that information to construct the number directly -- but my code ran quickly enough.

Equivalent of "continue" in Ruby

In C and many other languages, there is a continue keyword that, when used inside of a loop, jumps to the next iteration of the loop. Is there any equivalent of this continue keyword in Ruby?
Yes, it's called next.
for i in 0..5
if i < 2
next
end
puts "Value of local variable is #{i}"
end
This outputs the following:
Value of local variable is 2
Value of local variable is 3
Value of local variable is 4
Value of local variable is 5
=> 0..5
next
also, look at redo which redoes the current iteration.
Writing Ian Purton's answer in a slightly more idiomatic way:
(1..5).each do |x|
next if x < 2
puts x
end
Prints:
2
3
4
5
Inside for-loops and iterator methods like each and map the next keyword in ruby will have the effect of jumping to the next iteration of the loop (same as continue in C).
However what it actually does is just to return from the current block. So you can use it with any method that takes a block - even if it has nothing to do with iteration.
Ruby has two other loop/iteration control keywords: redo and retry.
Read more about them, and the difference between them, at Ruby QuickTips.
I think it is called next.
Use next, it will bypass that condition and rest of the code will work.
Below i have provided the Full script and out put
class TestBreak
puts " Enter the nmber"
no= gets.to_i
for i in 1..no
if(i==5)
next
else
puts i
end
end
end
obj=TestBreak.new()
Output:
Enter the nmber
10
1
2
3
4
6
7
8
9
10
Use may use next conditionally
before = 0
"0;1;2;3".split(";").each.with_index do |now, i|
next if i < 1
puts "before it was #{before}, now it is #{now}"
before = now
end
output:
before it was 0, now it is 1
before it was 1, now it is 2
before it was 2, now it is 3

Resources