Ruby - App Academy Practice Exercise About Condition in While Loop - ruby

I'm completing App Academy's practice problems for the first coding challenge and have a question regarding the solution provided for #8 nearby az:
# Write a method that takes a string in and returns true if the letter
# "z" appears within three letters **after** an "a". You may assume
# that the string contains only lowercase letters.
#
# Difficulty: medium.
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
# These are tests to check that your code is working. After writing
# your solution, they should all print true.
puts("\nTests for #nearby_az")
puts("===============================================")
puts('nearby_az("baz") == true: ' + (nearby_az('baz') == true).to_s)
puts('nearby_az("abz") == true: ' + (nearby_az('abz') == true).to_s)
puts('nearby_az("abcz") == true: ' + (nearby_az('abcz') == true).to_s)
puts('nearby_az("a") == false: ' + (nearby_az('a') == false).to_s)
puts('nearby_az("z") == false: ' + (nearby_az('z') == false).to_s)
puts('nearby_az("za") == false: ' + (nearby_az('za') == false).to_s)
puts("===============================================")
In the second while loop:
while (idx2 < string.length) && (idx2 <= idx1 + 3)
why is the condition (idx2 < string.length) necessary? I tested the code without it and got the same results.
Thank you for your assistance.

why is the condition (idx2 < string.length) necessary?
It is not necessary. It's a guard to prevent meaningless iterations of the loop, when idx2 goes out of bounds of the string.
Addressing characters at position beyond string's length will return nil. nil will never be equal to 'z'. So we might as well just stop when we reach the end. This is what the check is for here, optimization.
In other situations, out-of-bounds access often is a serious offense and leads to all kinds of problems (crashes, usually). So it makes sense to always do this.

I know this does not answer your exact question, and other people have answered it already, but as the usual case with programming, there's a better way. You can easily solve this with regular expression
def nearby_az(string)
!(string =~ /a\w{0,3}z/).nil?
end
The regex will match the pattern a, with 0 to 3 characters after it, then a z. If this matches nothing, the =~ operator will return nil, so nil? method returns true, meaning the string does not have nearby az, so we use the ! to invert the boolean, and this method will return false.
If there is a match, =~ returns the index of the first characters, which is not nil, so nil? return false, and we inverted it as before to return true.
Just thought this may be helpful.

Related

How to write nested if/then statements in Ruby

