Просмотр исходного кода

Added rules (and tests) for sorting polynomes and monomials.

Taddeus Kroes 13 лет назад
Родитель
Сommit
712dbdbcab
4 измененных файлов с 173 добавлено и 36 удалено
  1. 3 3
      src/rules/__init__.py
  2. 1 0
      src/rules/precedences.py
  3. 105 22
      src/rules/sort.py
  4. 64 11
      tests/test_rules_sort.py

+ 3 - 3
src/rules/__init__.py

@@ -14,7 +14,7 @@ from .fractions import match_constant_division, match_add_fractions, \
         match_extract_fraction_terms, match_division_in_denominator
 from .negation import match_negated_factor, match_negate_polynome, \
         match_negated_division
-from .sort import match_sort_polynome, match_sort_molinome
+from .sort import match_sort_polynome, match_sort_monomial
 from .goniometry import match_add_quadrants, match_negated_parameter, \
         match_half_pi_subtraction, match_standard_radian
 from .derivatives import match_zero_derivative, \
@@ -36,11 +36,11 @@ from .sqrt import match_reduce_sqrt
 RULES = {
         OP_ADD: [match_add_numerics, match_add_fractions,
                  match_combine_groups, match_add_quadrants,
-                 match_add_logarithms],
+                 match_add_logarithms, match_sort_polynome],
         OP_MUL: [match_multiply_numerics, match_expand, match_add_exponents,
                  match_multiply_zero, match_negated_factor,
                  match_multiply_fractions, match_factor_in_multiplicant,
-                 match_sort_molinome],
+                 match_sort_monomial],
         OP_DIV: [match_subtract_exponents, match_divide_numerics,
                  match_constant_division, match_divide_fractions,
                  match_negated_division, match_extract_fraction_terms,

+ 1 - 0
src/rules/precedences.py

@@ -98,4 +98,5 @@ IMPLICIT_RULES = [
         remove_power_of_one,
         negated_factor,
         add_numerics,
+        swap_factors,
         ]

+ 105 - 22
src/rules/sort.py

@@ -1,56 +1,139 @@
-from .utils import iter_pairs
+from .utils import iter_pairs, evals_to_numeric
 from ..node import ExpressionNode as N, Scope, OP_ADD, OP_MUL
 from ..possibilities import Possibility as P, MESSAGES
 from ..translate import _
 
 
-def swap_moni((left, right)):
-    left_num, right_num = left.is_numeric(), right.is_numeric()
+def get_power_prop(node):
+    """
+    Get the power properties of a node needed for sorting, being the variable
+    name and exponent value.
+    """
+    if node.is_variable():
+        return node.value, 1
 
-    if left_num and right_num:
-        return False
+    if node.is_power():
+        root, exponent = node
+
+        if root.is_variable():
+            if exponent.is_numeric():
+                # Numeric exponent: value can be compared to other exponents
+                exp = exponent.actual_value()
+            else:
+                # No numeric exponent: same precedence as the variable itself
+                exp = 1
+
+            return root.value, exp
+
+
+def swap_mono((left, right)):
+    """
+    Check if a pair of left and right multiplication factors in a monomial
+    should be swapped to be correctly sorted.
+    """
+    # Numerics should always be on the left of a monomial
+    if evals_to_numeric(right):
+        return not evals_to_numeric(left)
+
+    left_prop = get_power_prop(left)
+    right_prop = get_power_prop(right)
+
+    if left_prop and right_prop:
+        left_var, left_exp = left_prop
+        right_var, right_exp = right_prop
+
+        if left_var == right_var:
+            # Same variable, compare exponents
+            return left_exp > right_exp
+
+        # Compare variable names alphabetically
+        return left_var > right_var
+
+    return False
+
+
+def get_poly_prop(node):
+    """
+    Get the polynome properties of an monomial in a polynome, being the leading
+    variable name and the exponent it is raised to.
+    """
+    if node.is_op(OP_MUL):
+        scope = Scope(node)
+        power = None
 
-    left_var, right_var = left.is_variable(), right.is_variable()
+        for n in scope:
+            new_power = get_power_prop(n)
 
-    if left_var and right_var:
-        return cmp(left.value, right.value)
+            if new_power:
+                if not power:
+                    var, exp = power = new_power
+                    continue
 
-    if left_var and right_num:
-        return True
+                new_var, new_exp = new_power
+
+                if (new_exp > exp if new_var == var else new_var < var):
+                    var, exp = power = new_power
+
+        return power
+
+    return get_power_prop(node)
 
 
 def swap_poly((left, right)):
-    pass
+    """
+    Check if a pair of left and right addition factors in a polynomial should
+    be swapped to be correctly sorted.
+    """
+    left_poly = get_poly_prop(left)
+    right_poly = get_poly_prop(right)
 
+    if not left_poly:
+        return bool(right_poly)
+    elif not right_poly:
+        return False
 
-def match_sort_polynome(node):
+    left_var, left_exp = left_poly
+    right_var, right_exp = right_poly
+
+    if left_var == right_var:
+        # Same variable, compare exponents
+        return left_exp < right_exp
+
+    # Compare variable names alphabetically
+    return left_var > right_var
+
+
+def match_sort_monomial(node):
     """
+    Sort a monomial, pursuing the following form:
+    x^0 * x^1 * ... * x^n
+    For example: 2xx^2
     """
-    assert node.is_op(OP_ADD)
+    assert node.is_op(OP_MUL)
 
     scope = Scope(node)
 
     return [P(node, swap_factors, (scope, l, r))
-            for l, r in filter(swap_poly, iter_pairs(scope))]
+            for l, r in filter(swap_mono, iter_pairs(scope))]
 
 
-def match_sort_molinome(node):
+def match_sort_polynome(node):
     """
-    x * 2          ->  2x             # variable > number
-    x ^ 2 * x ^ 3  ->  x ^ 3 * x ^ 2  # exponents
-    yx             ->  xy             # alphabetically
+    Sort a polynome, pursuing the following form:
+    c_n * x^n * ... * c_1 * x^1 * c_0 * x^0
+    For example: 2x^2 + x - 3
     """
