Шаблон для сопоставления только "детей" определенных элементов - PullRequest
7 голосов
/ 23 июня 2011

Я хотел бы иметь возможность иметь шаблон, который соответствует только выражениям, которые (альтернативно: не являются) потомками некоторых других элементов.

Например, шаблон, соответствующий всем List s, не входящим в Graphics объект:

{ {1,2,3}, Graphics[Line[{{1,2},{3,4}}]] }

Этот шаблон будет соответствовать {1,2,3}, но не {{1,2},{3,4}}.

Существуют относительно простые способы извлечения выражений, соответствующих этим критериям, но шаблоны предназначены не только для извлечения, но и для замены , что является моим основным вариантом использования здесь (ReplaceAll).

Знаете ли вы какие-либо простые, сжатые и общие способы сделать это?

Можно ли вообще сделать это только с помощью шаблонов?

Ответы [ 4 ]

7 голосов
/ 23 июня 2011

Я предложу решение, основанное на предварительной обработке выражений и мягких переопределениях операций с использованием правил, а не самих правил. Вот код:

ClearAll[matchChildren, exceptChildren];
Module[{h, preprocess},
  preprocess[expr_, parentPtrn_, lhs_, match : (True | False)] :=
     Module[{pos, ptrnPos, lhsPos},
       ptrnPos = Position[expr, parentPtrn];
       lhsPos = Position[expr, lhs];
       pos = Cases[lhsPos, {Alternatives @@ PatternSequence @@@ ptrnPos, __}];
       If[! match,pos = Complement[Position[expr, _, Infinity, Heads -> False], pos]];
       MapAt[h, expr, pos]];

  matchChildren /: 
    fun_[expr_, matchChildren[parentPtrn_, lhs : Except[_Rule | _RuleDelayed]],
    args___] :=
       fun[preprocess[expr, parentPtrn, lhs, True], h[lhs], args] //. 
           h[x_] :> x;

  matchChildren /: 
    fun_[expr_, matchChildren[parentPtrn_, lhs_ :> rhs_], args___] :=
       fun[preprocess[expr, parentPtrn, lhs, True], h[lhs] :> rhs, args] //. 
           h[x_] :> x;

  exceptChildren /: 
   fun_[expr_,exceptChildren[parentPtrn_, lhs : Except[_Rule | _RuleDelayed]], 
   args___] :=
       fun[preprocess[expr, parentPtrn, lhs, False], h[lhs], args] //. 
           h[x_] :> x;

  exceptChildren /: 
   fun_[expr_, exceptChildren[parentPtrn_, lhs_ :> rhs_], args___] :=
       fun[preprocess[expr, parentPtrn, lhs, False], h[lhs] :> rhs, args] //. 
          h[x_] :> x;
]

Несколько подробностей об идеях реализации и о том, как это работает. Идея состоит в том, чтобы ограничить шаблон, который должен соответствовать, мы можем обернуть этот шаблон в какую-то голову (скажем, h), а также обернуть все элементы, соответствующие исходному шаблону, но также находящиеся (или не являющиеся) в некотором другом элемент (соответствующий «родительскому» шаблону) в той же голове h. Это можно сделать для общего «дочернего» шаблона. Технически, одна вещь, которая делает это возможным, это навязчивая природа применения правил (и передача параметров функции, которые имеют одинаковую семантику в этом отношении). Это позволяет взять правило типа x_List:>f[x], соответствующее универсальному шаблону lhs_:>rhs_, и изменить его на h[x_List]:>f[x], в общем случае, используя h[lhs]:>rhs. Это нетривиально, потому что RuleDelayed - это ограниченная конструкция, и только навязчивость другого RuleDelayed (или передача параметров функции) позволяет нам выполнять необходимую операцию области. В некотором смысле, это пример конструктивного использования того же эффекта, который приводит к негерметичной функциональной абстракции в Mathematica. Другой технической деталью здесь является использование UpValues для перегрузки функций, которые используют правила (Cases, ReplaceAll и т. Д.) «Мягким» способом, без добавления каких-либо правил к ним. В то же время UpValues здесь позволяет коду быть универсальным - один код выполняет множество функций, использующих шаблоны и правила. Наконец, я использую переменные Module в качестве механизма инкапсуляции, чтобы скрыть вспомогательную головку h и функцию preprocess. Как правило, это очень удобный способ добиться инкапсуляции как функций, так и данных в масштабе, меньшем, чем пакет, но больше, чем одна функция.

Вот несколько примеров:

In[171]:= expr = {{1,2,3},Graphics[Line[{{1,2},{3,4}}]]};

In[168]:= expr/.matchChildren[_Graphics,x_List:>f[x]]//FullForm
Out[168]//FullForm= List[List[1,2,3],Graphics[Line[f[List[List[1,2],List[3,4]]]]]]

In[172]:= expr/.matchChildren[_Graphics,x:{__Integer}:>f[x]]//FullForm
Out[172]//FullForm= List[List[1,2,3],Graphics[Line[List[f[List[1,2]],f[List[3,4]]]]]]

In[173]:= expr/.exceptChildren[_Graphics,x_List:>f[x]]//FullForm
Out[173]//FullForm= List[f[List[1,2,3]],Graphics[Line[List[List[1,2],List[3,4]]]]]

In[174]:= expr = (Tan[p]*Cot[p+q])*(Sin[Pi n]+Cos[Pi m])*(Tan[q]+Cot[q]);

