Произвольно Глубокое Сопоставление Шаблонов - PullRequest
15 голосов
/ 11 мая 2011

Есть ли способ создать шаблон Mathematica, который соответствует выражениям, чьи головы могут быть произвольно глубокими, то есть что-то вроде f[___][___][___]...?

Ответы [ 5 ]

12 голосов
/ 11 мая 2011

Предлагаемое решение

Кажется, что нет встроенной конструкции для автоматического тестирования вложенных головок. Мы можем достичь цели, написав функцию, которая для любого заданного (под) выражения формы f[___]...[___] будет эффективно определять f (что при незначительном злоупотреблении терминологией мы можем вызвать символическую головку для выражения ). Вот код:

ClearAll[shead];
SetAttributes[shead, HoldAllComplete];
shead[expr_] := Scan[Return, Unevaluated[expr], {-1}, Heads -> True];

Вот как это можно использовать (я буду использовать тот же набор тестов, что и @Sasha):

In[105]:= Cases[{f[1], g[f[1]], f[1, 2, 3][1], f[1][2][3][4]}, x_ /; shead[x] === f]

Out[105]= {f[1], f[1, 2, 3][1], f[1][2][3][4]}

Синтаксис шаблона

Если вы предпочитаете использовать синтаксис, предложенный @Sasha, эта версия будет выглядеть как

