Different ways of defining the conditions in a while loop. (Ruby) - ruby

I wrote a function which works. There is one part in there and it can be written in an alternative manner, but the alternative way of writing it doesn't work, although it should represent the same thing as the original code I wrote. I'd like to know why the alternative way of writing it does not work. When I ran it with the alternative codes, I think it became an infinite loop because there were no outputs.
This is a snippet of the function (The code in question is commented as "#ALTERNATIVE", there are four lines in total):
if idx2 > idx
idx3 = idx2 - 1
while idx3 >= idx #ALTERNATIVE 1: idx3 > idx
if arr[idx3] > arr[idx]
return idx3
elsif idx3 == idx #ALTERNATIVE 1: idx3 == idx + 1 or idx3 - 1 == idx
return idx2
end
idx3 -= 1
end
elsif idx > idx2
idx4 = idx2 + 1
while idx4 <= idx #ALTERNATIVE 2: idx4 < idx
if arr[idx4] > arr[idx]
return idx4
elsif idx4 == idx #ALTERNATIVE 2: idx4 == idx - 1 or idx4 + 1 == idx
return idx2
end
idx4 += 1
end
end

Your alternate code doesn't work in the cases where idx and idx2 are within 1 of each other.
Imagine idx == 1 and idx2 == 2.
if idx2 > idx
idx3 = idx2 - 1 # idx3 is equal to 1 and therefore equal to `idx`
while idx3 > idx # this is false, the while loop is not run
And nothing is returned. The same type of situation would be true for the elsif clause if idx == 2 and idx2 == 1 where the while loop would never run.

I don't know why the alternative code doesn't work, perhaps you've put in an idx2 equal to idx and it exited.
However, I thought it might be prudent to show you how to write this code more idiomatically:
def furthest_number_higher_than_base(arr, base, start)
range = start < base ? start..base : (base..start).to_a.reverse
range.each { |i| return i if arr[i] > arr[base] }
start
end

Related

Nuances of where to define a variable in ruby code

I've just started learning ruby, and the position of where variables are defined somewhat elude me. For example, why does this code work:
def two_sum(nums)
result = nil
i = 0
while i < nums.length
k = (nums.length - 1)
if nums[i] + nums[k] == 0
result = [i,k]
end
i += 1
k -= 1
end
return result
end
And why does this code not work:
def two_sum(nums)
result = nil
i = 0
k = (nums.length - 1)
while i < nums.length
if nums[i] + nums[k] == 0
result = [i,k]
end
i += 1
k -= 1
end
return result
end
Thank you in advance!
I think you code might just have a bug
while i < nums.length
k = (nums.length - 1)
...
k -= 1 # this statement has no effect!
end
Above, the value if k is always (nums.length - 1) because you reassign it at the begin of each iteration. The other statement has no effect.
k = (nums.length - 1)
while i < nums.length
...
k -= 1
end
Above, the value of k starts at (nums.length - 1) in the first iteration and is then reduced by 1 for each iteration.
Pro tipp —
It is very unusual in Ruby to use a for/while/until loop. If you want to loop over all elements use each or each_with_index instead
array.each { |each| ... }
array.each_with_index { |each, n| ... }

Placement of variable declaration in Ruby (beginner)

