patternpythonMinor
Mixed number fractions class
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,
```
#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
- 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.
- 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 resultdef 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.