简体   繁体   中英

Nested conditional statements in Ruby

My issues are two-fold:

  1. I didn't realize I could nest multiple conditionals like this. It seems pretty nasty. I think I know what's going on here, but could someone who really gets it explain so I can really get the concept?

  2. Since I don't understand nested conditionals too well, I'm a bit lost on the refactor, an area I am pretty weak in to begin with. Would y'all be so kind as to present some possible refactor solutions with explanations? It would help my limited understanding greatly.


def valid_triangle?(a, b, c, sum)
  if a != 0 || b != 0 || c != 0
    if a >= b
      largest = a
      sum += b
    else largest = b
      sum += a
    end
    if c > largest
      sum += largest
      largest = c
    else sum += c
    end
    if sum > largest
      return "true"
    else return "false"
    end
  else return false
  end
end

You can trim this down considerably by doing it a more Ruby-like way:

def valid_triangle?(*sides)
  case (sides.length)
  when 3
    sides.sort!

    sides[0] + sides[1] >= sides[2]
  else
    false
  end
end

Whenever possible, try and express your logic as a series of transformations on data, it's usually a lot easier to follow. In this case treat the incoming points as an array and sort them rather than having special-case logic for each point. That special case code is always trouble, hard to test, and prone to subtle failure if you make even a tiny mistake.

It's worth noting that in Ruby you should have your if statements formatted like this:

if condition
  # ...
elsif condition
  # ...
else
  # ...
end

Including code immediately following else may be valid syntax, but it's very confusing to look at. It seems you're testing against largest == b and have made a mistake, doing an assignment inadvertently, when really that's the content of the else block, not a condition for an elusive .

This code can be easily updated to include tests for negative lengths, like adding sides[0] > 0 as part of your logic.

Also, embrace the Ruby implicit return where the last value provided is the one returned from the method. The case statement actually propagates values from the one that triggered, making it very convenient for passing things through.

Nested conditionals

Really the concept of nested conditionals is not much different from the concept of any other type of conditional. A conditional controls whether or not all the code inside it is executed. So if you nest one conditional inside the other, the outer conditional will control whether the inner conditional is executed.

some_var = 5
if some_var > 1
  # Code here will be executed
  if some_var > 10
    # Code here will not be executed
  end
end

if some_var > 10
  # Code here will not be executed
  # The following conditional won't be checked at all,
  # because execution won't reach this point.
  if some_var > 1
    # This won't be executed.
  end
end
# The execution point will immediately jump to here once
# the some_var > 10 condition is determined to be false.

Refactoring your code

As for refactoring your code, there are a number of things here which could be improved. For one, nested conditionals often can and should be avoided. Not because they're difficult to understand, but because there are often more readable alternatives.

For example, your code encloses the entire method in one large conditional which basically says "don't execute the rest of this method unless this condition is true":

if a != 0 || b != 0 || c != 0
  # ...
else
  return false # Note: Statements are conventionally
               # placed on the next line after else.
               # Not on the same line.
end

This can be avoided by including one condition at the start of the method which immediately returns if the condition is false:

return false unless a != 0 || b != 0 || c != 0

The other thing I noticed is that you're returning strings in some cases instead of booleans:

if sum > largest
  return "true"
else
  return "false"
end

This isn't a good idea. Return an actual boolean instead:

if sum > largest
  return true
else
  return false
end

In fact though, this entire condition isn't all that useful. sum > largest is already evaluating to true in one case and false in the other, so you can just:

return sum > largest

Also, why are you passing sum in as an argument? Shouldn't that always start off as zero? You can remove that statement from the method signature and just initialize sum as 0 in the method body:

sum = 0

Now let's see what your code looks like:

def valid_triangle?(a, b, c)
  return false unless a != 0 || b != 0 || c != 0

  sum = 0

  if a >= b
    largest = a
    sum += b
  else
    largest = b
    sum += a
  end

  if c > largest
    sum += largest
    largest = c
  else
    sum += c
  end

  return sum > largest
end

That's looking much better. The code in the middle there is still pretty complicated though. Let's think about that for a moment... it seems you want to set largest to the largest value of the three sides, then set sum to the sum of the remaining sides. This is pretty easy to do with an array sort:

sides = [a, b, c].sort

Now, sides contains a sorted array of all sides. The last element will be the largest...

largest = sides.last

...and the other two sides can be used to find the sum:

sum = sides[0] + sides[1]

Now let's see where we're at:

def valid_triangle?(a, b, c)
  return false unless a != 0 || b != 0 || c != 0

  sides = [a, b, c].sort

  largest = sides.last
  sum = sides[0] + sides[2]

  return sum > largest
end

Nice!

How I might do it

With a better understanding of some of Ruby's built-in methods, this can be made even simpler. I won't go into all the details, but here's how I'd probably write this code:

def valid_triangle?(a, b, c)
  sides = [a, b, c].sort
  sides.all?{|s| s != 0} && sides.pop <= sides.reduce(:+)
end

How does this work? Let me break it down.

First, just as before, we collect all the sides into a sorted list so that the smallest side is the first element, and the largest side is the last element. This will make our later calculations much easier.

sides = [a, b, c].sort

Next, we check that all sides are non-zero.

sides.all?{|s| s != 0}

Then, we check that the largest side is at least as small as the sum of the other sides. I do this by removing the largest side from the sorted list, then checking that this value is smaller than the sum (found using sides.reduce(:+) ) of the remaining two sides in the list:

sides.pop <= sides.reduce(:+)

The result of these two conditions is combined with && and returned from the method (Ruby automatically returns the last executed statement in a method, so we can omit the return keyword here).

Here is a list links to the documentation for the built-in methods I used in this solution. Please examine the documentation for any methods you don't understand to see how they work:

Without touching the core algorithm, here are some things you could do to improve the method structure.

def valid_triangle?(a, b, c, sum)

  # terminate early if inputs are not in expected state
  return false if [a,b,c].include? 0

    if a >= b
      largest = a
      sum += b
    else 
      largest = b
      sum += a
    end

    # space to separate this from the previous if/else block
    if c > largest
      sum += largest
      largest = c
    else 
      sum += c
    end

  # be consistent on return type (string or boolean)
  sum > largest

  # ternary if can be used if return type is not a boolean 
  # strings are not recommended for true/false values though
  # sum > largest ? "true" : "false"
end

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM