2
\$\begingroup\$

I wrote a simple quadratic equation solver with Ruby to help me solve quadratic equations. Please tell me what I did right or wrong, and what I can do to improve it.

#!/usr/bin/ruby
if ARGV.length != 3
 STDERR << "Usage: #{0ドル} <a> <b> <c>\n"
 exit 1
end
a = ARGV[0].to_i
b = ARGV[1].to_i
c = ARGV[2].to_i
disc = b**2 - 4*a*c
d_str = (disc >= 0) ? (Math.sqrt(disc) % 1 == 0) ? Math.sqrt(disc).to_i : "√#{disc}" : "√#{disc}"
s = "#{-b} ± #{d_str}"
d = "#{2*a}"
puts "\e[4m#{s}\e[0m"
#puts "─"*(s.length)
puts d.center s.length
asked Mar 2, 2017 at 0:20
\$\endgroup\$
2
  • \$\begingroup\$ I would show the calculated value Math.sqrt(disc) even when it is a floating point number. Its also a good idea to check before using terminal control codes (A simple, though not foolproof, way is to check $stdout.isatty). The colorize gem is also, though ti does add a dependency to your code. \$\endgroup\$ Commented Mar 2, 2017 at 17:29
  • \$\begingroup\$ Any updates on this? \$\endgroup\$ Commented Mar 4, 2017 at 7:54

1 Answer 1

4
\$\begingroup\$

What you did right is solve the problem.

What you did "wrong" really depends on a number of factors, so I'll respond with what you can do to improve it, with an example that implements those improvements. Note, many improvements will make the code longer.

Improvements

  • Use the shebang #!/usr/bin/env ruby. It allows the environment to have ruby in a different directory than /usr/bin/ruby.
  • Use $stderr.puts "Usage: #{0ドル} <a> <b> <c>". $stderr is a global variable and can be changed. It allows standard error to be redirected, say to a file or a logger. It would be restored by $stderr = STDERR.
  • Ruby indentation standard is 2 spaces and no hard tabs.
  • Use disc_str to indicate it is a String version of disc.
  • Use disc_sqrt_str to indicate it is a String version of disc_sqrt.
  • Use a variable to reduce wasted calculations; sqrt is not a simple calculation.
  • Nested ? operators can be hard to read and can lead to mistakes.
  • Use numerator and denominator to convey intention, instead of s and d.
  • Allow for the case when discriminant is zero.
  • Use parentheses for arguments when method calls are arguments. Thus instead of f g h x do f g(h(x)) or even better f(g(h(x))).
  • Use discriminant because it is a Mathematics domain term. A non-mathematics literate developer will be blocked from maintaining it because Googling "disc" won't help them.
  • Eliminate common factors.
  • Eliminate fraction if denominator is 1.
  • Define methods for greater flexibility.
  • Include some tests to test each pathway.
  • Use appropriate comments and self-commenting code by using good names.
  • Use Rubocop to provide similar automated improvements.
  • Use the colorize gem in an optional way, since it is not actually necessary.
  • Check if it is available by using ''.respond_to?(:underline), which will mean that it is more flexible because another method could be added to String which might do a similar thing or even something rather different; CSS, LaTeX, ASCII, ... .
  • Use Unicode codes instead of the characters directly. Some editors may not display Unicode characters properly and could change the characters to something unexpected.
#!/usr/bin/env ruby
#
# quadratic - Output quadratic solutions as a fraction
#
# Luis Esteban 9 August 2020
# review of code of Dmitry Kudriavtsev
# Output rational solutions to the quadratic equation
# For a.x^2 + b.x + c = 0
#
# x = (-b ± √d) / 2a
# d = b^2 - 4.a.c (discriminant)
#
begin
 require 'colorize'
rescue LoadError
end
ALLOW_IMAGINARY = true
def solve_quadratic(a, b, c)
 discriminant = b**2 - 4*a*c
 denominator = 2 * a
 if discriminant > 0
 discriminant_sqrt = Math.sqrt(discriminant)
 if discriminant_sqrt % 1 == 0
 b, discriminant, denominator = simplify(b, discriminant_sqrt.to_i, denominator)
 numerator = [-b, " \u00b1 ", discriminant]
 else
 b, discriminant, denominator = simplify_with_sqrt(b, discriminant, denominator)
 numerator = [-b, " \u00b1 \u221a", discriminant]
 end
 elsif discriminant == 0
 solution = Rational(-b, denominator)
 numerator = [solution.numerator]
 denominator = solution.denominator
 else
 if ALLOW_IMAGINARY
 discriminant_sqrt = Math.sqrt(-discriminant)
 discriminant_sqrt = discriminant_sqrt.to_i if discriminant_sqrt % 1 == 0
 if discriminant_sqrt % 1 == 0
 b, discriminant, denominator = simplify(b, discriminant_sqrt.to_i, denominator)
 numerator = [-b, " \u00b1 i \u2a2f ", discriminant]
 else
 b, discriminant, denominator = simplify_with_sqrt(b, discriminant, denominator)
 numerator = [-b, " \u00b1 i \u2a2f \u221a", -discriminant]
 end
 else
 numerator = []
 end
 end
 
 [numerator, denominator]
end
def simplify(b, discriminant, denominator)
 gcd = [b, discriminant, denominator].inject(&:gcd)
 gcd = -gcd unless denominator.positive?
 
 [b, discriminant, denominator].map{|n| n / gcd }
end
def simplify_with_sqrt(b, discriminant, denominator)
 gcd = [b**2, discriminant, denominator**2].inject(&:gcd)
 gcd_sqrt = Math.sqrt(gcd).round
 if denominator.negative?
 gcd = -gcd
 gcd_sqrt = -gcd_sqrt
 end
 
 [
 b / gcd_sqrt,
 discriminant / gcd,
 denominator / gcd_sqrt
 ]
end
def display_fraction(numerator, denominator)
 size = numerator.size
 numerator = numerator.join
 denominator = denominator.to_s
 width = [numerator, denominator].map(&:length).max + 2
 if size > 0
 if denominator != "1"
 if ''.respond_to?(:underline)
 puts "#{numerator}".center(width).underline
 puts denominator.center(width)
 else
 puts numerator.center(width)
 puts "─" * width
 puts denominator.center(width)
 end
 else
 puts numerator
 end
 else
 puts "No solutions"
 end
end
if ARGV.length != 3
 $stderr.puts "Usage: #{0ドル} <a> <b> <c>"
 exit 1
else
 a, b, c = ARGV.map(&:to_i)
 puts "solution(s) to #{a}x\u00b2 + #{b}x + #{c} = 0"
 
 display_fraction(*solve_quadratic(a,b,c))
end
# Testing
# [
# [1, 1, -12], # Integer discriminant
# [-1, 1, 12], # Make denominator positive
# [1, 2, 0], # Don't show denominator if 1
# [4, 4, 1], # Don't show discriminant if 0
# [1, 2, -17], # Don't show denominator if 1 with surd
# [2, 4, 2], # Don't show denominator if 1 and discriminant if 0
# [1, 1, -13], # Show surd
# [1, 2, 17], # Imaginary solution with integer discriminant no denominator
# [1, 2, 16], # Imaginary solution with correct cancellation of surd
# [2, 0, 2], # Imaginary solution with surd, hide denominator
# [2, 8, 16], # Imaginary solution with integer discriminant
# ].each do |a,b,c|
# puts "For: a = #{a.inspect}, b = #{b.inspect}, c = #{c.inspect}"
# puts
# display_fraction(*solve_quadratic(a,b,c))
# puts
# end
answered Aug 9, 2020 at 11:50
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.