Search code examples
rubynomethoderror

Program working, but I'm still getting a NoMethodError 'length' nil


This program takes two arrays of strings and subtracts the length of the longest in a1 from the shortest in a2, and then vice-versa and returns the greater.

It works (passed 103/103 tests), but I'm getting this error:

NoMethodError: undefined method `length' for nil:NilClass 
main.rb:10:in `mxdiflg'
main.rb:66:in `block (2 levels) in <main>'

Where's the issue? And since this isn't the first time this has happened, how do I debug a program that works?

def mxdiflg(a1, a2)
    if a1 == '' || a2 == ''
    -1
  end
  a1_order = a1.sort{|left, right| left.length <=> right.length}
  a2_order = a2.sort{|left, right| left.length <=> right.length}

  a_total = a1_order.reverse[0].length - a2_order[0].length
  b_total = a2_order.reverse[0].length - a1_order[0].length

  if b_total > a_total
    b_total
  else
    a_total
  end
end

Solution

  • Starting with fixing your program. First of all, you say you're accepting arrays of strings, yet if a1 == '' || a2 == '' checks whether you passed empty strings. Putting -1, not a return -1 does essentially nothing.

    I am assuming that the error is in this line (you have the line in stacktrace, it's main.rb:10:in 'mxdiflg' therefore line 10 for you):

    a_total = a1_order.reverse[0].length - a2_order[0].length
    

    As if your array is empty, your array[0] will be nil so you cannot call .length on it (just as the error you pasted says suggest).

    As for debugging, at some point you will have to get comfortable with using Pry, but for now it should be enough that you check the line number and error message. In this case, it's quite clear that you're calling .length on a nil, so your a1_order[0] must be nil, so your array must be empty. You may also add simple puts messages, for example:

    puts "a1_order: #{a1_order}"
    puts "a2_order: #{a2_order}"
    a_total = a1_order.reverse[0].length - a2_order[0].length
    b_total = a2_order.reverse[0].length - a1_order[0].length
    

    Now when running your program, you can inspect your sorted arrays and it should be quite clear that you're trying to call methods on nils.

    Now, having that covered, we can try to make your program a bit nicer. First of all, as I mentioned, your first check doesn't make much sense. Let's turn it into:

    return -1 if [a1,a2].any?(&:empty)
    

    which will actually return from your method with -1 if any of arrays are empty.

    Going further:

    a1_order = a1.sort{|left, right| left.length <=> right.length}
    

    can be written as:

    a1_order.sort_by(&:length)
    

    Calling

    a1_order.reverse[0]
    

    is a bit inefficient, as it will create a copy of your array with a reversed order, you may simply do a1_order.last instead.

    If looking for maximum/minimum values, you may use Enumerable#max_by / Enumerable#min_by like so:

    a_total = a1.max_by(&:length).length - a2.min_by(&:length).length
    b_total = a2.max_by(&:length).length - a1.min_by(&:length).length
    

    And getting a higher value can be achieved with Array#max:

    [a_total, b_total].max
    

    Wrapping this all together, your method may look like this:

    def mxdiflg(a1, a2)
      return -1 if [a1, a2].any?(&:empty?)
    
      a_total = a1.max_by(&:length).length - a2.min_by(&:length).length
      b_total = a2.max_by(&:length).length - a1.min_by(&:length).length
    
      [a_total, b_total].max
    end