-    assert node.is_op(OP_MUL)
+    assert node.is_op(OP_ADD)
 
     scope = Scope(node)
 
     return [P(node, swap_factors, (scope, l, r))
-            for l, r in filter(swap_moni, iter_pairs(scope))]
+            for l, r in filter(swap_poly, iter_pairs(scope))]
 
 
 def swap_factors(root, args):
     """
-    left * right
+    f * g  ->  g * f
     """
     scope, left, right = args
 
@@ -60,4 +143,4 @@ def swap_factors(root, args):
     return scope.as_nary_node()
 
 
-MESSAGES[swap_factors] = _('Move {2} to the left of {1}.')
+MESSAGES[swap_factors] = _('Move {3} to the left of {2}.')

+ 64 - 11
tests/test_rules_sort.py

@@ -1,5 +1,5 @@
-from src.rules.sort import match_sort_polynome, swap_factors, \
-        match_sort_molinome, iter_pairs
+from src.rules.sort import match_sort_polynome, swap_factors, get_poly_prop, \
+        match_sort_monomial, iter_pairs, swap_mono, swap_poly, get_power_prop
 from src.node import Scope
 from src.possibilities import Possibility as P
 from tests.rulestestcase import RulesTestCase, tree
@@ -7,21 +7,74 @@ from tests.rulestestcase import RulesTestCase, tree
 
 class TestRulesSort(RulesTestCase):
 
