node.py 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  1. # vim: set fileencoding=utf-8 :
  2. import os.path
  3. import sys
  4. sys.path.insert(0, os.path.realpath('external'))
  5. from graph_drawing.graph import generate_graph
  6. from graph_drawing.line import generate_line
  7. from graph_drawing.node import Node, Leaf
  8. TYPE_OPERATOR = 1
  9. TYPE_IDENTIFIER = 2
  10. TYPE_INTEGER = 4
  11. TYPE_FLOAT = 8
  12. # Unary
  13. OP_NEG = 1
  14. # Binary
  15. OP_ADD = 2
  16. OP_SUB = 3
  17. OP_MUL = 4
  18. OP_DIV = 5
  19. OP_POW = 6
  20. OP_MOD = 7
  21. # N-ary (functions)
  22. OP_INT = 8
  23. OP_EXPAND = 9
  24. OP_COMMA = 10
  25. TYPE_MAP = {
  26. int: TYPE_INTEGER,
  27. float: TYPE_FLOAT,
  28. str: TYPE_IDENTIFIER,
  29. }
  30. OP_MAP = {
  31. '+': OP_ADD,
  32. # Either substraction or negation. Skip the operator sign in 'x' (= 2).
  33. '-': lambda x: OP_SUB if len(x) > 2 else OP_NEG,
  34. '*': OP_MUL,
  35. '/': OP_DIV,
  36. '^': OP_POW,
  37. 'mod': OP_MOD,
  38. 'int': OP_INT,
  39. 'expand': OP_EXPAND,
  40. ',': OP_COMMA,
  41. }
  42. def to_expression(obj):
  43. return obj if isinstance(obj, ExpressionBase) else ExpressionLeaf(obj)
  44. class ExpressionBase(object):
  45. def __lt__(self, other):
  46. """
  47. Comparison between this expression{node,leaf} and another
  48. expression{node,leaf}. This comparison will return True if this
  49. instance has less value than the other expression{node,leaf}.
  50. Otherwise, False is returned.
  51. The comparison is based on the following conditions:
  52. 1. Both are leafs. String comparison of the value is used.
  53. 2. This is a leaf and other is a node. This leaf has less value, thus
  54. True is returned.
  55. 3. This is a node and other is a leaf. This leaf has more value, thus
  56. False is returned.
  57. 4. Both are nodes. Compare the polynome properties of the nodes. True
  58. is returned if this node's root property is less than other's root
  59. property, or this node's exponent property is less than other's
  60. exponent property, or this node's coefficient property is less than
  61. other's coefficient property. Otherwise, False is returned.
  62. """
  63. if self.is_leaf():
  64. if other.is_leaf():
  65. # Both are leafs, string compare the value.
  66. return str(self.value) < str(other.value)
  67. # Self is a leaf, thus has less value than an expression node.
  68. return True
  69. if other.is_leaf():
  70. # Self is an expression node, and the other is a leaf. Thus, other
  71. # is greater than self.
  72. return False
  73. # Both are nodes, compare the polynome properties.
  74. s_coeff, s_root, s_exp = self.extract_polynome_properties()
  75. o_coeff, o_root, o_exp = other.extract_polynome_properties()
  76. return s_root < o_root or s_exp < o_exp or s_coeff < o_coeff
  77. def is_op(self, op):
  78. return not self.is_leaf() and self.op == op
  79. def is_leaf(self):
  80. return self.type != TYPE_OPERATOR
  81. def is_power(self):
  82. return not self.is_leaf() and self.op == OP_POW
  83. def is_nary(self):
  84. return not self.is_leaf() and self.op in [OP_ADD, OP_SUB, OP_MUL]
  85. def is_identifier(self):
  86. return self.is_leaf() and self.type & TYPE_IDENTIFIER
  87. def is_int(self):
  88. return self.is_leaf() and self.type & TYPE_INTEGER
  89. def is_float(self):
  90. return self.is_leaf() and self.type & TYPE_FLOAT
  91. def is_numeric(self):
  92. return self.is_leaf() and self.type & (TYPE_FLOAT | TYPE_INTEGER)
  93. def __add__(self, other):
  94. return ExpressionNode('+', self, to_expression(other))
  95. def __sub__(self, other):
  96. return ExpressionNode('-', self, to_expression(other))
  97. def __mul__(self, other):
  98. return ExpressionNode('*', self, to_expression(other))
  99. def __div__(self, other):
  100. return ExpressionNode('/', self, to_expression(other))
  101. def __pow__(self, other):
  102. return ExpressionNode('^', self, to_expression(other))
  103. def __neg__(self):
  104. return ExpressionNode('-', to_expression(self))
  105. class ExpressionNode(Node, ExpressionBase):
  106. def __init__(self, *args, **kwargs):
  107. super(ExpressionNode, self).__init__(*args, **kwargs)
  108. self.type = TYPE_OPERATOR
  109. self.op = OP_MAP[args[0]]
  110. if hasattr(self.op, '__call__'):
  111. self.op = self.op(args)
  112. def __str__(self): # pragma: nocover
  113. return generate_line(self)
  114. def __eq__(self, other):
  115. """
  116. Check strict equivalence.
  117. """
  118. if isinstance(other, ExpressionNode):
  119. return self.op == other.op and self.nodes == other.nodes
  120. return False
  121. def graph(self): # pragma: nocover
  122. return generate_graph(self)
  123. def extract_polynome_properties(self):
  124. """
  125. Extract polynome properties into tuple format: (coefficient, root,
  126. exponent). Thus: c * r ^ e will be extracted into the tuple (c, r, e).
  127. This function will normalize the expression before extracting the
  128. properties. Therefore, the expression r ^ e * c results the same tuple
  129. (c, r, e) as the expression c * r ^ e.
  130. >>> from src.node import ExpressionNode as N, ExpressionLeaf as L
  131. >>> c, r, e = L('c'), L('r'), L('e')
  132. >>> n1 = N('*', c, N('^', r, e))
  133. >>> n1.extract_polynome()
  134. (c, r, e)
  135. >>> n2 = N('*', N('^', r, e), c)
  136. >>> n2.extract_polynome()
  137. (c, r, e)
  138. """
  139. # TODO: change "get_polynome" -> "extract_polynome".
  140. # TODO: change retval of c * r ^ e to (c, r, e).
  141. # was: (root, exponent, coefficient, literal_exponent)
  142. # rule: r ^ e -> (1, r, e)
  143. if self.is_power():
  144. return (ExpressionLeaf(1), self[0], self[1])
  145. if self.op != OP_MUL:
  146. return
  147. # rule: 3 * 7 ^ e | 'a' * 'b' ^ e
  148. # expression: c * r ^ e ; tree:
  149. #
  150. # *
  151. # ╭┴───╮
  152. # c ^
  153. # ╭─┴╮
  154. # r e
  155. #
  156. # rule: c * r ^ e | (r ^ e) * c
  157. for i, j in [(0, 1), (1, 0)]:
  158. if self[j].is_power():
  159. return (self[i], self[j][0], self[j][1])
  160. # Normalize c * r and r * c -> c * r. Otherwise, the tuple will not
  161. # match if the order of the expression is different. Example:
  162. # r ^ e * c == c * r ^ e
  163. # without normalization, those expressions will not match.
  164. #
  165. # rule: c * r | r * c
  166. if self[0] < self[1]:
  167. return (self[0], self[1], ExpressionLeaf(1))
  168. return (self[1], self[0], ExpressionLeaf(1))
  169. def get_scope(self):
  170. """"""
  171. scope = []
  172. #op = OP_ADD | OP_SUB if self.op & (OP_ADD | OP_SUB) else self.op
  173. # TODO: what to do with OP_SUB and OP_ADD in get_scope?
  174. for child in self:
  175. if not child.is_leaf() and child.op == self.op:
  176. scope += child.get_scope()
  177. else:
  178. scope.append(child)
  179. return scope
  180. class ExpressionLeaf(Leaf, ExpressionBase):
  181. def __init__(self, *args, **kwargs):
  182. super(ExpressionLeaf, self).__init__(*args, **kwargs)
  183. self.type = TYPE_MAP[type(args[0])]
  184. def __eq__(self, other):
  185. if isinstance(other, int) or isinstance(other, float) \
  186. or isinstance(other, str):
  187. return self.value == other
  188. if other.is_leaf():
  189. return self.value == other.value
  190. return False
  191. def extract_polynome_properties(self):
  192. """
  193. An expression leaf will return the polynome tuple (1, r, 1), where r is
  194. the leaf itself. See also the method extract_polynome_properties in
  195. ExpressionBase.
  196. """
  197. # rule: 1 * r ^ 1 -> (1, r, 1)
  198. return (ExpressionLeaf(1), self, ExpressionLeaf(1))