Clear[headPattern];
headPattern[head_] := _?(Function[Null, shead[#] === head, HoldFirst]);

In[108]:= Cases[{f[1], g[f[1]], f[1, 2, 3][1], f[1][2][3][4]}, headPattern[f]]

Out[108]= {f[1], f[1, 2, 3][1], f[1][2][3][4]}

Дополнительные пояснения и комментарии

Как это работает

Вот некоторые подсказки для логики, которая приводит к этому решению, и как оно работает. Решение будет наиболее кратким и эффективным, если нам удастся использовать некоторые встроенные функции обхода выражений. На ум приходят Map, Scan, Cases, MapIndexed, Position. Учитывая, что нам нужны головы, нам нужно передать опцию Heads->True. Я использовал Scan, так как этот легко остановить в любой точке (в отличие от других упомянутых конструкций, для которых вам обычно нужно бросить исключение, чтобы остановить их «в середине», что довольно неэлегично и вызывает некоторые накладные расходы а также) как только мы найдем то, что хотим. Наш результат будет самым первым, что Scan найдет при обходе выражения в глубину, поэтому ожидается, что он будет очень эффективным (он не будет проходить по всему выражению).

Как избежать утечек оценки

Еще один комментарий к оценке. Вы можете видеть, что атрибут HoldAllComplete используется в shead, а Unevaluated используется в его теле. Это очень важно - они служат для предотвращения возможной оценки выражений, переданных в функцию. Это может иметь значение в таких случаях:

In[110]:= m = n = 0;
g[x_] := n++;
h[x_] := m++;
{Cases[Hold[f[g[1]][h[2]]], x_ /; shead[x] === f :> Hold[x], Infinity], {m, n}}

Out[113]= {{Hold[f[g[1]][h[2]]]}, {0, 0}}

Здесь мы видим то, что ожидаем - даже если Cases прошел через все выражение и подал его (под) части в shead, оценка подчастей не была вызвана shead. Теперь мы определяем наивную версию shead, которая "оценивает утечки":

sheadEval[expr_] := Scan[Return, expr, {-1}, Heads -> True]

А теперь

In[114]:= {Cases[Hold[f[g[1]][h[2]]], x_ /; sheadEval[x] === f :> Hold[x], Infinity], {m, n}}

Out[114]= {{Hold[f[g[1]][h[2]]]}, {2, 1}}

Последнее поведение в целом неудовлетворительное. Вся парадигма «код - данные», столь полезная в метапрограммировании, очень мощна в Mathematica, потому что вы можете использовать правила для деструктурирования кода. Возможная (нежелательная) оценка во время сопоставления с образцом может сильно ухудшить его. Вся проблема в подразделах. Обтекание Hold препятствует оценке всего выражения. Такие функции, как Cases и другие подобные функции для деструктуризации кода, настолько хороши, потому что они не оценивают части при выполнении структурного (синтаксического) сопоставления.

Комментарий к символическим головам

Последний комментарий здесь (в основном об определениях) заключается в том, что функция shead возвращает не совсем то, что обычно называется символической головой в Mathematica. Разница для атомных выражений. Например, shead[f] возвращает f, в то время как для атомарных выражений истинный символический заголовок должен совпадать с заголовком выражения (Symbol в этом случае). Я разработал функцию symbolicHead с таким поведением здесь , и эту функцию также можно успешно использовать вместо shead в приведенном выше, хотя shead более эффективен.

5 голосов
/ 12 мая 2011

Здесь можно использовать рекурсивную стратегию сопоставления:

curried[head_] := _head | (x_[___] /; MatchQ[Hold[x], _[curried[head]]])

Использование:

In[26]:= $testCases = {f, f[1], g[f[1]], f[1,2,3][1], f[1][2][3][4]};
         Cases[$testCases, curried[f]]

Out[27]= {f[1],f[1,2,3][1],f[1][2][3][4]}

Обновление

По предложению Леонида, Unevaluated может использоваться как более ясный и быстрый способ избежать утечек оценки в состоянии шаблона:

curried[head_] := _head | (x_[___] /; MatchQ[Unevaluated[x], curried[head]])
5 голосов
/ 11 мая 2011

Как насчет следующего:

In[277]:= 
ArbitrarilyDeepHeadPattern[
  head_Symbol] := _?(Function[
    MemberQ[
      Position[#, _head, {0, Infinity}, Heads -> True], {0 ...}]])

In[281]:= Cases[{f[1], g[f[1]], f[1, 2, 3][1], f[1][2][3][4]}, 
 ArbitrarilyDeepHeadPattern[f]]

Out[281]= {f[1], f[1, 2, 3][1], f[1][2][3][4]}
3 голосов
/ 12 мая 2011

Ответ WReach заставил меня пересмотреть рекурсивное определение, которое я попробовал вчера, но отказался от него.

Теперь я понимаю, что то, что у меня на самом деле работает, просто выдает ошибку. Это игрушка по сравнению с изящным методом Леонида, но я люблю лаконичный код, поэтому выкладываю его здесь для интереса или развлечения. Убедитесь, что для $RecursionLimit не установлено бесконечность, прежде чем запускать это.

Cases[
  {f, f[1], g[f[1]], f[1, 2, 3][1], f[1][2][3][4]}, 
  f // Blank@#|#0[#]@_&
]

Или даже:

Cases[
  {f, f[1], g[f[1]], f[1, 2, 3][1], f[1][2][3][4]},
  p=_f|p@_
]
0 голосов
/ 08 февраля 2016

Вот альтернативная версия @ Leonid's shead, чтобы найти символическую голову выражения. (Вы должны использовать оставшуюся часть его решения как есть.) Моя функция не включает никакой рекурсии, но вместо этого она использует Level в особом случае, когда для levelpecpec установлено значение {-1} возвращает все атомарные выражения, первым из которых является сама голова:

shead2[expr_] := First@Level[expr, {-1}, Heads -> True];

Он работает в режиме сопоставления с образцом точно так же, как shead:

In[264]:= Cases[{f[1], g[f[1]], f[1, 2, 3][1], f[1][2][3][4]}, 
 x_ /; shead2[x] === f]

Out[264]= {f[1], f[1, 2, 3][1], f[1][2][3][4]}

И чтобы понять, как это работает, вот как Level ведет себя с уровнями, установленными в {-1}:

In[263]:= 
Level[#, {-1}, Heads -> True] & /@ {f[1], g[f[1]], f[1, 2, 3][1], 
  f[1][2][3][4]}

Out[263]= {{f, 1}, {g, f, 1}, {f, 1, 2, 3, 1}, {f, 1, 2, 3, 4}}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...