Quellcode durchsuchen

Added support for fractions with negation and addition of negative numbers.

Sander Mathijs van Veen vor 14 Jahren
Ursprung
Commit
c5b2a5c6ab

+ 7 - 0
src/node.py

@@ -189,6 +189,9 @@ class ExpressionNode(Node, ExpressionBase):
         >>> n2 = N('*', N('^', r, e), c)
         >>> n2 = N('*', N('^', r, e), c)
         >>> n2.extract_polynome()
         >>> n2.extract_polynome()
         (c, r, e)
         (c, r, e)
+        >>> n3 = N('-', r)
+        >>> n3.extract_polynome()
+        (1, -r, 1)
         """
         """
         # TODO: change "get_polynome" -> "extract_polynome".
         # TODO: change "get_polynome" -> "extract_polynome".
         # TODO: change retval of c * r ^ e to (c, r, e).
         # TODO: change retval of c * r ^ e to (c, r, e).
@@ -198,6 +201,10 @@ class ExpressionNode(Node, ExpressionBase):
         if self.is_power():
         if self.is_power():
             return (ExpressionLeaf(1), self[0], self[1])
             return (ExpressionLeaf(1), self[0], self[1])
 
 
+        # rule: -r -> (1, r, 1)
+        if self.is_op(OP_NEG):
+            return (ExpressionLeaf(1), -self[0], ExpressionLeaf(1))
+
         if self.op != OP_MUL:
         if self.op != OP_MUL:
             return
             return
 
 

+ 32 - 10
src/rules/fractions.py

@@ -1,7 +1,7 @@
 from itertools import combinations
 from itertools import combinations
 
 
 from .utils import nary_node, least_common_multiple
 from .utils import nary_node, least_common_multiple
-from ..node import ExpressionLeaf as L, OP_DIV, OP_ADD, OP_MUL
+from ..node import ExpressionLeaf as L, OP_DIV, OP_ADD, OP_MUL, OP_NEG
 from ..possibilities import Possibility as P, MESSAGES
 from ..possibilities import Possibility as P, MESSAGES
 from ..translate import _
 from ..translate import _
 
 
@@ -63,15 +63,23 @@ def match_add_constant_fractions(node):
     1 / 2 + 3 / 4  ->  2 / 4 + 3 / 4  # Equalize denominators
     1 / 2 + 3 / 4  ->  2 / 4 + 3 / 4  # Equalize denominators
     2 / 4 + 3 / 4  ->  5 / 4          # Equal denominators, so nominators can
     2 / 4 + 3 / 4  ->  5 / 4          # Equal denominators, so nominators can
                                       # be added
                                       # be added
+    2 / 2 - 3 / 4  ->  4 / 4 - 3 / 4  # Equalize denominators
+    2 / 4 - 3 / 4  ->  -1 / 4         # Equal denominators, so nominators can
+                                      # be subtracted
     """
     """
     assert node.is_op(OP_ADD)
     assert node.is_op(OP_ADD)
 
 
     p = []
     p = []
-    fractions = filter(lambda n: n.is_op(OP_DIV), node.get_scope())
+
+    def is_division(node):
+        return node.is_op(OP_DIV) or \
+                (node.is_op(OP_NEG) and node[0].is_op(OP_DIV))
+
+    fractions = filter(is_division, node.get_scope())
 
 
     for a, b in combinations(fractions, 2):
     for a, b in combinations(fractions, 2):
-        na, da = a
-        nb, db = b
+        na, da = a if a.is_op(OP_DIV) else a[0]
+        nb, db = b if b.is_op(OP_DIV) else b[0]
 
 
         if da == db:
         if da == db:
             # Equal denominators, add nominators to create a single fraction
             # Equal denominators, add nominators to create a single fraction
@@ -96,28 +104,40 @@ def equalize_denominators(root, args):
     scope = root.get_scope()
     scope = root.get_scope()
 
 
     for fraction in args[:2]:
     for fraction in args[:2]:
-        n, d = fraction
+        n, d = fraction[0] if fraction.is_op(OP_NEG) else fraction
         mult = denom / d.value
         mult = denom / d.value
 
 
         if mult != 1:
         if mult != 1:
             n = L(n.value * mult) if n.is_numeric() else L(mult) * n
             n = L(n.value * mult) if n.is_numeric() else L(mult) * n
-            scope[scope.index(fraction)] = n / L(d.value * mult)
+
+            if fraction.is_op(OP_NEG):
+                scope[scope.index(fraction)] = -(n / L(d.value * mult))
+            else:
+                scope[scope.index(fraction)] = n / L(d.value * mult)
 
 
     return nary_node('+', scope)
     return nary_node('+', scope)
 
 
 
 
 def add_nominators(root, args):
 def add_nominators(root, args):
     """
     """
