HiveBrain v1.2.0
Get Started
← Back to all entries
patternpythonMinor

Mixed number fractions class

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
numbermixedfractionsclass

Problem

I'm looking for bugs. I tried to test all functionality in a variety of ways.

```
#by JB0x2D1

from decimal import Decimal
import math
import numbers
import operator
from fractions import Fraction

class Mixed(Fraction):
"""This class implements Fraction, which implements rational numbers."""
# We're immutable, so use __new__ not __init__
def __new__(cls, whole=0, numerator=None, denominator=None):
"""Constructs a Rational.

Takes a string like '-1 2/3' or '1.5', another Rational instance, a
numerator/denominator pair, a float, or a whole number/numerator/
denominator set. If one or more non-zero arguments is negative,
all are treated as negative and the result is negative.

General behavior: whole number + (numerator / denominator)

Examples
--------

>>> Mixed(Mixed(-1,1,2), Mixed(0,1,2), Mixed(0,1,2))
Mixed(-2, 1, 2)
Note: The above call is similar to:
>>> Fraction(-3,2) + Fraction(Fraction(-1,2), Fraction(1,2))
Fraction(-5, 2)
>>> Mixed('-1 2/3')
Mixed(-1, 2, 3)
>>> Mixed(10,-8)
Mixed(-1, 1, 4)
>>> Mixed(Fraction(1,7), 5)
Mixed(0, 1, 35)
>>> Mixed(Mixed(1, 7), Fraction(2, 3))
Mixed(0, 3, 14)
>>> Mixed(Mixed(0, 3, 2), Fraction(2, 3), 2)
Mixed(1, 5, 6)
>>> Mixed('314')
Mixed(314, 0, 1)
>>> Mixed('-35/4')
Mixed(-8, 3, 4)
>>> Mixed('3.1415')
Mixed(3, 283, 2000)
>>> Mixed('-47e-2')
Mixed(0, -47, 100)
>>> Mixed(1.47)
Mixed(1, 2116691824864133, 4503599627370496)
>>> Mixed(2.25)
Mixed(2, 1, 4)
>>> Mixed(Decimal('1.47'))
Mixed(1, 47, 100)

"""
self = super(Fraction, cls).__new__(cls)

if (numerator is None) and (denominator is None): #single argument
if isinstance(whole, numbers.Rational) or \
isinstance(whole,

Solution


  1. Bugs



Your doctests do not pass:

$ python3.3 -mdoctest cr35274.py

File "./cr35274.py", line 76, in cr35274.Mixed.__new__
Failed example:
Mixed(Mixed(-1,1,2), Mixed(0,1,2), Mixed(0,1,2))
Expected:
Mixed(-2, 1, 2)
Note: The above call is similar to:
Got:
Mixed(-2, 1, 2)

File "./cr35274.py", line 97, in cr35274.Mixed.__new__
Failed example:
Mixed('-47e-2')
Expected:
Mixed(0, -47, 100)
Got:
Mixed(0, 47, 100)

1 items had failures:
2 of 14 in cr35274.Mixed.__new__
Test Failed 2 failures.


  1. Commentary



As far as I can see, there are really only two things that you are trying to achieve:

-
To create the mixed fraction a b/c from the string "a b/c". But instead of implementing a whole new class, why not just write a function to parse the string and return a Fraction?

import re
from fractions import Fraction

_MIXED_FORMAT = re.compile(r"""
    \A\s*                      # optional whitespace at the start, then
    (?P[-+]?)            # an optional sign, then
    (?P\d+)             # integer part
    \s+                        # whitespace
    (?P\d+)               # numerator
    /(?P\d+)            # denominator
    \s*\Z                      # and optional whitespace to finish
""", re.VERBOSE)

def mixed(s):
    """Parse the string s as a (possibly mixed) fraction.

        >>> mixed('1 2/3')
        Fraction(5, 3)
        >>> mixed(' -1 2/3 ')
        Fraction(-5, 3)
        >>> mixed('-0  12/15')
        Fraction(-4, 5)
        >>> mixed('+45/15')
        Fraction(3, 1)

    """
    m = _MIXED_FORMAT.match(s)
    if not m:
        return Fraction(s)
    d = m.groupdict()
    result = int(d['whole']) + Fraction(int(d['num']), int(d['denom']))
    if d['sign'] == '-':
        return -result
    else:
        return result


-
To format a fraction in mixed notation. But why not just write this as a function:

def format_mixed(f):
    """Format the fraction f as a (possibly) mixed fraction.

        >>> all(format_mixed(mixed(f)) == f for f in ['1 2/3', '-3 4/5', '7/8'])
        True

    """
    if abs(f) <= 1 or f.denominator == 1:
        return str(f)
    return '{0} {1.numerator}/{1.denominator}'.format(int(f), abs(f - int(f)))


The rest of your code seems unnecessary and complicated.

Code Snippets

import re
from fractions import Fraction

_MIXED_FORMAT = re.compile(r"""
    \A\s*                      # optional whitespace at the start, then
    (?P<sign>[-+]?)            # an optional sign, then
    (?P<whole>\d+)             # integer part
    \s+                        # whitespace
    (?P<num>\d+)               # numerator
    /(?P<denom>\d+)            # denominator
    \s*\Z                      # and optional whitespace to finish
""", re.VERBOSE)

def mixed(s):
    """Parse the string s as a (possibly mixed) fraction.

        >>> mixed('1 2/3')
        Fraction(5, 3)
        >>> mixed(' -1 2/3 ')
        Fraction(-5, 3)
        >>> mixed('-0  12/15')
        Fraction(-4, 5)
        >>> mixed('+45/15')
        Fraction(3, 1)

    """
    m = _MIXED_FORMAT.match(s)
    if not m:
        return Fraction(s)
    d = m.groupdict()
    result = int(d['whole']) + Fraction(int(d['num']), int(d['denom']))
    if d['sign'] == '-':
        return -result
    else:
        return result
def format_mixed(f):
    """Format the fraction f as a (possibly) mixed fraction.

        >>> all(format_mixed(mixed(f)) == f for f in ['1 2/3', '-3 4/5', '7/8'])
        True

    """
    if abs(f) <= 1 or f.denominator == 1:
        return str(f)
    return '{0} {1.numerator}/{1.denominator}'.format(int(f), abs(f - int(f)))

Context

StackExchange Code Review Q#35274, answer score: 2

Revisions (0)

No revisions yet.