【问题标题】:Arbitrarily Deep Nested Pattern Matching任意深度嵌套模式匹配
【发布时间】:2011-05-11 01:03:57
【问题描述】:

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

【问题讨论】:

  • WReach 在线程"Currying with Mathematica" 中回答了类似的问题。但他的代码只适用于固定的表达式深度。
  • 欢迎来到 StackOverflow。如果您对该网站有任何疑问,请参阅this FAQ。请记得vote for and accept好的答案。
  • +1,对我来说,这个问题在尝试匹配Derivative[_][_][_]时最常出现,而且每次都很烦人。

标签: wolfram-mathematica


【解决方案1】:

建议的解决方案

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

进一步解释和cmets

工作原理

以下是导致此解决方案的逻辑及其工作原理的一些提示。如果我们设法利用一些内置的表达式遍历函数,该解决方案将是最简洁和高效的。想到的一些是MapScanCasesMapIndexedPosition。鉴于我们需要磁头,我们需要传递Heads->True 选项。我使用了Scan,因为这个很容易在任何时候停止(与其他提到的构造不同,你通常需要抛出一个异常来阻止它们“在中间”,这是相当不雅的并且会导致一些开销以及)一旦我们找到我们想要的。我们的结果将是Scan 在其深度优先表达式遍历中发现的第一件事,因此预计它会非常有效(它不会遍历整个表达式)。

避免评估泄漏

另一条评论是关于评估的。可以看到shead中使用了HoldAllComplete属性,其body中使用了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 函数,具有here 的这种行为,也可以成功地使用它来代替上面的shead,尽管shead 更有效。

【讨论】:

  • 如果我能多次投票,我会的。你是主教练。
  • @Mr.Wizard 谢谢!但正是我们基于评论的讨论促使我在这里进行。我希望我总是有态度为我的答案找到合适的措辞。
【解决方案2】:

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

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

【讨论】:

  • 递归模式的一个很好的使用 - +1。
  • 所以这就是如何让它发挥作用。这是我尝试的第一件事,我立即进行了无限递归。
  • 虽然我非常喜欢这个想法,但我必须说,即使是中等深度嵌套的头,这个解决方案的性能也很差。以下替代方案curriedAlt[head_] := _head | (x_[___] /; MatchQ[Unevaluated[x], curriedAlt[head]]) 提供了更好的性能,但基本相同。可以看到与例如的区别。此代码Cases[{HeadCompose[f, Sequence @@ Range[15]]}, curried[f]]curriedAlt 相同。
  • @Leonid UnevaluatedHold 更清楚地反映了意图,因此即使没有性能差异,我也更喜欢它。 MatchQ[Hold[...], _[...]] 模式只是我盲目地遵循我在不相关的上下文中使用的习语。我会更新我的答案以包含您的建议。
  • 我明白了 - 我也使用了很多类似于你提到的模式。是否喜欢设计和其他模式,但我们似乎经常在其中思考。
【解决方案3】:

以下内容如何:

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

【讨论】:

    【解决方案4】:

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

    【讨论】:

    • 很抱歉让您失望了,但错误信息的出现是有原因的。最后你得到的是一个非常深的嵌套模式,其深度由$RecursionLimit 控制。但是,它只适用于头部嵌套大致不超过该深度的表达式。这有效:Block[{$RecursionLimit = 20, p, n = 17}, MatchQ[HeadCompose[f, Sequence @@ Range[n]], p = _f | p@_]] ,但如果您将n 更改为18,它将停止工作。具有讽刺意味的是,您需要将 $RecursionLimit 设置为 Infinity 才能使您的构造正常工作。
    • @Leonid 就像我说的这只是一个玩具,但我认为假设头部深度不会超过默认 $RecursionLimit 256 是相当安全的。
    • @Mr.Wizard 我不会对头部深度做任何假设,因为它与$RecursionLimit 无关。但是,至少在概念上,这是一种治愈您的方法的方法。问题是似乎没有办法懒惰地评估Alternatives(也就是说,在通过模式匹配发现第一部分不匹配之前不评估它的第二部分),因为虽然从从左到右,这是在模式匹配器内部完成的,我们无法控制。 Alternatives 的惰性版本将保存您的方法。下面是一个可能的实现。
    • 继续...ClearAll[lazyAlternatives]; SetAttributes[lazyAlternatives, HoldRest]; lazyAlternatives[expr_] := expr; lazyAlternatives[first_, rest__] := _?(Function[ MatchQ[#, first] || MatchQ[#, lazyAlternatives[rest]]])。现在,您可以测试:MatchQ[HeadCompose[f, Sequence @@ Range[100]], p = lazyAlternatives[_f, p@_]] 给出True,没有任何生成的错误。但是,当然,代码不再像你想的那么简短,因为我们需要这个辅助函数。对于嵌套非常深的头,您仍然需要增加$RecursionLimit,以使其正常工作。
    • 某种意义上,@WReach 建议的方法也实现了惰性Alternatives 的一个版本,其中惰性是通过单独的头部curriedConditionHoldAll 属性实现的.他的版本似乎是实现这一目标的最优雅的方式。
    【解决方案5】:

    这是@Leonid 的shead 的替代版本,用于查找表达式的符号头。 (您应该按原样使用 his solution 的其余部分。)我的函数不涉及任何递归,而是使用 Level 有一个特殊情况,将 levelspec 设置为 {-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 在 levelspec 设置为 {-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}}
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-01-30
      • 1970-01-01
      • 1970-01-01
      • 2022-01-11
      • 1970-01-01
      • 2016-06-18
      • 2012-03-07
      相关资源
      最近更新 更多