Search code examples
rubyif-statementcontrol-flow

Can I avoid if-elsif-else in this ruby function?


I have three values (foo,bar,bad) and based on which one I pass into a function I want to use the other two.

For example calling self.method(foo) would result in something like this with foo being undefined.

def method
  self.foo = 180 - self.bar - self.bad
end

I can do it with a simple if-elsif-else setup, but is there a better (more idiomatic) way?

Update for clarity:

Here's what one suggestion might look like in production:

def solve_angles(missing)
  angles = 180 - [ A, B, C ].reject { |e| e == missing }.inject(:+)
end

called via @triangle.solve_angles(self.C) or even '@triangle.solve_angles("C") would be possible.


Solution

  • You don't need to specify which angle you're solving for; it's implicit in the problem's definition. If you start with something like this (anything resembling error handling elided):

    class Triangle
      def initialize h
        h.keys.each { |key| instance_variable_set "@#{key}".to_sym, h[key] }
      end
    
      def to_s
        "a=#{@a}, b=#{@b}, c=#{@c}"
      end
    
      def solve
        angle = instance_variables.inject(180) { |v, a| v -= instance_variable_get(a) }
        [:@a, :@b, :@c].each {|s| instance_variable_set(s, angle) unless instance_variable_defined? s }
        self
      end
    end
    

    Then:

    pry(main)> t = Triangle.new :a => 20, :c => 30
    => a=20, b=, c=30
    pry(main)> t.solve
    => a=20, b=130, c=30
    pry(main)> 
    

    You could also return/indicate which angle was actually solved for, if necessary.

    This doesn't actually avoid an if statement, which was your specific question. It eliminates the need to explicitly spell each one of them out, which I took as the intent of the question.

    If you truly need to "solve for", you could do add:

    def solve_for sym
      solve
      instance_variable_get("@#{sym}".to_sym)
    end
    

    Technically, you could solve only after determining the value isn't set, but meh.

    > t = Triangle.new :a => 20, :c => 30
    => a=20, b=, c=30
    > t.solve_for :b
    => 130
    > t
    => a=20, b=130, c=30
    > t = Triangle.new :a => 20, :c => 30
    => a=20, b=, c=30
    > t.solve_for :a
    => 20
    > t
    => a=20, b=130, c=30