Understanding 'next' in ruby - comparing solutions - ruby

I'm doing an online course:
Write a method that takes a string and returns true if the letter "z" appears within three letters after an "a". You may assume that the string contains only lowercase letters.
This is the solution given:
def nearby_az(string)
i = 0
while i < string.length
if string[i] != "a"
i += 1
next
end
j = i + 1
while (j < string.length) && (j <= i + 3)
if string[j] == "z"
return true
end
j += 1
end
i += 1
end
return false
end
This solution seems unnecessarily complex to me. Specifically, I don't understand why next is needed. I was wondering if someone could help me understand this function.
My solution appears to work as well with the tests I tried, but I'm wondering if the other solution is better and if my solution would have problems that I just haven't come across yet.
def nearby_az(string)
i = 0
while i < string.length
while string[i] != "a"
i += 1
end
if string[i + 1] == "z" || string[i + 2] == "z" || string[i + 3] == "z"
return true
else
i += 1
end
return false
end
end

In the original answer, if you remove next, the i index will be assumed to be representing an "a" position even if it is not, and will return a wrong result.
When you pass a string like "b" to your code, it will go into an endless loop because the outer condition while i < string.length has no control over:
while string[i] != "a"
i += 1
end
Notice that, once i is beyond the last position of a string, the condition string[i] becomes nil, and string[i] != "a" will be satisfied from there on.

Your solution will not work if the string will look like this "bbbbb..." or like this "abb" - that is -
1) your code will crash if string.length < 4 (no string[i+3])
2) your code will crash if no "a"s are in the string.
And here the "next" came in handy:
The purpose of the "next" is to skip the rest of the loop and jump right back to the start of it. So the original solution will first go over chars until it finds on "a" (skipping the other part of the loop is not) and when and only when it finds "a" - it goes to find the "z".

