From a4e632cc2df5512064b60d56621b25be90367e5c Mon Sep 17 00:00:00 2001 From: rocky Date: Sun, 4 Aug 2024 19:08:32 -0400 Subject: [PATCH] Split out Head-Related Structural Operations... on Expressions --- mathics/builtin/exp_structure/general.py | 213 +----------------- mathics/builtin/exp_structure/head_related.py | 102 +++++++++ mathics/core/systemsymbols.py | 2 +- 3 files changed, 106 insertions(+), 211 deletions(-) create mode 100644 mathics/builtin/exp_structure/head_related.py diff --git a/mathics/builtin/exp_structure/general.py b/mathics/builtin/exp_structure/general.py index d78b3152b..8c973b3a8 100644 --- a/mathics/builtin/exp_structure/general.py +++ b/mathics/builtin/exp_structure/general.py @@ -1,21 +1,18 @@ # -*- coding: utf-8 -*- """ -General Structural Expression Functions +Structural Expression Functions """ -from mathics.core.atoms import Integer, Rational +from mathics.core.atoms import Integer from mathics.core.builtin import BinaryOperator, Builtin, Predefined from mathics.core.exceptions import InvalidLevelspecError from mathics.core.expression import Evaluation, Expression from mathics.core.list import ListExpression from mathics.core.rules import Pattern -from mathics.core.symbols import Atom, Symbol, SymbolFalse, SymbolTrue +from mathics.core.symbols import Atom, SymbolFalse, SymbolTrue from mathics.core.systemsymbols import SymbolMap from mathics.eval.parts import python_levelspec, walk_levels -SymbolOperate = Symbol("Operate") -SymbolSortBy = Symbol("SortBy") - class ApplyLevel(BinaryOperator): """ @@ -45,116 +42,6 @@ class ApplyLevel(BinaryOperator): summary_text = "apply a function to a list, at the top level" -class BinarySearch(Builtin): - """ - - :Binary search algorithm: - https://en.wikipedia.org/wiki/Binary_search_algorithm ( - :WMA: - https://reference.wolfram.com/language/ref/BinarySearch.html) - -
-
'CombinatoricaOld`BinarySearch[$l$, $k$]' -
searches the list $l$, which has to be sorted, for key $k$ and \ - returns its index in $l$. - - If $k$ does not exist in $l$, 'BinarySearch' returns ($a$ + $b$) / 2, \ - where $a$ and $b$ are the indices between which $k$ would have \ - to be inserted in order to maintain the sorting order in $l$. - - Please note that $k$ and the elements in $l$ need to be comparable \ - under a - :strict total order: - https://en.wikipedia.org/wiki/Total_order. - -
'CombinatoricaOld`BinarySearch[$l$, $k$, $f$]' -
gives the index of $k$ in the elements of $l$ if $f$ is applied to the \ - latter prior to comparison. Note that $f$ \ - needs to yield a sorted sequence if applied to the elements of $l$. -
- - Number 100 is found at exactly in the fourth place of the given list: - - >> CombinatoricaOld`BinarySearch[{3, 4, 10, 100, 123}, 100] - = 4 - - Number 7 is found in between the second and third place (3, and 9)\ - of the given list. The numerical difference between 3 and 9 does \ - not figure into the .5 part of 2.5: - - >> CombinatoricaOld`BinarySearch[{2, 3, 9}, 7] // N - = 2.5 - - 0.5 is what you get when the item comes before the given list: - - >> CombinatoricaOld`BinarySearch[{-10, 5, 8, 10}, -100] // N - = 0.5 - - And here is what you see when the item comes at the end of the list: - - >> CombinatoricaOld`BinarySearch[{-10, 5, 8, 10}, 20] // N - = 4.5 - - >> CombinatoricaOld`BinarySearch[{{a, 1}, {b, 7}}, 7, #[[2]]&] - = 2 - """ - - context = "CombinatoricaOld`" - - rules = { - "CombinatoricaOld`BinarySearch[li_List, k_] /; Length[li] > 0": "CombinatoricaOld`BinarySearch[li, k, Identity]" - } - - summary_text = "search a sorted list for a key" - - def eval(self, li, k, f, evaluation: Evaluation): - "CombinatoricaOld`BinarySearch[li_List, k_, f_] /; Length[li] > 0" - - elements = li.elements - - lower_index = 1 - upper_index = len(elements) - - if ( - lower_index > upper_index - ): # empty list li? Length[l] > 0 condition should guard us, but check anyway - return Symbol("$Aborted") - - # "transform" is a handy wrapper for applying "f" or nothing - if f.get_name() == "System`Identity": - - def transform(x): - return x - - else: - - def transform(x): - return Expression(f, x).evaluate(evaluation) - - # loop invariants (true at any time in the following loop): - # (1) lower_index <= upper_index - # (2) k > elements[i] for all i < lower_index - # (3) k < elements[i] for all i > upper_index - while True: - pivot_index = (lower_index + upper_index) >> 1 # i.e. a + (b - a) // 2 - # as lower_index <= upper_index, lower_index <= pivot_index <= upper_index - pivot = transform(elements[pivot_index - 1]) # 1-based to 0-based - - # we assume a trichotomous relation: k < pivot, or k = pivot, or k > pivot - if k < pivot: - if pivot_index == lower_index: # see invariant (2), to see that - # k < elements[pivot_index] and k > elements[pivot_index - 1] - return Rational((pivot_index - 1) + pivot_index, 2) - upper_index = pivot_index - 1 - elif k == pivot: - return Integer(pivot_index) - else: # k > pivot - if pivot_index == upper_index: # see invariant (3), to see that - # k > elements[pivot_index] and k < elements[pivot_index + 1] - return Rational(pivot_index + (pivot_index + 1), 2) - lower_index = pivot_index + 1 - - class Depth(Builtin): """ :WMA link:https://reference.wolfram.com/language/ref/Depth.html @@ -340,72 +227,6 @@ class Null(Predefined): summary_text = "implicit result for expressions that do not yield a result" -class Operate(Builtin): - """ - - :WMA link: - https://reference.wolfram.com/language/ref/Operate.html - -
-
'Operate[$p$, $expr$]' -
applies $p$ to the head of $expr$. - -
'Operate[$p$, $expr$, $n$]' -
applies $p$ to the $n$th head of $expr$. -
- - >> Operate[p, f[a, b]] - = p[f][a, b] - - The default value of $n$ is 1: - >> Operate[p, f[a, b], 1] - = p[f][a, b] - - With $n$=0, 'Operate' acts like 'Apply': - >> Operate[p, f[a][b][c], 0] - = p[f[a][b][c]] - """ - - summary_text = "apply a function to the head of an expression" - messages = { - "intnn": "Non-negative integer expected at position `2` in `1`.", - } - - def eval(self, p, expr, n, evaluation: Evaluation): - "Operate[p_, expr_, Optional[n_, 1]]" - - head_depth = n.get_int_value() - if head_depth is None or head_depth < 0: - evaluation.message( - "Operate", "intnn", Expression(SymbolOperate, p, expr, n), 3 - ) - return - - if head_depth == 0: - # Act like Apply - return Expression(p, expr) - - if isinstance(expr, Atom): - return expr - - expr = expr.copy() - e = expr - - for i in range(1, head_depth): - e = e.head - if isinstance(e, Atom): - # n is higher than the depth of heads in expr: return - # expr unmodified. - return expr - - # Otherwise, if we get here, e.head points to the head we need - # to apply p to. Python's reference semantics mean that this - # assignment modifies expr as well. - e.set_head(Expression(p, e.head)) - - return expr - - class SortBy(Builtin): """ @@ -485,31 +306,3 @@ def __gt__(self, other): new_indices = sorted(list(range(len(raw_keys))), key=Key) new_elements = [raw_keys[i] for i in new_indices] # reorder elements return li.restructure(li.head, new_elements, evaluation) - - -class Through(Builtin): - """ - - :WMA link: - https://reference.wolfram.com/language/ref/Through.html - -
-
'Through[$p$[$f$][$x$]]' -
gives $p$[$f$[$x$]]. -
- - >> Through[f[g][x]] - = f[g[x]] - >> Through[p[f, g][x]] - = p[f[x], g[x]] - """ - - summary_text = "distribute operators that appears inside the head of expressions" - - def eval(self, p, args, x, evaluation: Evaluation): - "Through[p_[args___][x___]]" - - elements = [] - for element in args.get_sequence(): - elements.append(Expression(element, *x.get_sequence())) - return Expression(p, *elements) diff --git a/mathics/builtin/exp_structure/head_related.py b/mathics/builtin/exp_structure/head_related.py new file mode 100644 index 000000000..740da2725 --- /dev/null +++ b/mathics/builtin/exp_structure/head_related.py @@ -0,0 +1,102 @@ +""" +Head-Related Operations +""" + +from mathics.core.builtin import Builtin +from mathics.core.expression import Evaluation, Expression +from mathics.core.symbols import Atom +from mathics.core.systemsymbols import SymbolOperate + + +class Operate(Builtin): + """ + + :WMA link: + https://reference.wolfram.com/language/ref/Operate.html + +
+
'Operate[$p$, $expr$]' +
applies $p$ to the head of $expr$. + +
'Operate[$p$, $expr$, $n$]' +
applies $p$ to the $n$th head of $expr$. +
+ + >> Operate[p, f[a, b]] + = p[f][a, b] + + The default value of $n$ is 1: + >> Operate[p, f[a, b], 1] + = p[f][a, b] + + With $n$=0, 'Operate' acts like 'Apply': + >> Operate[p, f[a][b][c], 0] + = p[f[a][b][c]] + """ + + summary_text = "apply a function to the head of an expression" + messages = { + "intnn": "Non-negative integer expected at position `2` in `1`.", + } + + def eval(self, p, expr, n, evaluation: Evaluation): + "Operate[p_, expr_, Optional[n_, 1]]" + + head_depth = n.get_int_value() + if head_depth is None or head_depth < 0: + evaluation.message( + "Operate", "intnn", Expression(SymbolOperate, p, expr, n), 3 + ) + return + + if head_depth == 0: + # Act like Apply + return Expression(p, expr) + + if isinstance(expr, Atom): + return expr + + expr = expr.copy() + e = expr + + for i in range(1, head_depth): + e = e.head + if isinstance(e, Atom): + # n is higher than the depth of heads in expr: return + # expr unmodified. + return expr + + # Otherwise, if we get here, e.head points to the head we need + # to apply p to. Python's reference semantics mean that this + # assignment modifies expr as well. + e.set_head(Expression(p, e.head)) + + return expr + + +class Through(Builtin): + """ + + :WMA link: + https://reference.wolfram.com/language/ref/Through.html + +
+
'Through[$p$[$f$][$x$]]' +
gives $p$[$f$[$x$]]. +
+ + >> Through[f[g][x]] + = f[g[x]] + >> Through[p[f, g][x]] + = p[f[x], g[x]] + """ + + summary_text = "distribute operators that appears inside the head of expressions" + + def eval(self, p, args, x, evaluation: Evaluation): + "Through[p_[args___][x___]]" + + elements = [] + for element in args.get_sequence(): + elements.append(Expression(element, *x.get_sequence())) + return Expression(p, *elements) diff --git a/mathics/core/systemsymbols.py b/mathics/core/systemsymbols.py index c14097291..55bb40960 100644 --- a/mathics/core/systemsymbols.py +++ b/mathics/core/systemsymbols.py @@ -230,8 +230,8 @@ SymbolSin = Symbol("System`Sin") SymbolSinh = Symbol("System`Sinh") SymbolSlot = Symbol("System`Slot") -SymbolSparseArray = Symbol("System`SparseArray") SymbolSortBy = Symbol("System`SortBy") +SymbolSparseArray = Symbol("System`SparseArray") SymbolSplit = Symbol("System`Split") SymbolSqrt = Symbol("System`Sqrt") SymbolSqrtBox = Symbol("System`SqrtBox")