-    a / b + c / b  ->  (a + c) / b
+    a / b + c / b     ->  (a + c) / b
+    a / b + (-c / b)  ->  (a + (-c)) / b
     """
     """
+    # TODO: is 'add' Appropriate when rewriting to "(a + (-c)) / b"?
     ab, cb = args
     ab, cb = args
     a, b = ab
     a, b = ab
-    c = cb[0]
+
+    if cb[0].is_op(OP_NEG):
+        c = cb[0][0]
+        substitution = (a + (-c)) / b
+    else:
+        c = cb[0]
+        substitution = (a + c) / b
 
 
     scope = root.get_scope()
     scope = root.get_scope()
 
 
     # Replace the left node with the new expression
     # Replace the left node with the new expression
-    scope[scope.index(ab)] = (a + c) / b
+    scope[scope.index(ab)] = substitution
 
 
     # Remove the right node
     # Remove the right node
     scope.remove(cb)
     scope.remove(cb)
@@ -127,8 +147,10 @@ def add_nominators(root, args):
 
 
 def match_expand_and_add_fractions(node):
 def match_expand_and_add_fractions(node):
     """
     """
-    a * b / c + d * b / c  ->  (a + d) * (b / c)
+    a * b / c + d * b / c      ->  (a + d) * (b / c)
+    a * b / c + (- d * b / c)  ->  (a + (-d)) * (b / c)
     """
     """
+    # TODO: is 'add' Appropriate when rewriting to "(a + (-d)) / * (b / c)"?
     assert node.is_op(OP_MUL)
     assert node.is_op(OP_MUL)
 
 
     p = []
     p = []

+ 1 - 0
src/rules/groups.py

@@ -18,6 +18,7 @@ def match_combine_groups(node):
     ab + 2ab  ->  3ab
     ab + 2ab  ->  3ab
     ab + ba   ->  2ab
     ab + ba   ->  2ab
     """
     """
+    # TODO: handle OP_NEG nodes
     assert node.is_op(OP_ADD)
     assert node.is_op(OP_ADD)
 
 
     p = []
     p = []

+ 8 - 2
src/rules/numerics.py

@@ -1,7 +1,7 @@
 from itertools import combinations
 from itertools import combinations
 
 
 from .utils import nary_node
 from .utils import nary_node
-from ..node import ExpressionLeaf as Leaf, OP_DIV, OP_MUL
+from ..node import ExpressionLeaf as Leaf, OP_DIV, OP_MUL, OP_NEG
 from ..possibilities import Possibility as P, MESSAGES
 from ..possibilities import Possibility as P, MESSAGES
 from ..translate import _
 from ..translate import _
 
 
@@ -11,10 +11,16 @@ def add_numerics(root, args):
     Combine two constants to a single constant in an n-ary addition.
     Combine two constants to a single constant in an n-ary addition.
 
 
     Example:
     Example:
-    2 + 3  ->  5
+    2 + 3    ->  5
+    2 + -3   ->  -1
+    -2 + 3   ->  1
+    -2 + -3  ->  -5
     """
     """
     n0, n1, c0, c1 = args
     n0, n1, c0, c1 = args
 
 
+    c0 = (-c0[0].value) if c0.is_op(OP_NEG) else c0.value
+    c1 = (-c1[0].value) if c1.is_op(OP_NEG) else c1.value
+
     scope = root.get_scope()
     scope = root.get_scope()
 
 
     # Replace the left node with the new expression
     # Replace the left node with the new expression

+ 11 - 4
src/rules/poly.py

@@ -1,11 +1,15 @@
 from itertools import combinations
 from itertools import combinations
 
 
-from ..node import OP_ADD
+from ..node import OP_ADD, OP_NEG
 from ..possibilities import Possibility as P, MESSAGES
 from ..possibilities import Possibility as P, MESSAGES
 from .utils import nary_node
 from .utils import nary_node
 from .numerics import add_numerics
 from .numerics import add_numerics
 
 
 
 
+def is_numeric_or_negated_numeric(n):
+    return n.is_numeric() or (n.is_op(OP_NEG) and n[0].is_numeric())
+
+
 def match_combine_polynomes(node, verbose=False):
 def match_combine_polynomes(node, verbose=False):
     """
     """
     n + exp + m -> exp + (n + m)
     n + exp + m -> exp + (n + m)