So, as sawa stated, your loop doesn't accept strings without an a in them. It also doesn't understand strings with multiple a's. For example aaaaaaaz will return false because it finds the first a, checks the next 3 doesn't find a z and bails out.
You can solve your problem by removing the return false from the end and adding the same length condition as the outer loop to the inner loop.
His use of next is equivalent to the above. It is a way of threading the two things together. It is needlessly complex though, I agree.
A far simpler way (assuming you don't want to use regexps) is to just track the last occurance of an 'a'.
def nearby_az(string)
last_a = -4
string.chars.each_with_index do |c, i|
last_a = i if c == 'a'
return true if c == 'z' and i - last_a <= 3
end
return false
end

Related

Ruby - Find the longest non-repeating substring in any given string

I am working on an assignment where I have to take user input of a string and search through it to find the longest non-repeating string in it. So for example:
If the string is:
"abcabcabcdef"
My output needs to be:
"abcdef is the longest substring at the value of 6 characters"
Here is my poorly made code:
class Homework_4
puts "Enter any string of alphabetical characters: "
user_input = gets
longest_str = 0
empty_string = ""
map = {}
i = 0
j = 0
def long_substr()
while j < str_len
if map.key?(user_input[j])
i = [map[user_input[j]], i].max
end
longest_str = [longest_str, j - i + 1].max
map[user_input[j]] = j + 1
j += 1
end
longest_str
end
long_substr(user_input)
end
I have been working on this for over 6 hours today and I just can't figure it out. It seems like the internet has many ways to do it. Almost all of them confuse me greatly and don't really explain what they're doing. I don't understand the syntax they use or any of the variables or conditions.
All I understand is that I need to create two indicators that go through the inputted string searching for a non-repeating substring (sliding window method). I don't understand how to create them, what to make them do or even how to make them find and build the longest substring. It is very confusing to try and read the code that is full of random letters, symbols, and conditions. I'm sure my code is all sorts of messed up but any help or tips that could point me in the right direction would be greatly appreciated!
def uniq?(s)
# All letters of s uniq?
return s.chars.uniq == s.chars
end
def subs(s)
# Return all substrings in s.
(0..s.length).inject([]){|ai,i|
(i..s.length - i).inject(ai){|aj,j|
aj << s[i,j]
}
}.uniq
end
def longest_usub(s)
# Return first longest substring of s.
substrings(s).inject{|res, s| (uniq?(s) and s.length > res.length) ? s : res}
end
ruby's inject is actually a reduce function, where inject(optional_start_value){<lambda expression>} - and the lambda expression is similar to Python's lambda x, y: <return expression using x and y> just that lambda expressions are strangely written in Ruby as {|x, y| <return expression using x and y>}.
Python's range(i, y) is Ruby's i..y.
Python's slicing s[i:j] is in Ruby s[i..j] or s[i,j].
<< means add to end of the array.
Second solution (inspired by #Rajagopalan's answer)
def usub(s)
# Return first chunk of uniq substring in s
arr = []
s.chars do |char|
break if arr.include? char
arr << char
end
arr.join
end
def usubs(s)
# Return each position's usub() in s
(0..s.length).to_a.map{|i| usub(s[i,s.length])}
end
def longest_usub(s)
# return the longest one of the usubs() over s
usubs(s).max_by(&:length)
end
then you can do:
longest_usub("abcabcabcdef")
## "abcdef"
I have asssumed that a string is defined to be repeating if it contains a substring s of one or one more characters that is followed by the same substring s, and that a string is non-repeating if it is not repeating.
A string is seen to be repeating if and only if it matches the regular expression
R = /([a-z]+)\1/
Demo
The regular expression reads, "match one or more letters that are saved to capture group one, then match the content of capture group 1".
For convenience we can construct a simple helper method.
def nonrepeating?(str)
!str.match? R
end
I will perform a binary search to find the longest non-repeating string. First, I need a second helper method:
def find_nonrepeating(str, len)
0.upto(str.size-len) do |i|
s = str[i,len]
return s if nonrepeating?(s)
end
nil
end
find_nonrepeating("abababc", 7) #=> nil
find_nonrepeating("abababc", 6) #=> nil
find_nonrepeating("abababc", 5) #=> nil
find_nonrepeating("abababc", 4) #=> "babc"
find_nonrepeating("abababc", 3) #=> "aba"
find_nonrepeating("abababc", 2) #=> "ab"
find_nonrepeating("abababc", 1) #=> "a"
We may now implement the binary search.
def longest(str)
longest = ''
low = 0
high = str.size - 1
while low < high
mid = (low + high)/2
s = find_nonrepeating(str, mid)
if s
longest = s
low = mid + 1
else
high = mid - 1
end
end
longest
end
longest("dabcabcdef")
#=> "bcabcdef"
a = "abcabcabcdef"
arr = []
words = []
b=a
a.length.times do
b.chars.each do |char|
break if arr.include? char
arr << char
end
words << arr.join
arr.clear
b=b.chars.drop(1).join
end
p words.map(&:chars).max_by(&:length).join
Output
"abcdef"

How do I get a numerical value for all the characters that are repeated in a certain string?

def num_repeats(string)
letters = string.chars
idx = 0
n = 1
arr = []
lettercount = 0
while idx < letters.length
lettercount = 0
while n < letters.length
if letters[idx] == letters[n]
lettercount = 1
end
n+=1
end
if lettercount > 0
arr.push(idx)
end
idx += 1
end
return arr.length
end
puts(num_repeats("abdbccc"))
# == 2 since 2 letters are repeated across the string of characters
I keep getting zero, although as i see it if a number is repeated the value of numbercount should shift from zero to one and then allow some value to get pushed into the array where I later get the length of said array to determine the number of repeated characters. Is there an issue with my loops?
UPDATE
If you really want to use the same kind of code and algorithm to do that, then here are the problems of it :
In your second while loop the variable n is supposed to start from idx+1, considering you are trying to pick up an index and then find whether the character at that index is repeated somewhere after the index.
But even if you fix that you will get 3 for abdbccc. That kinda shows that your algorithm is wrong. When there are more than 2 occurrences of a repeated character, just like the process I said in the above para, you do that for every such character except for the last one, without checking whether the character had already been detected for repetition. Illustration :
str = 'aaa'
When idx = 0, you get str[idx] == str[n=1], adds it to the result.
When idx = 1, you get str[idx] == str[n=2], adds it to the result.
Now you counted a twice for repetition. I think you can fix that alone.
I think you are just trying to do the same as this (assumes you need to check lower case letters only) :
str = "abdbccc"
('a'..'z').count { |x| str.count(x) > 1 }
# => 2
Or if you need to check the number of repeated characters for any character :
str = "12233aabc"
str.chars.group_by(&:to_s).count do |k, v|
v.size > 1
end
# => 3
It's Ruby we are talking about. It's not really a good idea to write code like that in Ruby, I mean you are using a lot of while loops and manually tracking down their counters, while in Ruby you usually do not have to deal with those, considering all the convenient, less error-prone and shorter alternatives Ruby provides. I think you have a C like background, I recommend that you learn more of Ruby and Ruby way of doing things.
Didn't understood what you were trying to do, maybe you could use a hash to assist:
def num_repeats(string)
letters = string.chars
counter_hash = Hash.new(0)
letters.each { |l| counter_hash[l] += 1 }
counter_hash
end
You have this inner loop
while n < letters.length
if letters[idx] == letters[n]
lettercount = 1
end
n+=1
But nowhere are you resetting n, so after this loop has scanned once, it will skip past every subsequent time
You can mostly fix that by setting n to idx + 1 here
while idx < letters.length
lettercount = 0
n = idx + 1
while n < letters.length
You still will get a result of 3 because you are not detecting that c has already been counted
You can fix this final problem with a couple more tweaks
def num_repeats(string)
letters = string.chars
idx = 0
arr = []
lettercount = 0
while idx < letters.length
lettercount = 0
n = idx + 1 # <== start looking after idx char
while n < letters.length
if letters[idx] == letters[n]
lettercount += 1 # <== incrementing here
end
n+=1
end
if lettercount == 1 # <== check for exactly one
arr.push(idx)
end
idx += 1
end
return arr.length
end
This works because now lettercount == 2 for the first c so the duplicate is not counted until you get to the second c where lettercount == 1
This is still considered a poor solution as it has O(n**2) complexity. There are solutions - for example using Hash which are O(n)

Ruby - Finding the longest palindromic substring in a string

I understand how to find if one string is a palindrome
string1 == string1.reverse
It's a little more difficult though with multiple palindromes in a string
"abcdxyzyxabcdaaa"
In the above string, there are 4 palindromes of length greater than 1
"xyzyx", "yzy", "aaa" and "aa"
In this case, the longest palindrome is "xyxyx", which is 5 characters long.
How would I go about solving this problem though.
I know of the array#combination method, but that won't work in this case.
I was thinking of implementing something like this
def longest_palindrome(string)
palindromes = []
for i in 2..string.length-1
string.chars.each_cons(i).each {|x|palindromes.push(x) if x == x.reverse}
end
palindromes.map(&:join).max_by(&:length)
end
If your just looking for the largest palindrome substring, Here is a quick and dirty solution.
def longest_palindrome(string, size)
string.size.times do |start| # loop over the size of the string
break if start + size > string.size # bounds check
reverse = string[start, size].reverse
if string.include? reverse #look for palindrome
return reverse #return the largest palindrome
end
end
longest_palindrome(string, size - 1) # Palindrome not found, lets look for the next smallest size
end
def longest_palindrome(string)
longest = ''
i = 0
while i < string.length
j = 1
while (i + j) <= string.length
x = string.slice(i, j)
if (x.length > longest.length) && (x == x.reverse)
longest = x
end
j += 1
end
i += 1
end
longest
end
The slice method is handy to have for solving this problem. Test each substring with the classic double while loop approach with (i, j) representing a starting index and length of the substring respectively. string.slice(start_index, substring_length)
The String#slice method works like this:
"bdehannahc".slice(3, 8) == "hannah" # which is a palindrome and would be
# found by the method introduced above
This checks if the entire string str is a palindrome. If it is, we're finished; if not, check all substrings of length str.size-1. If one is a palindrome, we're finished; if not, check substrings of length str.size-1, and so on.
def longest_palindrome(str)
arr = str.downcase.chars
str.length.downto(1) do |n|
ana = arr.each_cons(n).find { |b| b == b.reverse }
return ana.join if ana
end
end
longest_palindrome "abcdxyzyxabcdaaa"
#=> "xyzyx"
longest_palindrome "abcdefghba"
#=> "a"
The key method here is Enumerable#each_cons.
Here is another solution, using less features of Ruby and iteration instead of recursion:
def longest_palindrome(string)
# to find the longest palindrome, start with whole thing
substr_start = 0
substr_length = string.length
while substr_length > 0 # 1 is a trivial palindrome and the end case
# puts 'substr_length is:' + substr_length.to_s
while substr_start <= string.length - substr_length
# puts 'start is: ' + substr_start.to_s
if palindrome?(string.slice(substr_start,substr_length))
puts 'found palindrome: ' + string.slice(substr_start,substr_length)
return string.slice(substr_start,substr_length)
end
substr_start += 1
end
substr_start = 0 # inner loop ctr reset
substr_length -= 1
end
puts 'null string tested?'
return ''
end

When and Why use Loop Do Construct in Ruby

I recently came up across a problem/solution that used Loop Do. I seldom have seen this so far in my learning Ruby Programming (I am a beginner with no CS experience).
# Write a function, `nearest_larger(arr, i)` which takes an array and an
# index. The function should return another index, `j`: this should
# satisfy:
#
# (a) `arr[i] < arr[j]`, AND
# (b) there is no `j2` closer to `i` than `j` where `arr[i] < arr[j]`.
#
# In case of ties (see example beow), choose the earliest (left-most)
# of the two indices. If no number in `arr` is largr than `arr[i]`,
# return `nil`.
#
# Difficulty: 2/5
describe "#nearest_larger" do
it "handles a simple case to the right" do
nearest_larger([2,3,4,8], 2).should == 3
end
it "handles a simple case to the left" do
nearest_larger([2,8,4,3], 2).should == 1
end
it "treats any two larger numbers like a tie" do
nearest_larger([2,6,4,8], 2).should == 1
end
it "should choose the left case in a tie" do
nearest_larger([2,6,4,6], 2).should == 1
end
it "handles a case with an answer > 1 distance to the left" do
nearest_larger([8,2,4,3], 2).should == 0
end
it "handles a case with an answer > 1 distance to the right" do
nearest_larger([2,4,3,8], 1).should == 3
end
it "should return nil if no larger number is found" do
nearest_larger( [2, 6, 4, 8], 3).should == nil
end
end
SOLUTION
def nearest_larger(arr, idx)
diff = 1
loop do
left = idx - diff
right = idx + diff
if (left >= 0) && (arr[left] > arr[idx])
return left
elsif (right < arr.length) && (arr[right] > arr[idx])
return right
elsif (left < 0) && (right >= arr.length)
return nil
end
diff += 1
end
end
nearest_larger([2,4,3,8], 1)
Can someone please explain to me when is the best time to use a "loop do" construct instead of the usual "while" or "unless" or "each" construct?
Adding up to the previous answers,
The "loop do" construct also offers a cleaner syntax when working with external iterators, e.g
No "loop do"
my_iterator = (1..9).each
begin
while(true)
puts my_iterator.next
end
rescue StopIteration => e
puts e
end
And now with "loop do" this would become
my_iterator = (1..9).each
loop do
puts my_iterator.next
end
And the exception is handled for you. It also allows you to loop through two collections at the same time and as soon as one of them runs out of elements the loop exits gracefully,
iterator = (1..9).each
iterator_two = (1..5).each
loop do
puts iterator.next
puts iterator_two.next
end
It will print: 1,1,2,2,3,3,4,4,5,5,6.
More info on it at: ruby-docs.org
In a language without loop, you might use a while construct like:
while( true ) {
# Do stuff until you detect it is done
if (done) break;
}
The point of it is that you start the loop without knowing how many of iterations to perform (or it is hard to calculate in advance), but it is easy to detect when the loop should end. In addition, for a particular case you might find the equivalent while (! done) { # do stuff } syntax clumsy, because the done condition can happen halfway through the loop, or in multiple places.
Ruby's loop is basically the same thing as the while( true ) - in fact you can use while( true ) almost interchangeably with it.
In the given example, there are following points of return within each iteration:
if (left >= 0) && (arr[left] > arr[idx])
return left # <-- HERE
elsif (right < arr.length) && (arr[right] > arr[idx])
return right # <-- HERE
elsif (left < 0) && (right >= arr.length)
return nil # <-- HERE
end
There is also an implied "else continue looping" here, if no end conditions are met.
These multiple possible exit points are presumably why the author chose the loop construct, although there are many ways of solving this problem in practice with Ruby. The given solution code is not necessarily superior to all other possibilities.
Using the loop do construct allows you to break on a conditional.
for instance:
i=0
loop do
i+=1
print "#{i} "
break if i==10
end
You would want to use this when you know the number of elements that will be processed, similar to that of the for each loop
loop with 'loop' construct will execute the given block endlessly
until the code inside the block breaks on certain condition.
it can be used when you don't have a collection to loop over, the
places where 'each' and 'for' cannot work.
the different between 'loop' and while/until is that while/until will
execute the given block when certain condition is meet, where as in
case of loop there is no condition to start, condition lies inside the
loop's block.
for better understanding read doc.
http://www.ruby-doc.org/core-1.9.2/Kernel.html#method-i-loop
Suppose You wanted to put a number of conditions, it might be neater to put them together. Instead of this, for example:
x = 0
while x <= 10
num = gets.to_f
break if num < 1
break if /\D/.match? num.to_s
puts num ** 2
end
Grouping the breaks together makes it more readable
x = 0
loop do
num = gets.to_f
break if num < 1
break if x <= 10
break if /\D/.match? num.to_s
puts num ** 2
end

How to execute multiple succeeding functions in 1 line in Ruby?

I have two succeeding function with the same condition and I wonder what is the best way to write this? I know the one way to do this is using if (condition) ... end, but I'm wondering if I can do it in one-line similar to bash, '[$n == $value] echo "$n" && break'.
n = 0
loop do
puts n if n == value # puts and break is having the same condition, but
break if n == value # can we do it in one line?
n += 1
end
Because n is truthy, you can use the 'and' joiner. It reads really nicely:
n = 0
loop do
puts n and break if n == value
n += 1
end
--edit--
As pointed out in comments, that won't actually work because puts returns nil, which isn't truthy. My bad. You can use 'or' instead, but that doesn't read nicely. So I'd say just group the statements with parenthesis.
n = 0
loop do
(puts n; break) if n == value
n += 1
end
You could also change the puts method to return the value it prints, and that would work with 'and', but that's probably not the smartest idea :)
I'm guessing your actual code is different to what you've pasted, so if the first method in your chain returns something, you can use 'and'.
One easy way is to just parenthesize the statements:
ruby-1.9.1-p378 > 0.upto(5) do |n|
ruby-1.9.1-p378 > (puts n; break;) if n == 3
ruby-1.9.1-p378 ?> puts ">>#{n}<<"
ruby-1.9.1-p378 ?> end
>>0<<
>>1<<
>>2<<
3
If it's a bit much to put in parentheses, a begin-end will do the trick:
0.upto(5) do |n|
begin
puts "I found a matching n!"
puts n
puts "And if you multiply it by 10, it is #{10*n}"
break;
end if n == 3
puts "((#{n}))"
end
Output:
((0))
((1))
((2))
I found a matching n!
3
And if you multiply it by 10, it is 30
proc { puts n; break; }.() if n == 3
One of the golden rules of Ruby is that if you are writing a loop, you are probably doing it wrong. In this particular case, all that your loop is doing is to find an element in a collection. In Ruby, there already is a method for finding an element in a collection: Enumerable#find. There is no need to write your own.
So, the code gets simplified to:
puts (0...1.0/0).find {|n| n == value }
Now that we have a nice declarative formulation of the problem, it is easy to see that (assuming sane equality semantics and sane semantics of value.to_s), this is exactly the same as:
puts value
So, the whole loop was completely unnecessary to begin with.

Resources