I'm supposed to
define a method, three_digit_format(n), that accepts an integer, n, as an argument. Assume that n < 1000. Your method should return a string version of n, but with leading zeros such that the string is always 3 characters long.
I have been tinkering with versions of the below code, but I always get errors. Can anyone advise?
def three_digit_format(n)
stringed = n.to_s
stringed.size
if stringed.size > 2
return stringed
end
elsif stringed > 1
return "0" + stringed
end
else
return "00" + stringed
end
end
puts three_digit_format(9)
rjust
You could just use rjust:
n.to_s.rjust(3, '0')
If integer is greater than the length of str, returns a new String of
length integer with str right justified and padded with padstr;
otherwise, returns str.
Your code
Problem
If you let your text editor indents your code, you can notice there's something wrong:
def three_digit_format(n)
stringed = n.to_s
stringed.size
if stringed.size > 2
return stringed
end
elsif stringed > 1 # <- elsif shouldn't be here
return "0" + stringed
end
else
return "00" + stringed
end
end
puts three_digit_format(9)
Solution
if, elsif and else belong to the same expression : there should only be one end at the end of the expression, not for each statement.
def three_digit_format(n)
stringed = n.to_s
if stringed.size > 2
return stringed
elsif stringed.size > 1
return "0" + stringed
else
return "00" + stringed
end
end
puts three_digit_format(9)
# 009
This function, as some have pointed out, is entirely pointless since there's several built-in ways of doing this. Here's the most concise:
def three_digit_format(n)
'%03d' % n
end
Exercises that force you to re-invent tools just drive me up the wall. That's not what programming is about. Learning to be an effective programmer means knowing when you have a tool at hand that can do the job, when you need to use several tools in conjunction, or when you have no choice but to make your own tool. Too many programmers jump immediately to writing their own tools and overlook more elegant solutions.
If you're committed to that sort of approach due to academic constraints, why not this?
def three_digit_format(n)
v = n.to_s
while (v.length < 3)
v = '0' + v
end
v
end
Or something like this?
def three_digit_format(n)
(n + 1000).to_s[1,3]
end
Where in that case values of the form 0-999 will be rendered as "1000"-"1999" and you can just trim off the last three characters.
Since these exercises are often absurd, why not take this to the limit of absurdity?
def three_digit_format(n)
loop do
v = Array.new(3) { (rand(10) + '0'.ord).chr }.join('')
return v if (v.to_i == n)
end
end
If you're teaching things about if statements and how to append elsif clauses, it makes sense to present those in a meaningful context, not something contrived like this. For example:
if (customer.exists? and !customer.on_fire?)
puts('Welcome back!')
elsif (!customer.exists?)
puts('You look new here, welcome!')
else
puts('I smell burning.')
end
There's so many ways a chain of if statements is unavoidable, it's how business logic ends up being implemented. Using them in inappropriate situations is how code ends up ugly and Rubocop or Code Climate give you a failing grade.
As others have pointed out, rjust and applying a format '%03d' % n are built in ways to do it.
But if you have to stick to what you've learned so far, I wonder if you've been introduced to the case statement?
def three_digit_format(n)
case n
when 0..9
return "00#{n}"
when 10..99
return "0#{n}"
when 100..999
return "#{n}"
end
end
I think it's cleaner than successive if statements.
Here's my spin on it:
def three_digit_format(n)
str = n.to_s
str_len = str.length
retval = if str_len > 2
str
elsif str_len > 1
'0' + str
else
'00' + str
end
retval
end
three_digit_format(1) # => "001"
three_digit_format(12) # => "012"
three_digit_format(123) # => "123"
Which can be reduced to:
def three_digit_format(n)
str = n.to_s
str_len = str.length
if str_len > 2
str
elsif str_len > 1
'0' + str
else
'00' + str
end
end
The way it should be done is by taking advantage of String formats:
'%03d' % 1 # => "001"
'%03d' % 12 # => "012"
'%03d' % 123 # => "123"

using the case statement to find an index and return it

