Prechádzať zdrojové kódy

Added fraction division rules.

Taddeus Kroes 14 rokov pred
rodič
commit
d0da0b903a
3 zmenil súbory, kde vykonal 119 pridanie a 16 odobranie
  1. 3 3
      src/rules/__init__.py
  2. 96 10
      src/rules/fractions.py
  3. 20 3
      tests/test_rules_fractions.py

+ 3 - 3
src/rules/__init__.py

@@ -11,7 +11,7 @@ from .numerics import match_add_numerics, match_divide_numerics, \
         match_raise_numerics
 from .fractions import match_constant_division, match_add_constant_fractions, \
         match_expand_and_add_fractions, match_multiply_fractions, \
-        match_equal_fraction_parts
+        match_divide_fractions, match_equal_fraction_parts
 from .negation import match_negated_factor, match_negate_polynome, \
         match_negated_division
 from .sort import match_sort_multiplicants
@@ -26,8 +26,8 @@ RULES = {
                  match_negated_factor, match_multiply_one,
                  match_sort_multiplicants, match_multiply_fractions],
         OP_DIV: [match_subtract_exponents, match_divide_numerics,
-                 match_constant_division, match_negated_division,
-                 match_equal_fraction_parts],
+                 match_constant_division, match_divide_fractions, \
+                 match_negated_division, match_equal_fraction_parts],
         OP_POW: [match_multiply_exponents, match_duplicate_exponent,
                  match_raised_fraction, match_remove_negative_exponent,
                  match_exponent_to_root, match_extend_exponent,

+ 96 - 10
src/rules/fractions.py

@@ -227,20 +227,79 @@ def multiply_with_fraction(root, args):
 MESSAGES[multiply_with_fraction] = _('Multiply {2} with fraction {3}.')
 
 
-def match_equal_fraction_parts(node):
+def match_divide_fractions(node):
     """
-    Divide nominator and denominator by the same part.
+    Reduce divisions of fractions to a single fraction.
 
     Examples:
-    ab / (ac)  ->  b / c
-    ab / a     ->  b / 1
-    a / (ab)   ->  1 / b
+    a / b / c        ->  a / (bc)
+    a / (b / c)      ->  ac / b
+    """
+    # TODO: IMPLICIT: a / b / (c / d)  ->*  ad / bd  ->  validation test!
+    assert node.is_op(OP_DIV)
 
-    If the same root appears in both nominator and denominator, extrct it so
-    that it can be reduced to a single power by power division rules.
-    a ^ p * b / a ^ q  ->  a ^ p / a ^ q * b / 1
-    a ^ p * b / a      ->  a ^ p / a * b / 1
-    a * b / a ^ q      ->  a / a ^ q * b / 1
+    nom, denom = node
+    p = []
+
+    if nom.is_op(OP_DIV):
+        p.append(P(node, divide_fraction, tuple(nom) + (denom,)))
+
+    if denom.is_op(OP_DIV):
+        p.append(P(node, divide_by_fraction, (nom,) + tuple(denom)))
+
+    return p
+
+
+def divide_fraction(root, args):
+    """
+    a / b / c        ->  a / (bc)
+    """
+    a, b, c = args
+
+    return a / (b * c)
+
+
+MESSAGES[divide_fraction] = _('Move {2} to denominator of fraction {0[0]}.')
+
+
+def divide_by_fraction(root, args):
+    """
+    a / (b / c)      ->  ac / b
+    """
+    a, b, c = args
+
+    return a * c / b
+
+
+MESSAGES[divide_by_fraction] = \
+        _('Move {3} to nominator of fraction {1} / {2}.')
+
+
+#def match_extract_divided_fractions(node):
+#    """
+#    Reduce divisions of fractions to a single fraction.
+#
+#    Examples:
+#    a / b / c        ->  a / bc
+#    a / (b / c)      ->  ac / b
+#    # IMPLICIT: a / b / (c / d)  ->*  ad / bd  ->  validation test!
+#    """
+#    assert node.is_op(OP_DIV)
+#
+#    nom, denom = node
+#    n_scope, d_scope = fraction_scopes(node)
+#    is_division = lambda n: n.is_op(OP_DIV)
+#    n_fractions, n_others = partition(is_division, n_scope)
+#    d_fractions, d_others = partition(is_division, d_scope)
+#
+#
+#    return []
+
+
+def fraction_scopes(node):
+    """
+    Get the multiplication scopes of the nominator and denominator of a
+    fraction.
     """
     assert node.is_op(OP_DIV)
 
@@ -256,6 +315,28 @@ def match_equal_fraction_parts(node):
     else:
         d_scope = [denominator]
 
+    return n_scope, d_scope
+
+
+def match_equal_fraction_parts(node):
+    """
+    Divide nominator and denominator by the same part.
+
+    Examples:
+    ab / (ac)  ->  b / c
+    ab / a     ->  b / 1
+    a / (ab)   ->  1 / b
+
+    If the same root appears in both nominator and denominator, extrct it so
+    that it can be reduced to a single power by power division rules.
+    a ^ p * b / a ^ q  ->  a ^ p / a ^ q * b / 1
+    a ^ p * b / a      ->  a ^ p / a * b / 1
+    a * b / a ^ q      ->  a / a ^ q * b / 1
+    """
+    assert node.is_op(OP_DIV)
+
+    nominator, denominator = node
+    n_scope, d_scope = fraction_scopes(node)
     p = []
 
     # Look for matching parts in scopes
@@ -330,6 +411,11 @@ MESSAGES[divide_fraction_parts] = \
 
 
 def extract_divided_roots(root, args):
+    """
+    a ^ p * b / a ^ q  ->  a ^ p / a ^ q * b / 1
+    a ^ p * b / a      ->  a ^ p / a * b / 1
+    a * b / a ^ q      ->  a / a ^ q * b / 1
+    """
     a, n_scope, d_scope, i, j = args
     n, d = root
     ap, aq, nom, denom = remove_from_scopes(n_scope, d_scope, i, j)

+ 20 - 3
tests/test_rules_fractions.py

@@ -1,9 +1,9 @@
 from src.rules.fractions import match_constant_division, division_by_one, \
         division_of_zero, division_by_self, match_add_constant_fractions, \
         equalize_denominators, add_nominators, match_multiply_fractions, \
-        multiply_fractions, multiply_with_fraction, \
-        match_equal_fraction_parts, divide_fraction_parts, \
-        extract_divided_roots
+        multiply_fractions, multiply_with_fraction, match_divide_fractions, \
+        divide_fraction, divide_by_fraction, match_equal_fraction_parts, \
+        divide_fraction_parts, extract_divided_roots
 from src.node import Scope
 from src.possibilities import Possibility as P
 from tests.rulestestcase import RulesTestCase, tree
@@ -147,6 +147,23 @@ class TestRulesFractions(RulesTestCase):
         self.assertEqual(multiply_fractions(root, (Scope(root), ab, cd)),
                          a * c / (b * d) * e)
 
+    def test_match_divide_fractions(self):
+        (a, b), c = root = tree('a / b / c')
+        self.assertEqualPos(match_divide_fractions(root),
+                [P(root, divide_fraction, (a, b, c))])
+
+        root = tree('a / (b / c)')
+        self.assertEqualPos(match_divide_fractions(root),
+                [P(root, divide_by_fraction, (a, b, c))])
+
+    def test_divide_fraction(self):
+        (a, b), c = root = tree('a / b / c')
+        self.assertEqual(divide_fraction(root, (a, b, c)), a / (b * c))
+
+    def test_divide_by_fraction(self):
+        a, (b, c) = root = tree('a / (b / c)')
+        self.assertEqual(divide_by_fraction(root, (a, b, c)), a * c / b)
+
     def test_match_equal_fraction_parts(self):
         (a, b), (c, a) = root = tree('ab / (ca)')
         self.assertEqualPos(match_equal_fraction_parts(root),