Check that 'z' appears within 3 letters after 'a' - nomethoderror

I need to write a method that takes a string and returns true if “z”
appears within three letters after an “a”.

def nearby_az(string)
letters = string.split(’’)

a = letters.index(“a”)
z = letters.index(“z”)

difference = z - a

if two != nil && difference.between?(0, 3) == true
return true
end
end

Here is the error I get when I run the code:

nearby_az(“baz”) == true: true
nearby_az(“abz”) == true: true
nearby_az(“abcz”) == true: true
/users/cyrilguirguis/Desktop/most-letters.rb:22:in nearby_az': undefined method-’ for nil:NilClass (NoMethodError)

The first three tests pass and then I get a no methoderror ? something
is wrong with the line difference = z - a … but can’t figure out what.

Here are all the tests:

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)

Test 4 (“a”) does not contain any z’s.

String#index returns nil if no match can be found.

There is no ‘-’ (minus) method defined for nil, thus ruby gives a
NoMethodError.

undefined method `-’ for nil:NilClass

Also, try the test case

“abbbbbzaz”

String#index only returns the index of the first occurence - what if
there are many a’s or z’s?

You could use a regexp (/a.{0,2}z/), but I suppose the task is about
learning how one could do it at a low level.

That makes sense. Thanks!!

I have new code which seems right to me, but it has syntactical errors
which I can’t figure out for the life of me…

def nearby_az(string)
letters = string.split(’’)

count = 0

while count < letters.length
current_letter = letters[count]

if current_letter == "a"
  next_index = count + 1

   while next_index < count+4

     if letters[next_index] == "z"
     return true
     end
   next += 1
   end
end
false

count += 1
end

end

Here are the errors

/users/cyrilguirguis/Desktop/most-letters.rb:30: syntax error,
unexpected tOP_ASGN, expecting keyword_end
next += 1
^
cyrils-MacBook-Pro:~ cyrilguirguis$ ruby
/users/cyrilguirguis/Desktop/most-letters.rb
/users/cyrilguirguis/Desktop/most-letters.rb:30: syntax error,
unexpected tOP_ASGN, expecting keyword_end
next += 1
^

I’'m sorry for incomplete code but im on tablet.

This doesnt solve your problem?

/[aA].{2}[zZ]/.match(string) == true?

next += 1

next is a keyword in ruby, you can’t use it as a variable like that.

Instead of string.split(’’), you can use

string.each_char.each_with_index do |letter,i|

end

The ‘false’ near the end isn’t doing anything. If you want it to be the
return value of the function, put it at the very end of the function.

Also, it often helps to use some print/puts statements and take a look
what’s happening inside the loop step by step.

For reference, here is a shorter version in ruby using the same
algorithm you’re using:

def nearby_az(string,range=3)
string.each_char.each_with_index do |letter,i|
return true if letter==“a” && string[i+1,range].index(“z”)
end
false
end

Or even shorter :wink:

f=->s{/a.{,2}z/=~s}

Now, rested, i can answer better your question.

def nearby_az(string)
return !/[aA].{2}[zZ]/.match(string).nil?
end

This method takes a part of string and test for first letter a or A and
the 4th digit is z or Z.

I think this is the best approach.

Dansei thanks for the tip on put statements to find errors!

Thank you Fabio as well. I’m actually trying to code it another way, but
definitely helps to have the elegant solution.

My bad. Replace the number 2 with 3

explanation of regex:

! - negate match
/ - start of regex
[aA] - Take first char if is aA char (the aA is in order to take both
down and upcase)
.{3} - take exact 3 any char after aA
[zZ] - take the 5th position if is zZ char (the zZ is in order to take
both down and upcase)
/ - end regex

Let me know if it helps!

so here is the long version:

def nearby_az(string)
letters = string.split(’’)

count = 0

while count < letters.length
current_letter = letters[count]

            if current_letter != a
                count += 1
                next
            end

next_index = count + 1

while next_index < count+4 && next_index < letters.length

     if letters[next_index] == "z"
     return true
     end

next_index += 1

end

count += 1
end

return false
end

Fabio, I tried running yours and the first two tests fail.

Dansei…that’s a sweet solution! :smiley:

def nearby_az(string,range=3)
string.each_char.each_with_index do |letter,i|
return true if letter==“a” && string[i+1,range].index(“z”)
end
false
end

What’s going on here exactly, you have two iterators running
side-by-side? One checks for letter to equal “a” and the other checks if
index within a range = “z” ?

Learning a lot.

In ruby, you can combine iterators:

string.each_char

Gives you an iterator over each character and your iteration looks like
this

a c c z g h

Next,

string.each_char.each_with_index

takes the previous iterator and returns a new iterator whose elements
are an array with the element and its index. It looks like this:

[a,0] [c,1] [c,2] [z,3] [g,4] [h,5]

Inside the loop, we check if the first character is an ‘a’ and if a ‘z’
is to be found within 3 characters. We need the current index for this.

/[aA].{2}[zZ]/.match(string)

This fails not because of the ‘2’, but because this check for exactly 2
characters after an ‘a’, and only then for a ‘z’.

It should be

/[aA].{0,2}[zZ]/.match(string)

This checks for 0 to 2 characters after an ‘a’. Note that it accepts
upper case letters as well: “Afgz”

The way I interpret “within 3 characters” is that “aggz” should match,
but “agggz” should not. So there may be at most 2 other characters after
an ‘a’?