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
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
). Thecolorize
gem is also, though ti does add a dependency to your code. \$\endgroup\$