Более элегантное решение для triangle.rb Ruby Koans

Я работаю через Ruby Koans и перешел к about_triangle_project.rb, в котором вам необходимо написать код для метода, треугольника.

Код для этих элементов можно найти здесь:

https://github.com/edgecase/ruby_koans/blob/master/koans/about_triangle_project.rb

https://github.com/edgecase/ruby_koans/blob/master/koans/triangle.rb

В triangle.rb я создал следующий метод:

def triangle(a, b, c)
  if ((a == b) && (a == c) && (b == c))
    return :equilateral
  elsif ((a == b) || (a == c) || (b == c))
    return :isosceles
  else
    return :scalene
  end
end

Я знаю, что, читая Криса Пайн "Учись программировать", всегда есть много способов сделать что-то. Хотя приведенный выше код работает, я не могу не думать, что есть более элегантный способ сделать это. Кто-нибудь может пожелать предложить свои мысли о том, как сделать такой метод более эффективным и компактным?

Еще одна вещь, о которой мне интересно, - почему для определения равностороннего треугольника мне не удалось создать условие (a == b == c). Это доказательство равностороннего треугольника, но Ruby ненавидит синтаксис. Есть ли легкое объяснение, почему это?

Ответы

Ответ 1

Существует простое объяснение, почему это:

== в Ruby - это оператор, который выполняет определенную функцию. У операторов есть правила для определения того, в каком порядке они применяются, поэтому, например, a + 2 == 3 оценивает добавление перед проверкой равенства. Но оценивается только один оператор за раз. Не имеет смысла иметь две проверки равенства рядом друг с другом, потому что проверка равенства оценивается как true или false. Некоторые языки допускают это, но это все еще не работает правильно, потому что тогда вы будете оценивать true == c, если a и b были равны, что, очевидно, неверно, даже если a == b == c в математические термины.

Что касается более элегантного решения:

case [a,b,c].uniq.size
when 1 then :equilateral
when 2 then :isosceles
else        :scalene
end

Или, даже более короткий (но менее читаемый):

[:equilateral, :isosceles, :scalene].fetch([a,b,c].uniq.size - 1)

Ответ 2

Другой подход:

def triangle(a, b, c)
  a, b, c = [a, b, c].sort
  raise TriangleError if a <= 0 or a + b <= c
  return :equilateral if a == c
  return :isosceles if a == b or b == c
  return :scalene
end

Ответ 3

def triangle(a, b, c)
  if a == b && a == c                # transitivity => only 2 checks are necessary
    :equilateral
  elsif a == b || a == c || b == c   # == operator has the highest priority
    :isosceles
  else
    :scalene                         # no need for return keyword
  end
end

Ответ 4

Я позаимствовал Chuck cool uniq.size и обработал его в оо-решении. Изначально я просто хотел извлечь аргумент аргумента в качестве предложения охраны, чтобы поддерживать принцип единой ответственности, но поскольку оба метода работали с одними и теми же данными, я думал, что они принадлежат друг другу в объекте.

# for compatibility with the tests
def triangle(a, b, c)
  t = Triangle.new(a, b, c)
  return t.type
end

class Triangle
  def initialize(a, b, c)
    @sides = [a, b, c].sort
    guard_against_invalid_lengths
  end

  def type
    case @sides.uniq.size
    when 1 then :equilateral
    when 2 then :isosceles
    else :scalene
    end
  end

  private
  def guard_against_invalid_lengths
    if @sides.any? { |x| x <= 0 }
      raise TriangleError, "Sides must be greater than 0"
    end

    if @sides[0] + @sides[1] <= @sides[2]
      raise TriangleError, "Not valid triangle lengths"    
    end
  end
end

Ответ 5

class TriangleError < StandardError
end

def triangle(a, b, c)
  sides = [a,b,c].sort

  raise TriangleError if sides.first <= 0 || sides[2] >= sides[1] + sides[0]
  return :equilateral if sides.uniq.length  == 1
  return :isosceles if sides.uniq.length  == 2
  :scalene
end

Ответ 6

Вот мое решение:

def triangle(a, b, c)
  sides = [a, b, c].sort
  raise TriangleError, "Invalid side #{sides[0]}" unless sides[0] > 0
  raise TriangleError, "Impossible triangle" if sides[0] + sides[1] <= sides[2]
  return [:scalene, :isosceles, :equilateral][ 3 - sides.uniq.size ]
end

Ответ 7

Хм.. я не знал о uniq - так что из smalltalk (много лет назад) я использовал:

require 'set'
def triangle(a, b, c)
  case [a, b, c].to_set.count
    when 1 then :equilateral
    when 2 then :isosceles
    else :scalene
  end
end

Ответ 8

Исходя из мира matlab, я привык массировать функции "все" и "все", и был достаточно доволен, чтобы найти их в Ruby. Итак:

def triangle(a, b, c)
  eqs = [a==b, a==c, b==c]
  eqs.all?? :equilateral : eqs.any?? :isosceles : :scalene
end

Не знаю, насколько это оптимально, с точки зрения удобочитаемости, времени вычисления... (ruby noob).

Ответ 9

Вот мое решение:

def triangle(a, b, c)
  return :equilateral if a == b and b == c
  return :isosceles if ( a == b or b == c or a == c )
  return :scalene
end