Coverage for /usr/lib/python3/dist-packages/sympy/core/sorting.py: 77%
71 statements
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-14 15:55 +0200
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-14 15:55 +0200
1from collections import defaultdict
3from .sympify import sympify, SympifyError
4from sympy.utilities.iterables import iterable, uniq
7__all__ = ['default_sort_key', 'ordered']
10def default_sort_key(item, order=None):
11 """Return a key that can be used for sorting.
13 The key has the structure:
15 (class_key, (len(args), args), exponent.sort_key(), coefficient)
17 This key is supplied by the sort_key routine of Basic objects when
18 ``item`` is a Basic object or an object (other than a string) that
19 sympifies to a Basic object. Otherwise, this function produces the
20 key.
22 The ``order`` argument is passed along to the sort_key routine and is
23 used to determine how the terms *within* an expression are ordered.
24 (See examples below) ``order`` options are: 'lex', 'grlex', 'grevlex',
25 and reversed values of the same (e.g. 'rev-lex'). The default order
26 value is None (which translates to 'lex').
28 Examples
29 ========
31 >>> from sympy import S, I, default_sort_key, sin, cos, sqrt
32 >>> from sympy.core.function import UndefinedFunction
33 >>> from sympy.abc import x
35 The following are equivalent ways of getting the key for an object:
37 >>> x.sort_key() == default_sort_key(x)
38 True
40 Here are some examples of the key that is produced:
42 >>> default_sort_key(UndefinedFunction('f'))
43 ((0, 0, 'UndefinedFunction'), (1, ('f',)), ((1, 0, 'Number'),
44 (0, ()), (), 1), 1)
45 >>> default_sort_key('1')
46 ((0, 0, 'str'), (1, ('1',)), ((1, 0, 'Number'), (0, ()), (), 1), 1)
47 >>> default_sort_key(S.One)
48 ((1, 0, 'Number'), (0, ()), (), 1)
49 >>> default_sort_key(2)
50 ((1, 0, 'Number'), (0, ()), (), 2)
52 While sort_key is a method only defined for SymPy objects,
53 default_sort_key will accept anything as an argument so it is
54 more robust as a sorting key. For the following, using key=
55 lambda i: i.sort_key() would fail because 2 does not have a sort_key
56 method; that's why default_sort_key is used. Note, that it also
57 handles sympification of non-string items likes ints:
59 >>> a = [2, I, -I]
60 >>> sorted(a, key=default_sort_key)
61 [2, -I, I]
63 The returned key can be used anywhere that a key can be specified for
64 a function, e.g. sort, min, max, etc...:
66 >>> a.sort(key=default_sort_key); a[0]
67 2
68 >>> min(a, key=default_sort_key)
69 2
71 Notes
72 =====
74 The key returned is useful for getting items into a canonical order
75 that will be the same across platforms. It is not directly useful for
76 sorting lists of expressions:
78 >>> a, b = x, 1/x
80 Since ``a`` has only 1 term, its value of sort_key is unaffected by
81 ``order``:
83 >>> a.sort_key() == a.sort_key('rev-lex')
84 True
86 If ``a`` and ``b`` are combined then the key will differ because there
87 are terms that can be ordered:
89 >>> eq = a + b
90 >>> eq.sort_key() == eq.sort_key('rev-lex')
91 False
92 >>> eq.as_ordered_terms()
93 [x, 1/x]
94 >>> eq.as_ordered_terms('rev-lex')
95 [1/x, x]
97 But since the keys for each of these terms are independent of ``order``'s
98 value, they do not sort differently when they appear separately in a list:
100 >>> sorted(eq.args, key=default_sort_key)
101 [1/x, x]
102 >>> sorted(eq.args, key=lambda i: default_sort_key(i, order='rev-lex'))
103 [1/x, x]
105 The order of terms obtained when using these keys is the order that would
106 be obtained if those terms were *factors* in a product.
108 Although it is useful for quickly putting expressions in canonical order,
109 it does not sort expressions based on their complexity defined by the
110 number of operations, power of variables and others:
112 >>> sorted([sin(x)*cos(x), sin(x)], key=default_sort_key)
113 [sin(x)*cos(x), sin(x)]
114 >>> sorted([x, x**2, sqrt(x), x**3], key=default_sort_key)
115 [sqrt(x), x, x**2, x**3]
117 See Also
118 ========
120 ordered, sympy.core.expr.Expr.as_ordered_factors, sympy.core.expr.Expr.as_ordered_terms
122 """
123 from .basic import Basic
124 from .singleton import S
126 if isinstance(item, Basic):
127 return item.sort_key(order=order)
129 if iterable(item, exclude=str):
130 if isinstance(item, dict):
131 args = item.items()
132 unordered = True
133 elif isinstance(item, set):
134 args = item
135 unordered = True
136 else:
137 # e.g. tuple, list
138 args = list(item)
139 unordered = False
141 args = [default_sort_key(arg, order=order) for arg in args]
143 if unordered:
144 # e.g. dict, set
145 args = sorted(args)
147 cls_index, args = 10, (len(args), tuple(args))
148 else:
149 if not isinstance(item, str):
150 try:
151 item = sympify(item, strict=True)
152 except SympifyError:
153 # e.g. lambda x: x
154 pass
155 else:
156 if isinstance(item, Basic):
157 # e.g int -> Integer
158 return default_sort_key(item)
159 # e.g. UndefinedFunction
161 # e.g. str
162 cls_index, args = 0, (1, (str(item),))
164 return (cls_index, 0, item.__class__.__name__
165 ), args, S.One.sort_key(), S.One
168def _node_count(e):
169 # this not only counts nodes, it affirms that the
170 # args are Basic (i.e. have an args property). If
171 # some object has a non-Basic arg, it needs to be
172 # fixed since it is intended that all Basic args
173 # are of Basic type (though this is not easy to enforce).
174 if e.is_Float:
175 return 0.5
176 return 1 + sum(map(_node_count, e.args))
179def _nodes(e):
180 """
181 A helper for ordered() which returns the node count of ``e`` which
182 for Basic objects is the number of Basic nodes in the expression tree
183 but for other objects is 1 (unless the object is an iterable or dict
184 for which the sum of nodes is returned).
185 """
186 from .basic import Basic
187 from .function import Derivative
189 if isinstance(e, Basic):
190 if isinstance(e, Derivative):
191 return _nodes(e.expr) + sum(i[1] if i[1].is_Number else
192 _nodes(i[1]) for i in e.variable_count)
193 return _node_count(e)
194 elif iterable(e):
195 return 1 + sum(_nodes(ei) for ei in e)
196 elif isinstance(e, dict):
197 return 1 + sum(_nodes(k) + _nodes(v) for k, v in e.items())
198 else:
199 return 1
202def ordered(seq, keys=None, default=True, warn=False):
203 """Return an iterator of the seq where keys are used to break ties in
204 a conservative fashion: if, after applying a key, there are no ties
205 then no other keys will be computed.
207 Two default keys will be applied if 1) keys are not provided or 2) the
208 given keys do not resolve all ties (but only if ``default`` is True). The
209 two keys are ``_nodes`` (which places smaller expressions before large) and
210 ``default_sort_key`` which (if the ``sort_key`` for an object is defined
211 properly) should resolve any ties.
213 If ``warn`` is True then an error will be raised if there were no
214 keys remaining to break ties. This can be used if it was expected that
215 there should be no ties between items that are not identical.
217 Examples
218 ========
220 >>> from sympy import ordered, count_ops
221 >>> from sympy.abc import x, y
223 The count_ops is not sufficient to break ties in this list and the first
224 two items appear in their original order (i.e. the sorting is stable):
226 >>> list(ordered([y + 2, x + 2, x**2 + y + 3],
227 ... count_ops, default=False, warn=False))
228 ...
229 [y + 2, x + 2, x**2 + y + 3]
231 The default_sort_key allows the tie to be broken:
233 >>> list(ordered([y + 2, x + 2, x**2 + y + 3]))
234 ...
235 [x + 2, y + 2, x**2 + y + 3]
237 Here, sequences are sorted by length, then sum:
239 >>> seq, keys = [[[1, 2, 1], [0, 3, 1], [1, 1, 3], [2], [1]], [
240 ... lambda x: len(x),
241 ... lambda x: sum(x)]]
242 ...
243 >>> list(ordered(seq, keys, default=False, warn=False))
244 [[1], [2], [1, 2, 1], [0, 3, 1], [1, 1, 3]]
246 If ``warn`` is True, an error will be raised if there were not
247 enough keys to break ties:
249 >>> list(ordered(seq, keys, default=False, warn=True))
250 Traceback (most recent call last):
251 ...
252 ValueError: not enough keys to break ties
255 Notes
256 =====
258 The decorated sort is one of the fastest ways to sort a sequence for
259 which special item comparison is desired: the sequence is decorated,
260 sorted on the basis of the decoration (e.g. making all letters lower
261 case) and then undecorated. If one wants to break ties for items that
262 have the same decorated value, a second key can be used. But if the
263 second key is expensive to compute then it is inefficient to decorate
264 all items with both keys: only those items having identical first key
265 values need to be decorated. This function applies keys successively
266 only when needed to break ties. By yielding an iterator, use of the
267 tie-breaker is delayed as long as possible.
269 This function is best used in cases when use of the first key is
270 expected to be a good hashing function; if there are no unique hashes
271 from application of a key, then that key should not have been used. The
272 exception, however, is that even if there are many collisions, if the
273 first group is small and one does not need to process all items in the
274 list then time will not be wasted sorting what one was not interested
275 in. For example, if one were looking for the minimum in a list and
276 there were several criteria used to define the sort order, then this
277 function would be good at returning that quickly if the first group
278 of candidates is small relative to the number of items being processed.
280 """
282 d = defaultdict(list)
283 if keys:
284 if isinstance(keys, (list, tuple)):
285 keys = list(keys)
286 f = keys.pop(0)
287 else:
288 f = keys
289 keys = []
290 for a in seq:
291 d[f(a)].append(a)
292 else:
293 if not default:
294 raise ValueError('if default=False then keys must be provided')
295 d[None].extend(seq)
297 for k, value in sorted(d.items()):
298 if len(value) > 1:
299 if keys:
300 value = ordered(value, keys, default, warn)
301 elif default:
302 value = ordered(value, (_nodes, default_sort_key,),
303 default=False, warn=warn)
304 elif warn:
305 u = list(uniq(value))
306 if len(u) > 1:
307 raise ValueError(
308 'not enough keys to break ties: %s' % u)
309 yield from value