arun-that-plays-chess
arun-that-plays-chess

Reputation: 29

'undefined method `>' for nil:NilClass in ruby

This is my code for converting a user entered string into a Caesar cipher.

puts "text?"
text = gets.chomp

puts "key?"
key = gets.chomp.to_i 

plainTex = Array.new 
ciphTex = Array.new

j = 0 

text.each_byte do |i|
  plainTex[j] = i
  j += 1
end

j = 0 

plainTex.each_entry do |i|

  if ( i == 32 )
    ciphTex[j] = plainTex[j]
    j += 1
    end

  if( plainTex[j] > 64) and (plainTex[j] < 91 )
    if( (plainTex[j] + key) > 91)
      ciphTex[j] = (plainTex[j] + key ) - 90
      j += 1
    else 
      ciphTex[j] = plainTex[j] + key 
      j += 1 
    end
  end

  if( plainTex[j] > 94) and (plainTex[j] < 123)
    if( (plainTex [j] + key) > 122)
      ciphTex [j] = (plainTex[j] + key) - 122
      j += 1 
    else 
      ciphTex[j] = plainTex[j] + key
      j += 1
    end
  end
 end 

ciphTex.each_entry do |i|
  puts i
end

now, I'm getting error : undefined method `>' for nil:NilClass (NoMethodError)

Search on the web led me to conclude that plainTex might be nil,as the error message is saying( which should not be the case as plainTex is fed data beforehand).

On a side note : the program runs fine when the input string is all lowercase and has no space. I don't know why.

So, what am I doing wrong?

Upvotes: 2

Views: 1368

Answers (1)

Marko Avlijaš
Marko Avlijaš

Reputation: 1659

Problem Explained

When you access array with index that is out of bounds you'll get no error, but result nil.

irb --simple-prompt
>> a = [1]
=> [1]
>> a[0]
=> 1
>> a[1]
=> nil
>> a[200]
=> nil

Debugging your code

A little bit of puts statements reveal the issue:

text.each_byte do |i|
  plainTex[j] = i
  j += 1
end

j = 0 

puts "Length: #{plainTex.length}"

plainTex.each_entry do |i|

  if ( i == 32 )
    ciphTex[j] = plainTex[j]
    j += 1
  end
  puts "j: #{j}"

This is the output

text?
Testing Test
key?
32
Length: 12
j: 0
j: 2
j: 3
j: 4
j: 5
j: 6
j: 7
j: 8
j: 10
j: 11
j: 12
test.rb:30:in `block in <main>': undefined method `>' for nil:NilClass (NoMethodError)
    from test.rb:23:in `each'
    from test.rb:23:in `each_entry'
    from test.rb:23:in `<main>

Last index of array in this case was 11 and you accessed index 12. You got result nil then you tried to call method > on nil.

Professional debugging

You can install gem pry-byebug and use short aliases s, n for step into, next etc.

https://github.com/deivid-rodriguez/pry-byebug

Bug Found

Using pry-byebug it's immediatelly clear what's the bug, even though I don't really understand your code because those numbers are meaningless to me.

For first letter it increments j in second condition if( plainTex[j] > 64) and (plainTex[j] < 91 ). Then with incremented j it moves to if( plainTex[j] > 94) and (plainTex[j] < 123) where it increments it again.

You should use elsif for all of these or next

Code Cleanup

Ruby convention is to use snake case to name your variable and I'd change this piece of code:

if( plainTex[j] > 64) and (plainTex[j] < 91 )
  if( (plainTex[j] + key) > 91)
    ciphTex[j] = (plainTex[j] + key ) - 90
    j += 1
  else 
    ciphTex[j] = plainTex[j] + key 
    j += 1 
  end
end

to this:

if plain_text[j].between?(65, 90)
  ciph_text[j] = plain_text[j] + key
  ciph_text[j] -= 90 if ciph_text[j] > 91
  j += 1
end

It seems that you are repeating j += 1 in every condition, so why not remove it outside if/else blocks and put it as last line before the loop end?

Also I am a human and I don't want to remember that "A" is 65 and "Z" is 90. Why not say so in the code?

if plain_text[j].between?("A".ord, "Z".ord)

I have never messed with ASCII in ruby, but I am sure you could further improve this, possibly do entire cypher in one simple loop with 10 lines of code or less.

Ruby is really good at processing arrays, so use that. I'd do something like this:

plain_text = text.codepoints
cyph_text = plain_text.map do |code|
  if code == ' '.ord
    ' '.ord
  elsif
    # return cypher code
  end
end

Check out how map function works.

Upvotes: 2

Related Questions