@@ -49,9 +53,12 @@ def match_combine_polynomes(node, verbose=False):
             # roots, or: same root and exponent -> combine coefficients.
             # roots, or: same root and exponent -> combine coefficients.
             # TODO: Addition with zero, e.g. a + 0 -> a
             # TODO: Addition with zero, e.g. a + 0 -> a
             if c0 == 1 and c1 == 1 and e0 == 1 and e1 == 1 \
             if c0 == 1 and c1 == 1 and e0 == 1 and e1 == 1 \
-                    and r0.is_numeric() and r1.is_numeric():
-                # 2 + 3 -> 5
-                p.append(P(node, add_numerics, (n0, n1, r0.value, r1.value)))
+                    and all(map(is_numeric_or_negated_numeric, [r0, r1])):
+                # 2 + 3    ->  5
+                # 2 + -3   ->  -1
+                # -2 + 3   ->  1
+                # -2 + -3  ->  -5
+                p.append(P(node, add_numerics, (n0, n1, r0, r1)))
             elif c0.is_numeric() and c1.is_numeric() and r0 == r1 and e0 == e1:
             elif c0.is_numeric() and c1.is_numeric() and r0 == r1 and e0 == e1:
                 # 2a + 2a -> 4a
                 # 2a + 2a -> 4a
                 # a + 2a -> 3a
                 # a + 2a -> 3a

+ 2 - 0
tests/parser.py