After playing about with this all morning(i am still new to this), I have decided to ask the experts.
This is the quest:
You are going to be given a word.
Your job is to return the middle character of the word.
If the word's length is odd, return the middle character.
If the word's length is even, return the middle 2 characters.
This is what I have so far:
def median(string)
array = string.split(//)
case array
when array.length == 1
return array[0]
when array.length == 2
return array[0] + array[1]
when array.length.odd? && array.length >= 3
return array[(array.length - 1) / 2]
when array.length.even? && array.length >= 4
return array[((array.length / 2 ) - 1)] + array[(array.length / 2)]
else nil
end
end
puts median("testing")
what is wrong with my code. It runs but delivers nothing. Any help hugely appreciated.
You need to remove array from the line case array. I only did this change to your code and it works just fine:
def median(string)
array = string.split(//)
case
when array.length == 1
return array[0]
when array.length == 2
return array[0] + array[1]
when array.length.odd? && array.length >= 3
return array[(array.length - 1) / 2]
when array.length.even? && array.length >= 4
return array[((array.length / 2 ) - 1)] + array[(array.length / 2)]
else nil
end
end
In the initial version of the code, having case array meant that on each branch Ruby would compare the "when" value (which is always a boolean value) to array, which is never boolean, therefore never equal to any of the branches. this is why the else nil branch was always the exit point of the case block.
Changing case array to case will tell Ruby that it does not need to performa comparison, but to evaluate each branch condition and execute the first one that is true.
This being said, your code can be simplified in a few ways:
there's no need to convert the string to an array; string indexing would work just fine in this case
the conditions && array.length >= 3 and && array.length >= 4 are superfluous; a lower value would hit the array.length == 1 or array.length == 2 branches
the array.length == 1 or array.length == 2 branches are really not that special; you can just treat them in the odd/even length cases.
Applying these considerations I ended up with this code, which also works OK:
def word_median(word)
half = word.length / 2
case
when word == '' then ''
when word.length.odd? then word[half]
else word[half - 1..half]
end
end

why does this ruby code not return anything?

I'm trying to take a number and return a string with dashes around any odd numbers. Also, the string should not begin or end with a dash.
I've written the following but it does not return anything:
def dasherize_number(num)
string = num.to_s
i = 0
while i<string.length
if (string[i].to_i % 2) != 0
string[i] = '-' + string[i] + '-'
end
i += 1
end
if string[0] == '-'
string.pop(1)
end
if (string.length - 1) == '-'
string.pop(1)
end
string
end
It appears to be looping infinitely if I understand correctly; the console shows no output and doesn't allow me to do anything else unless I refresh. I've reviewed the code by each character, but I can't figure where it goes wrong.
There were a lot of logical issues in your code.
here's something that might just work for you
def dasherize_number(num)
string = num.to_s
str_len = string.length
i = 0
while i < str_len
next if string[i] == '-'
if (string[i].to_i % 2) != 0
string[i] = '-' + string[i] + '-'
str_len = string.length
i += 3
else
i += 1
end
end
if string[0] == '-'
string = string[1..-1]
end
if (string[string.length - 1]) == '-'
string = string[0..-2]
end
string.gsub('--', '-')
end
Explaination
Firstly, you had this condition in your while loop i < string.length
Which wouldn't work, because the length of the string keeps changing. So i've used a variable to store the value and update the variable if the string is updated.
If the string is updated, we can be sure that we can skip the next two indexes.
eg: number inputed -> 122
then after first iteration the string would be -1-22
so we don't want to run the same condition for the next index because, that would be 1 again, hence the infinite loop. (Hope you get the idea)
pop wouldn't work on string, just because we can access characters using indexes like for arrays, we can't use pop for strings.
To make sure there are no consecutive dashes, i've used gsub to replace them with single dash.
The problem seems to be in this part of the code:
while i<string.length
if (string[i].to_i % 2) != 0
string[i] = '-' + string[i] + '-'
end
i += 1
end
If your string contains odd number it increases its length by 2 more chars (2x-), but incrementing it by 1 (i+=1).
Assign initial string length to a var and check its length in the while loop.
string_length = string.length
while i < string_length
if ((string[i].to_i % 2) != 0)
string[i] = '-' + string[i] + '-'
end
i += 1
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: Create a symbol searching method

So this is supposed to be a method that tells you if there is a + symbol before AND after every letter (a-z). Can't figure out why it always returns false. An example of when it should return true is if the input is +d+. It should return false however if the input was +d+d
def SimpleSymbols(str)
idx = 0
while idx < str.length
if str[idx].ord > 96 && str[idx].ord < 123
return false if str[idx - 1] != "+" || str[idx + 1] != "+"
end
idx += 1
end
return true
end
SimpleSymbols(STDIN.gets)
This returns true for me. But I'm wondering if you're getting bit by a line termination issue? Since gets() keeps the line termination, what do these two lines return in your environment:
SimpleSymbols("+d+d")
and
SimpleSymbols("+d+d\n")
? That might provide a pointer to your problem.
When you start the loop and idx is set to 0, the "str[idx - 1]" will equal -1. Setting the statement to check the last character not the first.
return false if str[idx - 1] != "+" || str[idx + 1] != "+"
So "f++d+" will pass as TRUE while it's actually false.
GL

Resources