Răsfoiți Sursa

Implemented retrieval possibilities successfully.

Taddeus Kroes 14 ani în urmă
părinte
comite
0d06e0ae67
5 a modificat fișierele cu 155 adăugiri și 101 ștergeri
  1. 23 15
      src/node.py
  2. 6 1
      src/possibilities.py
  3. 66 75
      src/rules/poly.py
  4. 10 10
      tests/test_node.py
  5. 50 0
      tests/test_rules_poly.py

+ 23 - 15
src/node.py

@@ -93,25 +93,29 @@ class ExpressionNode(Node, ExpressionBase):
         node.parent = self.parent
         self.parent = None
 
-    def get_order(self):
-        if self.is_power() and self[0].is_identifier() and self[1].is_leaf():
-            # a ^ 3
-            return (self[0].value, self[1].value, 1)
-
-        if not self.op & OP_MUL:
+    def get_polynome(self):
+        """
+        Identifier nodes of all polynomes, tuple format is:
+        (identifier, exponent, coefficient, literal_exponent)
+        """
+        if self.is_power():
+            # a ^ e
+            return (self[0], self[1], ExpressionLeaf(1), True)
+
+        if self.op != OP_MUL:
             return
 
         for n0, n1 in [(0, 1), (1, 0)]:
             if self[n0].is_numeric():
                 if self[n1].is_identifier():
-                    # 2 * a
-                    return (self[n1].value, 1, self[n0].value)
+                    # c * a
+                    return (self[n1], ExpressionLeaf(1), self[n0], False)
                 elif self[n1].is_power():
-                    # 2 * a ^ 3
+                    # c * a ^ e
                     coeff, power = self
+                    root, exponent = power
 
-                    if power[0].is_identifier() and power[1].is_leaf():
-                        return (power[0].value, power[1].value, coeff.value)
+                    return (root, exponent, coeff, True)
 
     def get_scope(self):
         """"""
@@ -119,7 +123,7 @@ class ExpressionNode(Node, ExpressionBase):
         #op = OP_ADD | OP_SUB if self.op & (OP_ADD | OP_SUB) else self.op
 
         for child in self:
-            if not child.is_leaf() and child.op & self.op:
+            if not child.is_leaf() and child.op == self.op:
                 scope += child.get_scope()
             else:
                 scope.append(child)
@@ -133,9 +137,13 @@ class ExpressionLeaf(Leaf, ExpressionBase):
 
         self.type = TYPE_MAP[type(args[0])]
 
-    def get_order(self):
-        if self.is_identifier():
-            return (self.value, 1, 1)
+    def get_polynome(self):
+        """
+        Identifier nodes of all polynomes, tuple format is:
+        (identifier, exponent, coefficient, literal_exponent)
+        """
+        # a = 1 * a ^ 1
+        return (self, ExpressionLeaf(1), ExpressionLeaf(1), False)
 
     def replace(self, node):
         if not hasattr(self, 'parent'):

+ 6 - 1
src/possibilities.py

@@ -11,9 +11,14 @@ class Possibility(object):
     def __repr__(self):
         return str(self)
 
+    # TODO: Add unit tests
     def __eq__(self, other):
+        self_arg0, self_arg1 = zip(*self.args)
+        other_arg0, other_arg1 = zip(*other.args)
+
         return self.handler == other.handler \
-               and map(hash, self.args) == map(hash, other.args)
+               and self_arg1 == other_arg1 \
+               and map(hash, self_arg0) == map(hash, other_arg0)
 
 
 def filter_duplicates(items):

+ 66 - 75
src/rules/poly.py

@@ -11,28 +11,25 @@ def match_expand(node):
     a * (b + c) -> ab + ac
     """
     assert node.type == TYPE_OPERATOR
-    assert node.op & OP_MUL
+    assert node.op == OP_MUL
 
+    # TODO: fix!
     return []
-    p = []
-
-    # 'a' parts
-    left = []
 
-    # '(b + c)' parts
-    right = []
+    p = []
+    a = []
+    bc = []
 
     for n in node.get_scope():
-        if node.type == TYPE_OPERATOR:
-            if n.op & OP_ADD:
-                right.append(n)
-        else:
-            left.append(n)
+        if n.is_leaf():
+            a.append(n)
+        elif n.op == OP_ADD:
+            bc.append(n)
 
-    if len(left) and len(right):
-        for l in left:
-            for r in right:
-                p.append(P(node, expand_single, l, r))
+    if a and bc:
+        for a_node in a:
+            for bc_node in bc:
+                p.append(P(node, expand_single, a_node, bc_node))
 
     return p
 
@@ -64,83 +61,77 @@ def match_combine_factors(node):
     k0 * v ^ n + exp + k1 * v ^ n -> exp + (k0 + k1) * v ^ n
     """
     assert node.type == TYPE_OPERATOR