I'm working through some online practice problems to learn Ruby, and while I was able to solve this one, I'm struggling to understand something about the placement of variable declaration.
Why does the following code work when I declare/define "pair" (sum of array indices) inside the idx2 while-loop, but not when I do so directly after declaring the indices themselves?
def two_sum(nums)
idx=0
idx2=0
while idx<nums.length-1
idx2=idx+1
while idx2<nums.length
pair=nums[idx]+nums[idx2]
if pair==0
return [idx, idx2]
else
idx2+=1
end
end
idx+=1
end
end
The version above works, but the structure below does not.
def two_sum(nums)
idx=0
idx2=0
pair=nums[idx]+nums[idx2]
while idx<nums.length-1
idx2=idx+1
while idx2<nums.length
if pair==0
return [idx, idx2]
else
idx2+=1
end
end
idx+=1
end
end
If anyone can provide an explanation or some entry-level resources about this, I'd greatly appreciate it. Thanks.
Edit:
Sorry for indentation and specificity issues regarding desired output. I'm entirely new to this and sometimes details slip by, I appreciate the feedback. Thanks for the step-by-step logical run through, that's exactly what I was looking for.
Let's look at the working code and the broken code one at a time.
Let's say nums = [1, 2, -1, 3]
Supposably Working Code
1 def two_sum(nums)
2 idx = 0
3 idx2 = 0
4
5 while idx < nums.length-1
6 idx2 = idx+1
7 while idx2 < nums.length
8 pair = nums[idx] + nums[idx2]
9 if pair == 0
10 return [idx, idx2]
11 else
12 idx2 += 1
13 end
14 end
15 idx += 1
16 end
17 end
When we start off
idx = 0, idx2 = 0
Now we are in the first while loop (line 6)
idx = 0, idx2 = 1
2nd while loop (line 8)
idx = 0, idx2 = 1, pair = nums[0] + nums[1] = 3
pair != 0, so we are now in the else statement. (line 12)
idx = 0, idx2 = 2, pair is still 3
We now loop back to the 2nd while loop (line 8 again)
idx = 0, idx2 = 2, pair = nums[0] + nums[2] = 0
pair == 0, so we return [0, 2] (line 10)
This is how the code is suppose to run.
Broken Code
1 def two_sum(nums)
2 idx = 0
3 idx2 = 0
4 pair = nums[idx] + nums[idx2]
5 while idx < nums.length-1
6 idx2 = idx+1
7 while idx2 < nums.length
8
9 if pair == 0
10 return [idx, idx2]
11 else
12 idx2 += 1
13 end
14 end
15 idx += 1
16 end
17 end
We will run through the same logic again.
Before we hit the first while loop (line 2 - 4)
idx = 0, idx2 = 0, pair = nums[0] + nums[0] = 2
Now we are in the first while loop, (line 6)
idx = 0, idx2 = 1, pair = 2
And now in second while loop, pair != 0, so we hit the else statement, (line 12)
idx = 0, idx2 = 2, pair = 2
Now we loop back to the start of the second while loop. pair != 2, so we hit the else statement.
idx = 0, idx2 = 3, pair = 2
pair != 0, so we hit the else statement.
idx = 0, idx2 = 4, pair = 2
So now, idx2 < nums.length is false, so we exit the second while loop, and nowidx = 1`, and we continue to cycle.
As you can see, because you declared pair outside of the while loop, it never recalculates to different values, so you will never hit the if statement, because pair will never equal zero, unless of course, you get 0 on the initial sum.
while loops are meant for cases where you are not sure how many loops you need. An example is checking a user's entry, where you may need to loop once, or ten times, depending on when the user gets the entry correct. In this case, you know exactly how many times you need to run. So, an iterator is better for the job here.
Here's an example of how that can be written. Keep in mind there's probably better ways to do this, I just want to show an example.
def two_sum(nums)
nums.each_with_index do |num1, idx1|
nums.each_with_index do |num2, idx2|
next if idx1 == idx2
return [idx1, idx2] if num1 + num2 == 0
end
end
end

Ruby longest palindrome - why does a slight modification in a while loop break my code?

I'm trying to solve this Ruby problem and I can't figure out why having a minor while loop difference renders one test false: longest_palindrome("abba") outputs "bb" instead of "abba", which is false. I can only solve it with for and while loops, so please no advanced methods. It's easier to highlight the difference in the code block (first one is the working solution, second is mine. Also assume the palindrome? method is already defined):
def longest_palindrome(string)
best_palindrome = nil
idx1 = 0
***while idx1 < string.length
length = 1
while (idx1 + length) <= string.length
substring = string.slice(idx1, length)***
if palindrome?(substring) && (best_palindrome == nil || substring.length > best_palindrome.length)
best_palindrome = substring
end
length += 1
end
idx1 += 1
end
return best_palindrome
end
def longest_palindrome(string)
longest = nil
i = 0
***while i < string.length
i2 = 1
while i2 < string.length***
if palindrome?(string.slice(i, i2)) == true && (longest == nil || string.slice(i, i2).length > longest.length)
longest = string.slice(i, i2)
end
i2 += 1
end
i += 1
end
return longest
end
This part of your code...
while i2 < string.length
... means you're never checking the maximum possible length.
"abba".slice(0,4) is the entire string, but you only ever go up to "abba".slice(0,3) which is "abb".
So you never test the entire string.
Change the line to...
while i2 <= string.length
...and it should be ok.

Ruby's 'next' keyword

Simple question for the following set of codes:
def nearby_az(string)
idx1 = 0
while idx1 < string.length
if string[idx1] != "a"
idx1 += 1
next #<--------------
end
idx2 = idx1 + 1
while (idx2 < string.length) && (idx2 <= idx1 + 3)
if string[idx2] == "z"
return true
end
idx2 += 1
end
idx1 += 1
end
return false
end
What does the word "next" do in the line with the commented arrow? And is "next" a method? If not, what's the correct technical jargon for this? Cheers
next is not a method, it's a keyword. It applies to the outer while loop in your example, it stops current iteration and 'calls' next one.

Please explain this Ruby method that returns number of repeat letters

Can anybody please explain this code? I don't understand specifically:
elsif idx2 > idx1
is_repeat = true
end
Why are we comparing indices to determine if a letter has been
repeated?
Also what does the 'next' term do inside of the if statement?
The full code is shown below:
# Write a method that takes in a string and returns the number of
# letters that appear more than once in the string. You may assume
# the string contains only lowercase letters. Count the number of
# letters that repeat, not the number of times they repeat in the
# string.
#
# Difficulty: hard.
def num_repeats(array)
repeats = 0
idx1 = 0
while idx1 < array.length
is_repeat = false
idx2 = 0
while idx2 < array.length
if array[idx1] != array[idx2]
idx2 += 1
next
elsif idx2 < idx1
# will have previously counted this repeat
break
elsif idx2 > idx1
is_repeat = true
end
idx2 += 1
end
if is_repeat
repeats += 1
end
idx1 += 1
end
return repeats
end
If both conditions before (if idx2 < array.length and elsif idx2 < idx1) are false but idx2 > idx1 is true, than set the local variable is_repeat to true...

Resources