-    def test_match_sort_molinome_constant(self):
+    def test_swap_mono(self):
+        self.assertTrue(swap_mono(tree('x, 1 / 2')))
+        self.assertFalse(swap_mono(tree('2, 1 / 2')))
+
+        self.assertTrue(swap_mono(tree('x, 2')))
+        self.assertFalse(swap_mono(tree('2, x')))
+        self.assertTrue(swap_mono(tree('x ^ 2, 2')))
+
+        self.assertTrue(swap_mono(tree('y, x')))
+        self.assertFalse(swap_mono(tree('x, y')))
+        self.assertFalse(swap_mono(tree('x, x')))
+
+        self.assertTrue(swap_mono(tree('x ^ 3, x ^ 2')))
+        self.assertFalse(swap_mono(tree('x ^ 2, x ^ 3')))
+
+    def test_swap_poly(self):
+        self.assertTrue(swap_poly(tree('2, x')))
+        self.assertFalse(swap_poly(tree('x, 2')))
+
+        self.assertTrue(swap_poly(tree('a, a^2')))
+        self.assertFalse(swap_poly(tree('a^2, a')))
+
+        self.assertTrue(swap_poly(tree('y, x')))
+        self.assertFalse(swap_poly(tree('x, y')))
+        self.assertFalse(swap_poly(tree('x, x')))
+
+        self.assertFalse(swap_poly(tree('x ^ 3, x ^ 2')))
+        self.assertTrue(swap_poly(tree('x ^ 2, x ^ 3')))
+
+    def test_get_power_prop(self):
+        self.assertEqual(get_power_prop(tree('a')), ('a', 1))
+        self.assertEqual(get_power_prop(tree('a ^ b')), ('a', 1))
+        self.assertEqual(get_power_prop(tree('a ^ 2')), ('a', 2))
+        self.assertEqual(get_power_prop(tree('a ^ -2')), ('a', -2))
+        self.assertIsNone(get_power_prop(tree('1')))
+
+    def test_get_poly_prop(self):
+        self.assertEqual(get_poly_prop(tree('a ^ 2')), ('a', 2))
+        self.assertEqual(get_poly_prop(tree('2a ^ 2')), ('a', 2))
+        self.assertEqual(get_poly_prop(tree('ca ^ 2 * 2')), ('a', 2))
+        self.assertEqual(get_poly_prop(tree('ab ^ 2')), ('a', 1))
+        self.assertEqual(get_poly_prop(tree('a^3 * a^2')), ('a', 3))
+        self.assertEqual(get_poly_prop(tree('a^2 * a^3')), ('a', 3))
+        self.assertIsNone(get_poly_prop(tree('1')))
+
+    def test_match_sort_monomial_constant(self):
         x, l2 = root = tree('x * 2')
-        self.assertEqualPos(match_sort_molinome(root),
+        self.assertEqualPos(match_sort_monomial(root),
                 [P(root, swap_factors, (Scope(root), x, l2))])
 
         root = tree('2x')
-        self.assertEqualPos(match_sort_molinome(root), [])
+        self.assertEqualPos(match_sort_monomial(root), [])
+
+    def test_match_sort_monomial_variables(self):
+        y, x = root = tree('yx')
+        self.assertEqualPos(match_sort_monomial(root),
+                [P(root, swap_factors, (Scope(root), y, x))])
+
+        root = tree('xy')
+        self.assertEqualPos(match_sort_monomial(root), [])
 
-    #def test_match_sort_molinome_variables(self):
-    #    y, x = root = tree('yx')
-    #    self.assertEqualPos(match_sort_molinome(root),
-    #            [P(root, swap_factors, (Scope(root), y, x))])
+    def test_match_sort_polynome(self):
+        x, x2 = root = tree('x + x ^ 2')
+        self.assertEqualPos(match_sort_polynome(root),
+                [P(root, swap_factors, (Scope(root), x, x2))])
 
-    #    root = tree('xy')
-    #    self.assertEqualPos(match_sort_molinome(root), [])
+        root = tree('x + 2')
+        self.assertEqualPos(match_sort_polynome(root), [])
 
     def test_swap_factors(self):
         x, l2 = root = tree('x * 2')