-    assert node.op & OP_ADD
+    assert node.op == OP_ADD
 
     p = []
 
-    # Collect all nodes that can be combined
-    # Numeric leaves
-    numerics = []
-
-    # Identifier leaves of all orders, tuple format is;
-    # (identifier, exponent, coefficient)
-    orders = []
+    # Collect all nodes that can be combined:
+    # a ^ e     = 1 * a ^ e
+    # c * a     = c * a ^ 1
+    # c * a ^ e
+    # a         = 1 * a ^ 1
+    #
+    # Identifier nodes of all polynomes, tuple format is:
+    #   (identifier, exponent, coefficient, literal_coefficient)
+    polys = []
 
     for n in node.get_scope():
-        if not n.is_leaf():
-            order = n.get_order()
-
-            if order:
-                orders.append(order)
-        elif n.is_numeric():
-            numerics.append(n)
-        elif n.is_identifier():
-            orders.append((n.value, 1, 1))
-
-    if len(numerics) > 1:
-        for num0, num1 in combinations(numerics, 2):
-            p.append(P(node, combine_numerics, (num0, num1)))
-
-    if len(orders) > 1:
-        for order0, order1 in combinations(orders, 2):
-            id0, exponent0, coeff0 = order0
-            id1, exponent1, coeff1 = order1
-
-            if id0 == id1 and exponent0 == exponent1:
-                # Same identifier and exponent -> combine coefficients
-                args = order0 + (coeff1,)
-                p.append(P(node, combine_orders, args))
-
-    return p
+        polynome = n.get_polynome()
 
+        if polynome:
+            polys.append((n, polynome))
 
-def combine_numerics(root, args):
-    """
-    Combine two numeric leaves in an n-ary plus.
+    # Each combination of powers of the same value and polynome can be added
+    if len(polys) >= 2:
+        for left, right in combinations(polys, 2):
+            r0, e0, c0 = left[1][:3]
+            r1, e1, c1 = right[1][:3]
 
-    Example:
-    >>> 3 + 4 -> 7
-    """
-    others = list(set(root.get_scope()) - set(args))
-    value = sum([n.value for n in args])
+            if (r0.is_numeric() and r1.is_numeric() and e0 == e1 == Leaf(1)) \
+                    or (r0 == r1 and e0 == e1):
+                # Both numeric and same exponent -> combine coefficients and
+                # roots, or:
+                # Same identifier and exponent -> combine coefficients
+                p.append(P(node, combine_polynomes, (left, right)))
 
-    return nary_node('+', others + [Leaf(value)])
+    return p
 
 
-def combine_orders(root, args):
+def combine_polynomes(root, args):
     """
-    Combine two identifier multiplications of any order in an n-ary plus.
+    Combine two identifier multiplications of any polynome in an n-ary plus.
 
     Example:
-    3x + 4x -> 7x
+    c * a ^ b + d * a ^ b -> (c + d) * a ^ b
     """
-    identifier, exponent, coeff0, coeff1, others = args
+    left, right = args
+    nl, pl = left
+    nr, pr = right
+    c0, r0, e0 = pl
+    c1, r1, e1 = pr
 
-    coeff = coeff0 + coeff1
+    scope = root.get_scope()
 
-    if not exponent:
-        # a ^ 0 -> 1
-        ident = Leaf(1)
-    elif exponent == 1:
-        # a ^ 1 -> a
-        ident = Leaf(identifier)
+    if r0.is_numeric() and r1.is_numeric() and e0 == e1 == 1:
+        new_root = Leaf(r0.value + r1.value)
     else:
-        # a ^ n -> a ^ n
-        ident = Node('^', Leaf(identifier), Leaf(exponent))
+        new_root = r0
 
-    if coeff == 1:
-        combined = ident
+    if pl[3] or pr[3]:
+        # literal a ^ 1 -> a ^ 1
+        power = Node('^', pl[0], pl[1])
     else:
