1
\$\begingroup\$

Given an angle in degrees, output a pretty string representation of its value in radians as a fraction.

Where pretty means:

  • Simplified as much as possible.
  • With no unnecessary ones.
  • Using the Unicode character that represents pi: π
  • With a single minus in front if negative (a minus at the denominator is not allowed).

For example: degrees_to_pretty_radians(-120) #=> '-2π/3'


I wanted to write this in Python too to compare it to how it looked implemented in Javascript

The code is pretty straightforward and passes a lot of test-cases, but I am still interested in any kind of feedback:

def degrees_to_pretty_radians(angle: "in degrees") \
                    -> "Pretty string for the value in radians.":
    """
    >>> tests = [0, 1, 18, 31, 45, 60, 120, 180, 270, 360, 480]
    >>> all_tests = tests + [-x for x in tests]
    >>> for angle in all_tests: print(angle, degrees_to_pretty_radians(angle))
    0 0π
    1 π/180
    18 π/10
    31 31π/180
    45 π/4
    60 π/3
    120 2π/3
    180 π
    270 3π/2
    360 2π
    480 8π/3
    0 0π
    -1 -π/180
    -18 -π/10
    -31 -31π/180
    -45 -π/4
    -60 -π/3
    -120 -2π/3
    -180 -π
    -270 -3π/2
    -360 -2π
    -480 -8π/3

    """
    gcd = fractions.gcd(angle, 180)
    denominator = "" if 180 // gcd == 1 else "/{}".format(180 // gcd)
    numerator = "" if abs(angle // gcd) == 1 else abs(angle // gcd)
    sign = "-" if angle < 0 else ""
    return "{}{}π{}".format(sign, numerator, denominator)
\$\endgroup\$
2
\$\begingroup\$

Personally, function annotations are best used with types or almost-types. Unlike holroy, I appreciate their use, but like him I'd move what you put in the annotation inside the docstring. I'd instead use

def degrees_to_pretty_radians(angle: int) -> str

Alternatively you can use Real or Integral from numbers as the angle parameter.

Further, I'd use the argument name to clarify:

def pretty_radians(*, degrees: int) -> str

This way one writes

pretty_radians(degrees=1234)

instead of

degrees_to_pretty_radians(1234)

This is both more self-explanatory and easier to read.

\$\endgroup\$
3
\$\begingroup\$

Here are some thoughts on your code:

  • I'm not too fond of function annotations – Maybe it's just me, but I find the function annotations a slightly confusing syntax addition. I would rather extend the docstring with this information.
  • Compress the doctests a little – You get bonus points for using doctests, but I would compress the output somewhat. Maybe build an array of result values, and do a comparison against that. Or simply reduce the number of values. As it stands, it is more doctests code than actual code doing something useful
  • Avoid duplicating calculations – You do the 180 // gcd and abs(angle // gcd) twice. One for the if and one as a result. This could possibly read better using temporary variables to both avoid the double calculation, and to simplify the ternary statements.

However there is an even neater solution to avoid most of your code, and that is to use all of the capabilities of the fractions module. Using this module your function can be reduced to:

rads = fractions.Fraction(angle, 180)
return "{}π/{}".format(angle, rads.numerator, rads.denominator)

Of course, this does return mostly the same as your function returns, but it does return some of the basic cases like "0π/1" or "1π/3". These could be handled using some ternary operations, which leads us back to code similar to your original code (but without exposing the calculations, and with compression (not reduction) of test cases):

def degrees_to_pretty_radians(degrees):
    """Return the degrees in fractions of π:

    >>> tests = [0, 1, 18, 31, 45, 60, 120, 180, 270, 360, 480]
    >>> all_tests = tests + [-x for x in tests]
    >>> expected_result = ['0π', 'π/180', 'π/10', '31π/180',
    ...        'π/4', 'π/3', '2π/3', 'π', '3π/2', '2π', '8π/3',
    ...        '0π', '-π/180', '-π/10', '-31π/180',
    ...        '-π/4', '-π/3', '-2π/3', '-π', '-3π/2', '-2π', '-8π/3']
    >>> actual_result = [degrees_to_pretty_radians(d) for d in all_tests]
    >>> actual_result == expected_result
    True
    """

    rads = fractions.Fraction(degrees, 180)
    numerator = "" if rads.numerator == 1 else \
                "-" if rads.numerator == -1 else \
                rads.numerator
    denominator = "" if rads.denominator == 1 else \
                  "/{}".format(rads.denominator)
    return "{}π{}".format(numerator, denominator)
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.