Przeglądaj źródła

Added some more fraction rewrite rules.

Taddeus Kroes 14 lat temu
rodzic
commit
f0701c3a9a
3 zmienionych plików z 152 dodań i 5 usunięć
  1. 5 3
      src/rules/__init__.py
  2. 95 1
      src/rules/fractions.py
  3. 52 1
      tests/test_rules_fractions.py

+ 5 - 3
src/rules/__init__.py

@@ -4,12 +4,14 @@ from .powers import match_add_exponents, match_subtract_exponents, \
         match_multiply_exponents, match_duplicate_exponent, \
         match_remove_negative_exponent, match_exponent_to_root
 from .numerics import match_divide_numerics
-from .fractions import match_constant_division
+from .fractions import match_constant_division, match_add_constant_fractions, \
+        match_expand_and_add_fractions
 
 
 RULES = {
-        OP_ADD: [match_combine_polynomes],
-        OP_MUL: [match_expand, match_add_exponents],
+        OP_ADD: [match_add_constant_fractions, match_combine_polynomes],
+        OP_MUL: [match_expand, match_add_exponents, \
+                 match_expand_and_add_fractions],
         OP_DIV: [match_subtract_exponents, match_divide_numerics, \
                  match_constant_division],
         OP_POW: [match_multiply_exponents, match_duplicate_exponent, \

+ 95 - 1
src/rules/fractions.py

@@ -1,4 +1,7 @@
-from ..node import ExpressionLeaf as L, OP_DIV
+from itertools import combinations
+
+from .utils import nary_node, least_common_multiple
+from ..node import ExpressionLeaf as L, OP_DIV, OP_ADD, OP_MUL
 from ..possibilities import Possibility as P, MESSAGES
 from ..translate import _
 
@@ -8,6 +11,7 @@ def match_constant_division(node):
     a / 0  ->  Division by zero
     a / 1  ->  a
     0 / a  ->  0
+    a / a  ->  1
     """
     assert node.is_op(OP_DIV)
 
@@ -15,6 +19,7 @@ def match_constant_division(node):
     nominator, denominator = node
 
     # a / 0
+    # TODO: move to parser
     if denominator == 0:
         raise ZeroDivisionError()
 
@@ -26,6 +31,10 @@ def match_constant_division(node):
     if nominator == 0:
         p.append(P(node, division_of_zero))
 
+    # a / a
+    if nominator == denominator:
+        p.append(P(node, division_by_self))
+
     return p
 
 
@@ -41,3 +50,88 @@ def division_of_zero(root, args):
     0 / a  ->  0
     """
     return L(0)
+
+
+def division_by_self(root, args):
+    """
+    a / a  ->  1
+    """
+    return L(1)
+
+
+def match_add_constant_fractions(node):
+    """
+    1 / 2 + 3 / 4  ->  2 / 4 + 3 / 4  # Equalize denominators
+    2 / 4 + 3 / 4  ->  5 / 4          # Equal denominators, so nominators can
+                                      # be added
+    """
+    assert node.is_op(OP_ADD)
+
+    p = []
+    fractions = filter(lambda n: n.is_op(OP_DIV), node.get_scope())
+
+    for a, b in combinations(fractions, 2):
+        na, da = a
+        nb, db = b
+
+        if da == db:
+            # Equal denominators, add nominators to create a single fraction
+            p.append(P(node, add_nominators, (a, b)))
+        elif da.is_numeric() and db.is_numeric():
+            # Denominators are both numeric, rewrite both fractions to the
+            # least common multiple of their denominators. Later, the
+            # nominators will be added
+            denom = least_common_multiple(da.value, db.value)
+            p.append(P(node, equalize_denominators, (a, b, denom)))
+
+    return p
+
+
+def equalize_denominators(root, args):
+    """
+    1 / 2 + 3 / 4  ->  2 / 4 + 3 / 4
+    a / 2 + b / 4  ->  2a / 4 + b / 4
+    """
+    denom = args[2]
+
+    scope = root.get_scope()
+
+    for fraction in args[:2]:
+        n, d = fraction
+        mult = denom / d.value
+
+        if mult != 1:
+            n = L(n.value * mult) if n.is_numeric() else L(mult) * n
+            scope[scope.index(fraction)] = n / L(d.value * mult)
+
+    return nary_node('+', scope)
+
+
+def add_nominators(root, args):
+    """
+    a / b + c / b  ->  (a + c) / b
+    """
+    ab, cb = args
+    a, b = ab
+    c = cb[0]
+
+    scope = root.get_scope()
+
+    # Replace the left node with the new expression
+    scope[scope.index(ab)] = (a + c) / b
+
+    # Remove the right node
+    scope.remove(cb)
+
+    return nary_node('+', scope)
+
+
+def match_expand_and_add_fractions(node):
+    """
+    a * b / c + d * b / c  ->  (a + d) * (b / c)
+    """
+    assert node.is_op(OP_MUL)
+
+    p = []
+
+    return p

+ 52 - 1
tests/test_rules_fractions.py

@@ -1,5 +1,6 @@
 from src.rules.fractions import match_constant_division, division_by_one, \
-        division_of_zero
+        division_of_zero, division_by_self, match_add_constant_fractions, \
+        equalize_denominators, add_nominators
 from src.possibilities import Possibility as P
 from tests.test_rules_poly import tree
 from tests.rulestestcase import RulesTestCase
@@ -21,6 +22,10 @@ class TestRulesFractions(RulesTestCase):
         possibilities = match_constant_division(root)
         self.assertEqualPos(possibilities, [P(root, division_of_zero)])
 
+        root = a / a
+        possibilities = match_constant_division(root)
+        self.assertEqualPos(possibilities, [P(root, division_by_self)])
+
     def test_division_by_one(self):
         a = tree('a')
         root = a / 1
@@ -32,3 +37,49 @@ class TestRulesFractions(RulesTestCase):
         root = zero / a
 
         self.assertEqualNodes(division_of_zero(root, ()), zero)
+
+    def test_division_by_self(self):
+        a, one = tree('a,1')
+        root = a / a
+
+        self.assertEqualNodes(division_by_self(root, ()), one)
+
+    def test_match_add_constant_fractions(self):
+        a, b, c, l1, l2, l3, l4 = tree('a,b,c,1,2,3,4')
+
+        n0, n1 = root = l1 / l2 + l3 / l4
+        possibilities = match_add_constant_fractions(root)
+        self.assertEqualPos(possibilities,
+                [P(root, equalize_denominators, (n0, n1, 4))])
+
+        (((n0, n1), n2), n3), n4 = root = a + l1 / l2 + b + l3 / l4 + c
+        possibilities = match_add_constant_fractions(root)
+        self.assertEqualPos(possibilities,
+                [P(root, equalize_denominators, (n1, n3, 4))])
+
+        n0, n1 = root = l2 / l4 + l3 / l4
+        possibilities = match_add_constant_fractions(root)
+        self.assertEqualPos(possibilities,
+                [P(root, add_nominators, (n0, n1))])
+
+        (((n0, n1), n2), n3), n4 = root = a + l2 / l4 + b + l3 / l4 + c
+        possibilities = match_add_constant_fractions(root)
+        self.assertEqualPos(possibilities,
+                [P(root, add_nominators, (n1, n3))])
+
+    def test_equalize_denominators(self):
+        a, b, l1, l2, l3, l4 = tree('a,b,1,2,3,4')
+
+        n0, n1 = root = l1 / l2 + l3 / l4
+        self.assertEqualNodes(equalize_denominators(root, (n0, n1, 4)),
+                              l2 / l4 + l3 / l4)
+
+        n0, n1 = root = a / l2 + b / l4
+        self.assertEqualNodes(equalize_denominators(root, (n0, n1, 4)),
+                              (l2 * a) / l4 + b / l4)
+
+    def test_add_nominators(self):
+        a, b, c = tree('a,b,c')
+        n0, n1 = root = a / b + c / b
+
+        self.assertEqualNodes(add_nominators(root, (n0, n1)), (a + c) / b)