任意深度嵌套模式匹配

时间:2011-05-11 01:03:57

标签: wolfram-mathematica

有没有办法创建一个Mathematica模式,该模式匹配头部可能任意深度的表达式,例如f[___][___][___]...

5 个答案:

答案 0 :(得分:12)

建议的解决方案

似乎没有内置构造来自动对嵌套头进行模式测试。我们可以通过编写一个函数来实现目标,该函数对于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]}

进一步的解释和评论

如何运作

以下是导致此解决方案的逻辑以及工作原理的一些提示。如果我们设法利用一些内置的表达式遍历函数,那么解决方案将是最简洁和高效的。我想到的一些问题包括MapScanCasesMapIndexedPosition。鉴于我们需要头部,我们需要传递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没有触发子部分的评估。 {1}}。现在我们定义一个简单的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)。我已经使用此行为here开发了symbolicHead函数,并且在上面的shead中也可以成功使用该函数,尽管shead效率更高。< / p>

答案 1 :(得分:5)

以下内容如何:

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]}

答案 2 :(得分:5)

这里可以使用递归匹配策略:

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]}

<强>更新

根据Leonid的建议,Unevaluated可以用作更清晰,更快捷的方法来避免模式条件中的评估泄漏:

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

答案 3 :(得分:3)

WReach的回答让我重新审视了一个递归定义,我昨天尝试过然后放弃了。

我现在意识到我实际上工作了,它只会引发错误。与Leonid的精细方法相比,它是一个玩具,但我喜欢简洁的代码,所以我在这里发布它的兴趣或娱乐。在运行此选项之前,请确保没有将$RecursionLimit设置为Infinity。

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@_
]

答案 4 :(得分:0)

这是@ Leonid&#39; s SELECT Course_NO , COUNT(section_ID) [Sections] , Course_NO * COUNT(section_ID) [Capacity] FROM SECTION GROUP BY course_No HAVING COUNT(section_id) > 3; 的替代版本,用于查找表达式的符号头部。 (你应该按原样使用余下的his solution。)我的函数不涉及任何递归,而是使用Level有一个特殊情况,其中将levelspec设置为{{1}返回所有原子表达式,第一个是头本身:

shead

它在模式匹配中的作用与{-1}的作用相同:

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

为了帮助理解它是如何工作的,以下是shead在levelpec设置为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