@@ -120,6 +120,8 @@ def apply_expressions(base_class, expressions, fail=True, silent=False,
 
 
             if fail:
             if fail:
                 raise
                 raise
+
+
 def graph(parser, *exp, **kwargs):
 def graph(parser, *exp, **kwargs):
     return generate_graph(ParserWrapper(parser, **kwargs).run(exp))
     return generate_graph(ParserWrapper(parser, **kwargs).run(exp))
 
 

+ 3 - 1
tests/test_leiden_oefenopgave.py

@@ -32,7 +32,9 @@ class TestLeidenOefenopgave(TestCase):
                 ('2/15 + 1/4',      '8 / 60 + 15 / 60'),
                 ('2/15 + 1/4',      '8 / 60 + 15 / 60'),
                 ('8/60 + 15/60',    '(8 + 15) / 60'),
                 ('8/60 + 15/60',    '(8 + 15) / 60'),
                 ('(8 + 15) / 60',   '23 / 60'),
                 ('(8 + 15) / 60',   '23 / 60'),
-                # FIXME: ('2/7 - 4/11',      '-6 / 77'),
+                ('2/7 - 4/11',      '22 / 77 + -28 / 77'),
+                ('22/77 + -28/77',  '(22 + -28) / 77'),
+                ('(22 + -28)/77',    '-6 / 77'),
                 # FIXME: ('(7/3) * (3/5)',   '7 / 5'),
                 # FIXME: ('(7/3) * (3/5)',   '7 / 5'),
                 # FIXME: ('(3/4) / (5/6)',   '9 / 10'),
                 # FIXME: ('(3/4) / (5/6)',   '9 / 10'),
                 # FIXME: ('1/4 * 1/x',       '1 / (4x)'),
                 # FIXME: ('1/4 * 1/x',       '1 / (4x)'),

+ 4 - 0
tests/test_rewrite.py

@@ -24,3 +24,7 @@ class TestRewrite(TestCase):
 
 
     def test_addition_identifiers_rewrite(self):
     def test_addition_identifiers_rewrite(self):
         self.assertRewrite(['2 + 3a + 4', '6 + 3a'])
         self.assertRewrite(['2 + 3a + 4', '6 + 3a'])
+
+    def test_division_rewrite(self):
+        self.assertRewrite(['2/7 - 4/11', '22 / 77 + -28 / 77',
+                            '(22 + -28) / 77', '-6 / 77'])

+ 28 - 1
tests/test_rules_fractions.py

@@ -68,6 +68,19 @@ class TestRulesFractions(RulesTestCase):
         self.assertEqualPos(possibilities,
         self.assertEqualPos(possibilities,
                 [P(root, add_nominators, (n1, n3))])
                 [P(root, add_nominators, (n1, n3))])
 
 
+    def test_add_constant_fractions_with_negation(self):
+        a, b, c, l1, l2, l3, l4 = tree('a,b,c,1,2,3,4')
+
+        (((n0, n1), n2), n3), n4 = root = a + l2 / l2 + b + (-l3 / l4) + c
+        possibilities = match_add_constant_fractions(root)
+        self.assertEqualPos(possibilities,
+                [P(root, equalize_denominators, (n1, n3, 4))])
+
+        (((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):
     def test_equalize_denominators(self):
         a, b, l1, l2, l3, l4 = tree('a,b,1,2,3,4')
         a, b, l1, l2, l3, l4 = tree('a,b,1,2,3,4')
 
 
@@ -79,8 +92,22 @@ class TestRulesFractions(RulesTestCase):
         self.assertEqualNodes(equalize_denominators(root, (n0, n1, 4)),
         self.assertEqualNodes(equalize_denominators(root, (n0, n1, 4)),
                               (l2 * a) / l4 + b / l4)
                               (l2 * a) / l4 + b / l4)
 
 
+        #2 / 2 - 3 / 4  ->  4 / 4 - 3 / 4  # Equalize denominators
+        n0, n1 = root = l1 / l2 + (-l3 / l4)
+        self.assertEqualNodes(equalize_denominators(root, (n0, n1, 4)),
+                              l2 / l4 + (-l3 / l4))
+
+        #2 / 2 - 3 / 4  ->  4 / 4 - 3 / 4  # Equalize denominators
+        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):
     def test_add_nominators(self):
         a, b, c = tree('a,b,c')
         a, b, c = tree('a,b,c')
         n0, n1 = root = a / b + c / b
         n0, n1 = root = a / b + c / b
-
         self.assertEqualNodes(add_nominators(root, (n0, n1)), (a + c) / b)
         self.assertEqualNodes(add_nominators(root, (n0, n1)), (a + c) / b)
+
+        #2 / 4 + 3 / -4  ->  2 / 4 + -3 / 4
+        #2 / 4 - 3 / 4  ->  -1 / 4  # Equal denominators, so nominators can
+        n0, n1 = root = a / b + (-c / b)
+        self.assertEqualNodes(add_nominators(root, (n0, n1)), (a + (-c)) / b)

+ 10 - 2
tests/test_rules_numerics.py

@@ -10,8 +10,16 @@ class TestRulesNumerics(RulesTestCase):
     def test_add_numerics(self):
     def test_add_numerics(self):
         l0, a, l1 = tree('1,a,2')
         l0, a, l1 = tree('1,a,2')
 
 
-        self.assertEqual(add_numerics(l0 + l1, (l0, l1, 1, 2)), 3)
-        self.assertEqual(add_numerics(l0 + a + l1, (l0, l1, 1, 2)), L(3) + a)
+        self.assertEqual(add_numerics(l0 + l1, (l0, l1, L(1), L(2))), 3)
+        self.assertEqual(add_numerics(l0 + a + l1, (l0, l1, L(1), L(2))),
+                         L(3) + a)
+
+    def test_add_numerics_negations(self):
+        l0, a, l1 = tree('1,a,2')
+
+        self.assertEqual(add_numerics(l0 + -l1, (l0, -l1, L(1), -L(2))), -1)
+        self.assertEqual(add_numerics(l0 + a + -l1, (l0, -l1, L(1), -L(2))),
+                         L(-1) + a)
 
 
     def test_match_divide_numerics(self):
     def test_match_divide_numerics(self):
         a, b, i2, i3, i6, f1, f2, f3 = tree('a,b,2,3,6,1.0,2.0,3.0')
         a, b, i2, i3, i6, f1, f2, f3 = tree('a,b,2,3,6,1.0,2.0,3.0')

+ 0 - 2
tests/test_rules_poly.py

@@ -36,14 +36,12 @@ class TestRulesPoly(RulesTestCase):
         self.assertEqualPos(possibilities,
         self.assertEqualPos(possibilities,
                 [P(root, combine_polynomes, (a1, a2, 2, 1, 'a', 3))])
                 [P(root, combine_polynomes, (a1, a2, 2, 1, 'a', 3))])
 
 
-
     def test_identifiers_coeff_exponent_both(self):
     def test_identifiers_coeff_exponent_both(self):
         a1, a2 = root = tree('2a3+2a3')
         a1, a2 = root = tree('2a3+2a3')
         possibilities = match_combine_polynomes(root)
         possibilities = match_combine_polynomes(root)
         self.assertEqualPos(possibilities,
         self.assertEqualPos(possibilities,
                 [P(root, combine_polynomes, (a1, a2, 2, 2, 'a', 3))])
                 [P(root, combine_polynomes, (a1, a2, 2, 2, 'a', 3))])
 
 
-
     def test_basic_subexpressions(self):
     def test_basic_subexpressions(self):
         a_b, c, d = tree('a+b,c,d')
         a_b, c, d = tree('a+b,c,d')
         left, right = root = tree('(a+b)^d + (a+b)^d')
         left, right = root = tree('(a+b)^d + (a+b)^d')