【问题标题】:PatternTest not optimized?PatternTest 没有优化?
【发布时间】:2011-12-13 04:23:23
【问题描述】:

在准备对An unexpected behavior of PatternTest in Mathematica 的回复时,我遇到了我自己的意外Mathematica 行为。

请考虑:

test = (Print[##]; False) &;
MatchQ[{1, 2, 3, 4, 5}, {x__?test, y__}]
在计算 In[1]:= 1 期间

在评估 In[1]:= 1

在评估 In[1]:= 1

在评估 In[1]:= 1
错误

因为,正如西蒙对文档的引用简明扼要地指出:

__?test 之类的形式,序列中的每个元素都与__ 匹配 应用测试时必须产生True

我想知道为什么 Mathematica 会分别测试列表的第一个元素四次。当然有四种方法可以构成基本模式{x__, y__},如果这是一个Condition 测试,那么构成序列x 的所有元素都需要进行测试,但我认为这不是案例在这里。

如果列表的第一个元素失败PatternTest 那么给定的模式不能匹配,逻辑是否不成立?

如果它成立,为什么 Mathematica 不做这个简单的优化?


从 yoda 的回答中借用一个例子,这里是另一个似乎是过度评估的例子:

In[1]:= test2 = (Print@##; Positive@##) &;
MatchQ[{1, 2, 3, 4, -5}, {x__?test2, y__?Negative}]

During evaluation of In[1]:= 1

During evaluation of In[1]:= 1

During evaluation of In[1]:= 2

During evaluation of In[1]:= 1

During evaluation of In[1]:= 2

During evaluation of In[1]:= 3

During evaluation of In[1]:= 1

During evaluation of In[1]:= 2

During evaluation of In[1]:= 3

During evaluation of In[1]:= 4

Out[2]= True

我承认我以前从未探索过模式匹配的这一方面,我对这看似低效的做法感到不安。这真的像看起来那么糟糕,还是发生了某种自动缓存? Print 似乎对此表示反对。

  • 有没有更有效的基于模式的方法来写这个?

  • 正确的模式匹配行为是否需要这种冗余级别,为什么?


我仓促做出了错误的断言,但我留下了它,因为下面的好答案解决了这个问题。请在以后的答案中忽略它。

很容易证明在其他方面也进行了类似的优化 案例:

MatchQ[{-1, 2, 3, 4, 5}, {__?Positive, y__?test}]

(什么都不打印。)

错误

这里 Mathematica 正确地甚至从未测试任何元素 y

【问题讨论】:

    标签: wolfram-mathematica pattern-matching


    【解决方案1】:

    我认为每个人都忘记了测试函数中可能出现的副作用。这就是我认为正在发生的事情:正如 Mr.Wizard 和其他人所提到的,模式可能有几种匹配方式,只是组合方式。对于{x}{y} 的每个组合,首先测试x 模式。顺便说一句,定义多个参数的函数(##)没有意义,因为正如@Simon 解释的那样,测试函数分别应用于序列中的每个元素。这也解释了为什么只打印第一个元素(-1):一旦找到第一个不匹配的元素,模式匹配器就会停止并继续测试下一个可用的组合。

    下面是一个更具说明性的例子:

    In[20]:= 
    MatchQ[{-1,2,3,4,5},{_,x__?(Function[Print[#];Positive[#]])}]
    During evaluation of In[20]:= 2
    During evaluation of In[20]:= 3
    During evaluation of In[20]:= 4
    During evaluation of In[20]:= 5
    
    Out[20]= True
    

    现在它会打印所有这些,因为函数会一一应用于它们,就像在这种情况下一样。

    现在到问题的关键了。在这里,我设置了一个带有副作用的测试函数,它决定在测试第一个元素后改变主意:

    Module[{flag = False}, 
      ClearAll[test3]; 
      test3[x_] := 
         With[{fl = flag}, 
            If[! flag, flag = True]; 
            Print[x]; 
            fl
         ]
    ];
    

    第一个组合({-1},{2,3,4,5} 将被拒绝,因为函数首先给出False。但是第二个({-1,2},{3,4,5})将被接受。这正是我们观察到的:

    In[22]:= 
    MatchQ[{-1,2,3,4,5},{x__?test3,y__}]
    During evaluation of In[22]:= -1
    During evaluation of In[22]:= -1
    During evaluation of In[22]:= 2
    
    Out[22]= True
    

    模式匹配器一找到匹配项,打印就会停止。

    现在,从这里必须很明显,问题和其他一些答案中提到的优化通常是不可能的,因为模式匹配器无法控制测试函数中可能存在的可变状态。

    在考虑模式匹配时,我们通常将其视为一个独立于求值的过程,这在很大程度上是正确的,因为模式匹配器是系统的内置组件,一旦模式和表达式求值,它就会接受并在很大程度上绕过了主要的评估循环。然而,也有一些值得注意的例外,这使得模式匹配更加强大,但代价是它与评估器纠缠在一起。其中包括ConditionPatternTest 的使用,因为这两个是主要评估过程的“入口点”,进入原本与之隔离的模式匹配过程。一旦模式匹配器达到其中之一,它就会根据要测试的条件调用主评估器,然后一切皆有可能。这让我再次观察到模式匹配器在使用 PatternTestCondition 的测试不存在时最有效,并且模式完全符合语法 - 在 的情况下,它可以优化.

    【讨论】:

    • Leonid,使用带有副作用的模式测试进行编程(而不是简单地用于调试)即使对我来说也太"perverse",而且我认为我已经成为@987654322 的疯子@ 标签。你真的容忍这个有什么用吗?
    • @Mr.Wizard 我同意这不是最佳做法,但有时这可能是必要的。例如,有时您可能希望根据全局条件打开和关闭函数的某些定义,例如f[x_]:=something/;someVariable,其中someVariable 是控制条件的全局变量或表达式。我有几个案例,这是实现我想要的唯一简单方法。但由于您的问题是关于一般算法,而不是代码风格,所以答案似乎是不能排除这种可能性,因此没有一般优化。
    • 我的猜测是,这已经够晦涩和反常了,最好排除这种使用并进行优化。我想你的经验不是这样的。尽管如此,在Mathematica 编程中广泛使用的模式匹配由于极端情况而被放慢速度还是令人失望的。恕我直言,如果需要一个特殊的处理程序(如VerbatimPatternHold 等)来激活“maybe-this-uses-side-effects”算法,那就更好了。你怎么看?
    • @Mr.Wizard 会很好,但会导致错误,因为它把更多的责任放在程序员的肩上,如果他/她使用不当,优化会毁掉他们的代码非常微妙的方式。即使我举了一个例子,也有优化不重新计算someVariable,这让我非常头疼,直到我想起我需要调用Update[f]。但是,我认为具有副作用的测试功能通常不能被视为极端情况。它们仅适用于像我们这样的人通常关心的问题的子集,它们是......
    • @Mr.Wizard ...算法的高度。许多现实世界的案例更加临时,并且可能取决于我们无法控制的一些外部因素。您当然可能会争辩说,模式匹配对那些人来说可能是一个错误的范例,但由于这是最深层次的原生 Mathematica 构造,它通常会大大简化 Mathematica 中的事情。事实似乎在中间的某个地方:我们通常应该避免以这种方式使用模式,但完全排除这种使用会在 IMO 过于极端——在现实世界中,可变性很重要。
    【解决方案2】:

    这里只是一个简单的猜测,但{__?Positive, y__?test} 必须匹配列表的开头直到结尾。因此,{-1, 2, 3, 4, 5} 在第一个元素上失败,因此没有打印输出。尝试将Positive 替换为((Print[##];Positive[#])&),您会发现它的行为方式与第一个模式相同。

    【讨论】:

    • 你是对的。我纠结于自己的逻辑,懒得花点时间用Trace检查。但是,我的第一个粗体问题仍然存在。
    【解决方案3】:

    我认为 Mathematica 优化模式测试的前提是不正确的。考虑模式{x__, y__}。正如你所说的那样,{1,2,3,4,5} 可以通过 4 种方式适应这种模式。如果您现在使用?test 向它添加模式测试,则MatchQ 应该返回True,如果4 种方式中的任何 匹配该模式。所以它必须测试所有可能的选项直到找到匹配。所以优化只在有正面的时候进行,而不是在模式失败的时候。

    以下是使用 Positive 对您的示例进行的修改,这表明 Mathematica 没有按照您的要求进行优化:

    test2 = (Print@##; Positive@##) &;
    MatchQ[{-1, 2, 3, 4, 5}, {x__?test2, y__}]
    

    它打印了 4 次!如果第一个元素确实是肯定的(因此不必测试其余部分),它会以一次打印正确终止。 在第一个元素是 True 时应用第二个元素的测试,这就是为什么 y 的测试没有应用在您的元素中。这是一个不同的例子:

    test3 = (Print@##; Negative@##) &; 
    MatchQ[{1, 2, 3, 4, -5}, {x__?test2, y__?test3}]
    

    我同意你的逻辑,如果第一个失败,那么考虑到每个元素都应该匹配,所有后续的可能性也会失败。我的猜测是,Mathematica 还没有那么那么聪明,并且为 ______ 中的任何一个实现相同的行为更简单,而不是为每个不同的行为。根据您是使用 __ 还是 ___ 来实现不同的行为会掩盖测试的真实性质并使调试变得更加困难。

    【讨论】:

      【解决方案4】:

      好的,我会去的。

      MatchQ[{1, 2, 3, 4, 5}, {x__?test, y__}]//Trace
      

      表明槽序列是冗余的,因为只有一个元素,在这种情况下是序列中的第一个元素,被测试。例如

      MatchQ[{1, 2, 3, 4, 5}, {_, x__?test, y__}] // Trace
      

      现在打印 3 个二。如果将第一个模式更改为 BlankSequence,则会生成更多排列,其中一些排列具有不同的第一个元素

      MatchQ[{1, 2, 3, 4, 5}, {__, x__?test, y__}] // Trace
      

      在你的最后一个例子中

      MatchQ[{-1, 2, 3, 4, 5}, {__?Positive, y__?test}]
      

      第一个元素总是无法通过测试。如果您将其更改为

      MatchQ[{1, 2, -3, 4, 5}, {__?Positive, y__?test}]
      

      你会看到更多符合你预期的东西。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2016-07-09
        • 1970-01-01
        • 1970-01-01
        • 2017-07-18
        • 2021-03-17
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多