In[175]:= expr/.matchChildren[_Plus,x_Tan:>f[x]]
Out[175]= Cot[p+q] (Cot[q]+f[Tan[q]]) (Cos[m \[Pi]]+Sin[n \[Pi]]) Tan[p]

In[176]:= expr/.exceptChildren[_Plus,x_Tan:>f[x]]
Out[176]= Cot[p+q] f[Tan[p]] (Cos[m \[Pi]]+Sin[n \[Pi]]) (Cot[q]+Tan[q])

In[177]:= Cases[expr,matchChildren[_Plus,x_Tan:>f[x]],Infinity]
Out[177]= {f[Tan[q]]}

In[178]:= Cases[expr,exceptChildren[_Plus,x_Tan:>f[x]],Infinity]
Out[178]= {f[Tan[p]]}

In[179]:= Cases[expr,matchChildren[_Plus,x_Tan],Infinity]
Out[179]= {Tan[q]}

In[180]:= Cases[expr,matchChildren[_Plus,x_Tan],Infinity]
Out[180]= {Tan[q]}

Ожидается, что он будет работать с большинством функций, имеющих формат fun[expr_,rule_,otherArgs___]. В частности, это включает Cases,DeleteCases, Replace, ReplaceAll,ReplaceRepeated. Я не обобщал списки правил, но это должно быть легко сделать. Это может не работать должным образом в некоторых тонких случаях, например с нетривиальными головками и сопоставлением с рисунком на головах.

6 голосов
/ 23 июня 2011

Согласно вашему объяснению в комментарии к ответу acl:

На самом деле я бы хотел, чтобы это работало на любом уровень в выражении <...>. <...> что мне нужно, это замена: заменить все выражения, которые соответствуют этому "шаблон", а остальное оставь без изменений. Я думаю самый простой Возможное решение - найти положения элементов, затем с помощью ReplacePart. Но это также может получить довольно сложно в конце концов.

Я думаю, что это можно сделать за один проход с ReplaceAll. Здесь мы можем положиться на документированную особенность ReplaceAll: она не смотрит на части исходного выражения, которые уже были заменены, даже если они заменяются собой! Ссылаясь на документацию: «ReplaceAll просматривает каждую часть expr, пробует все правила на ней и затем переходит к следующей части expr. используется первое правило, которое применяется к определенной части; дальнейшие правила для этой части или для любой из ее частей не пробуются. "

Вот мое решение (whatIwant - это то, что вы хотите сделать с подобранными деталями):

replaceNonChildren[lst_List] := 
 ReplaceAll[#, {x_List :> whatIwant[x], y_ :> y}] & /@ lst

Вот ваш тестовый пример:

replaceNonChildren[{{1, 2, 3}, Graphics[Line[{{1, 2}, {3, 4}}]]}] // InputForm
=> {whatIwant[{1, 2, 3}], Graphics[Line[{{1, 2}, {3, 4}}]]}

Вот функция, которая заменяет только внутри определенной головки (Graphics в этом примере):

replaceChildren[lst_List] := 
 ReplaceAll[#, {y : Graphics[__] :> (y /. x_List :> whatIwant[x])}] & /@ lst

Вот тестовый пример:

replaceChildren[{{1, 2, 3}, Graphics[Line[{{1, 2}, {3, 4}}]]}] // InputForm
=> {{1, 2, 3}, Graphics[Line[whatIwant[{{1, 2}, {3, 4}}]]]}
3 голосов
/ 23 июня 2011

Вы можете написать рекурсивную функцию, которая спускается в дерево выражений и действует на нужные вам типы выражений, только если внутри правильного типа подвыражения, оставляя все остальное в покое. Шаблоны будут активно использоваться в определении функции.

Рассмотрим, например, следующее выражение.

test = {{1, 2}, Graphics[{
  Point[{{-1, 0}, {1, 0}}],
  Line[{{-1, 0}, {1, 0}}]},
 Frame -> True, 
 PlotRange -> {{-1, 1}, {-0.5, 0.5}}]};

Предположим, что мы хотим повернуть каждую упорядоченную пару, которую мы видим в первом аргументе Graphics о начале координат, на угол Pi / 4, оставляя при этом другие точки в покое. Следующая функция делает это.

Clear[f];
f[{x_?NumericQ, y_?NumericQ}] := If[flag === True,
  RotationMatrix[Pi/4].{x, y}, {x, y}];
f[Graphics[primitives_, rest___]] := Block[{flag = True},
  Graphics[f[primitives], rest]];
f[x_?AtomQ] := x;
f[x_] := f /@ x;

Теперь мы проверяем

f[test]
0 голосов
/ 23 июня 2011

Возможно, я вас неправильно понимаю, но, если я правильно понимаю, вы хотите сопоставить все выражения с заголовком List, у которых есть свойство, что при переходе вверх в дереве выражений мы никогда не встретим Graphics. Я не уверен, как сделать это за один проход, но если вы хотите совпадать дважды, вы можете сделать что-то вроде

lst = {randhead[5], {1, 2, {3, 5}}, Graphics[Line[{{1, 2}, {3, 4}}]]};
Cases[#, _List] &@Cases[#, Except@Graphics[___]] &@lst
(*
----> {{1, 2, {3, 5}}}
*)

, который сначала выбирает элементы, так что Head не Graphics (это делается с помощью Cases[#, Except@Graphics[___]] &, который возвращает {randhead[5], {1, 2, {3, 5}}}), затем выбирает элементы с Head List из возвращенного списка. Обратите внимание, что я добавил еще кое-что к lst.

Но, по-видимому, вы знали об этом и собирались выполнить какой-либо шаблон?

...