-        combined = Node('*', Leaf(coeff), ident)
+        # nonliteral a ^ 1 -> a
+        power = pl[0]
+
+    # replacement: (c + d) * a ^ b
+    # a, b and c are from 'left', d is from 'right'.
+    replacement = Node('*', Node('+', pl[2], pr[2]), power)
+
+    # Replace the left node with the new expression
+    scope[scope.index(nl)] = replacement
+
+    # Remove the right node
+    scope.remove(nr)
 
-    return nary_node('+', others + [combined])
+    return nary_node('+', scope)

+ 10 - 10
tests/test_node.py

@@ -51,23 +51,23 @@ class TestNode(unittest.TestCase):
         self.assertTrue(L(1.5).is_numeric())
         self.assertFalse(L('a').is_numeric())
 
-    def test_get_order_identifier(self):
-        self.assertEqual(L('a').get_order(), ('a', 1, 1))
+    def test_get_polynome_identifier(self):
+        self.assertEqual(L('a').get_polynome(), (L('a'), L(1), L(1), False))
 
-    def test_get_order_None(self):
-        self.assertIsNone(L(1).get_order())
+    def test_get_polynome_None(self):
+        self.assertIsNone(N('+').get_polynome())
 
-    def test_get_order_power(self):
+    def test_get_polynome_power(self):
         power = N('^', L('a'), L(2))
-        self.assertEqual(power.get_order(), ('a', 2, 1))
+        self.assertEqual(power.get_polynome(), (L('a'), L(2), L(1), True))
 
-    def test_get_order_coefficient_exponent_int(self):
+    def test_get_polynome_coefficient_exponent_int(self):
         times = N('*', L(3), N('^', L('a'), L(2)))
-        self.assertEqual(times.get_order(), ('a', 2, 3))
+        self.assertEqual(times.get_polynome(), (L('a'), L(2), L(3), True))
 
-    def test_get_order_coefficient_exponent_id(self):
+    def test_get_polynome_coefficient_exponent_id(self):
         times = N('*', L(3), N('^', L('a'), L('b')))
-        self.assertEqual(times.get_order(), ('a', 'b', 3))
+        self.assertEqual(times.get_polynome(), (L('a'), L('b'), L(3), True))
 
     def test_get_scope_binary(self):
         plus = N('+', *self.l[:2])

+ 50 - 0
tests/test_rules_poly.py

@@ -0,0 +1,50 @@
+import unittest
+
+from src.node import ExpressionNode as N, ExpressionLeaf as L
+from src.rules.poly import match_combine_factors, combine_polynomes
+from src.possibilities import Possibility as P
+from src.parser import Parser
+from tests.parser import ParserWrapper
+
+
+def tree(exp, **kwargs):
+    return ParserWrapper(Parser, **kwargs).run([exp])
+
+
+class TestRulesPoly(unittest.TestCase):
+
+    #def test_match_combine_factors_numeric_combinations(self):
+    #    l0, l1, l2 = L(1), L(2), L(2)
+    #    plus = N('+', N('+', l0, l1), l2)
+    #    p = match_combine_factors(plus)
+    #    self.assertEqualPos(p, [P(plus, combine_polynomes, (l0, l1)),
+    #                            P(plus, combine_polynomes, (l0, l2)),
+    #                            P(plus, combine_polynomes, (l1, l2))])
+
+    def assertEqualPos(self, possibilities, expected):
+        self.assertEqual(len(possibilities), len(expected))
+
+        for p, e in zip(possibilities, expected):
+            self.assertEqual(p.root, e.root)
+            self.assertEqual(p, e)
+
+    def test_numeric(self):
+        l1, l2 = root = tree('1+2')
+        self.assertEqualPos(match_combine_factors(root),
+                [P(root, combine_polynomes, ((l1, (l1, l1, l1, False)),
+                                             (l2, (l2, l1, l1, False))))])
+
+        a1, a2 = root = tree('a+a')
+        self.assertEqualPos(match_combine_factors(root),
+                [P(root, combine_polynomes, ((a1, (a1, l1, l1, False)),
+                                             (a2, (a2, l1, l1, False))))])
+
+        a1, a2 = root = tree('a+2a')
+        self.assertEqualPos(match_combine_factors(root),
+                [P(root, combine_polynomes, ((a1, (a1, l1, l1, False)),
+                                             (a2, (a2[1], l1, l2, False))))])
+
+        a1, a2 = root = tree('a2+a2')
+        self.assertEqualPos(match_combine_factors(root),
+                [P(root, combine_polynomes, ((a1, (a1[0], l2, l1, True)),
+                                             (a2, (a2[0], l2, l1, True))))])