【问题标题】:Non-standard evaluation and PackedArray非标准评估和 PackedArray
【发布时间】:2011-02-06 05:42:32
【问题描述】:

我之前有 asked 如何制作 allTrue[{x,list},test] 函数来保护占位符符号 x 在当前上下文中不被评估,就像 Table[expr,{x,...}] 保护 x 一样

我最终使用的配方间歇性地失败了,我发现问题是由列表自动转换为 PackedArrays 引起的。这是一个失败的例子

SetAttributes[allTrue, HoldAll];
allTrue[{var_, lis_}, expr_] := 
  LengthWhile[lis, 
    TrueQ[ReleaseHold[Hold[expr] /. HoldPattern[var] -> #]] &] == 
   Length[lis];
allTrue[{y, Developer`ToPackedArray[{1, 1, 1}]}, y > 0]

无论{1,2,3}是否自动转换为PackedArray,我都希望allTrue[{x,{1,2,3}},x>0]返回True,有什么更好的实现方式?

【问题讨论】:

  • 不确定性能影响,但几乎可以肯定 LengthWhile[FromPackedArray@lis, ... 有效
  • 是的,这就是我现在正在使用的,但我对这种任务的整体风格更感兴趣
  • 来自文档使用 ToPackedArray 不会改变 Mathematica 生成的结果 ... lier!骗子!裤子着火了!
  • @belisarius 我被告知该错误已在源代码中修复,因此在 8.0.1 中有望再次出现这种情况(实际上,该错误仅影响 8.0,7.x 不受影响)

标签: wolfram-mathematica


【解决方案1】:

这是我已经使用了很长一段时间的版本(最初是为我的书的第二版编写的,但最终我经常使用它)。如果参数表示一些未评估的代码,那么测试函数必须具有 HoldAllHoldFirst 属性,如果我们希望将代表一个特定子句的单个代码以其未评估的形式传递给它(这可能是也可能不是可取的)。

ClearAll[fastOr];
Attributes[fastOr] = {HoldRest};
fastOr[test_, {args___}] := fastOr[test, args];
fastOr[test_, args___] :=
TrueQ[Scan[
        Function[arg, If[test[arg], Return[True]], HoldAll],
        Hold[args]]];

编辑:我刚刚注意到问题中链接的页面底部的 Daniel Reeves 的解决方案与这个非常相似。主要区别在于我关心短路和保持论点不被评估(见下文),而丹尼尔只关注短路部分。

它确实有短路行为。我们需要HoldRest 属性,因为我们想以未评估的形式保留参数。我们还需要纯函数中的HoldAll(或HoldFirst)属性来保留每个未计算的参数,直到它被传递给test。它在用于test 的主体之前是否被评估现在取决于test 的属性。举个例子:

Clear[fullSquareQ];
fullSquareQ[x_Integer] := IntegerQ[Sqrt[x]];

In[13]:= Or @@ Map[fullSquareQ, Range[50000]] // Timing

Out[13]= {0.594, True}

In[14]:= fastOr[fullSquareQ, Evaluate[Range[10000]]] // Timing

Out[14]= {0., True}

这是一个示例,我们将一些代码片段作为参数传递,从而引发副作用(打印)。最后一个参数的代码没有机会执行,因为在前一个子句中已经确定了结果:

In[15]:= fastOr[# &, Print["*"]; False, Print["**"]; False, 
  Print["***"]; True, Print["****"]; False]

During evaluation of In[15]:= *

During evaluation of In[15]:= **

During evaluation of In[15]:= ***

Out[15]= True

请注意,由于fastOr 接受一般的未评估代码片段作为Or 的子句,因此如果您不关心它们将在开始(就像上面的 Range 示例一样)。

最后,我将说明 fastOr 的保留代码的编程构造,以展示如何使用它(如果您愿意,可以将其视为关于使用保留表达式的小型速成课程)。以下函数在处理保留表达式时非常有用:

joinHeld[a___Hold] := Hold @@ Replace[Hold[a], Hold[x___] :> Sequence[x], {1}];

例子:

In[26]:= joinHeld[Hold[Print[1]], Hold[Print[2], Print[3]], Hold[], Hold[Print[4]]]

Out[26]= Hold[Print[1], Print[2], Print[3], Print[4]]

以下是我们如何使用它以编程方式构造在上面的 Print-s 示例中使用的保留参数:

In[27]:= 
held = joinHeld @@ MapThread[Hold[Print[#]; #2] &, 
      {NestList[# <> "*" &, "*", 3], {False, False, True, False}}]

Out[27]= Hold[Print["*"]; False, Print["**"]; False, Print["***"]; True, Print["****"]; False]

要将它传递给fastOr,我们将使用另一个有用的习惯用法:追加(或前置)到Hold[args],直到我们获得所有函数参数,然后使用Apply(请注意,一般来说,如果我们不'不希望我们附加/前置的部分进行评估,我们必须将其包装在 Unevaluated 中,所以一般的成语看起来像 Append[Hold[parts___],Unevaluated[newpart]]):

In[28]:= fastOr @@ Prepend[held, # &]

During evaluation of In[28]:= *

During evaluation of In[28]:= **

During evaluation of In[28]:= ***

Out[28]= True

关于你提到的原始实现,你可以看看我前段时间对它的评论。问题是 TakeWhile 和 LengthWhile 在 v. 8.0.0 中存在打包数组的错误,它们在 8.0.1 的源代码中已修复 - 因此,从 8.0.1 开始,您可以使用我的版本或 Michael 的版本。

HTH

编辑:

我刚刚注意到,在您提到的帖子中,您想要一种不同的语法。虽然在这种情况下采用fastOr 中采用的方法并不是很困难,但这里有一个不同的实现,可以说它与这种特定语法的现有语言结构更接近。我建议使用Table 和异常,因为Table 中的迭代器接受与您想要的相同的语法。这里是:

ClearAll[AnyTrue, AllTrue];
SetAttributes[{AnyTrue, AllTrue}, HoldAll];
Module[{exany, exall},
 AnyTrue[iter : {var_Symbol, lis_List}, expr_] :=
    TrueQ[Catch[Table[If[TrueQ[expr], Throw[True, exany]], iter], exany]];
 AllTrue[iter : {var_Symbol, lis_List}, expr_] :=
   Catch[Table[If[! TrueQ[expr], Throw[False, exall]], iter], exall] =!= False;
];

几句话解释一下:我在顶层使用模块,因为我们只需要定义一次的自定义异常标记,也可以在定义时完成。打破 Table 的方法是通过异常。不是很优雅并且对性能的影响很小,但是我们购买了由Table 完成的迭代器变量的自动动态本地化,并且很简单。为了以安全的方式执行此操作,我们必须使用唯一标记标记异常,这样我们就不会错误地捕获其他异常。我发现使用 Module 来创建持久异常标记通常是一个非常有用的技巧。现在,一些例子:

In[40]:= i = 1

Out[40]= 1

In[41]:= AnyTrue[{i, {1, 2, 3, 4, 5}}, i > 3]

Out[41]= True

In[42]:= AnyTrue[{i, {1, 2, 3, 4, 5}}, i > 6]

Out[42]= False

In[43]:= AllTrue[{i, {1, 2, 3, 4, 5}}, i > 3]

Out[43]= False

In[44]:= AllTrue[{i, {1, 2, 3, 4, 5}}, i < 6]

Out[44]= True

In[45]:= AllTrue[{a, {1, 3, 5}}, AnyTrue[{b, {2, 4, 5}}, EvenQ[a + b]]]

Out[45]= True

In[46]:= AnyTrue[{a, {1, 3, 5}}, AllTrue[{b, {2, 4, 5}}, EvenQ[a + b]]]

Out[46]= False

我从对 i 的赋值开始,以表明迭代器变量的可能全局值无关紧要 - 这是由 Table 处理的。最后,请注意(正如我在其他地方评论的那样),您对 AllTrueAnyTrue 的原始签名有点过于严格,因为以下内容不起作用:

In[47]:= lst = Range[5];
AllTrue[{i, lst}, i > 3]

Out[48]= AllTrue[{i, lst}, i > 3] 

(由于HoldAll 属性,lst 表示列表的事实在模式匹配时是未知的)。没有充分的理由保留此行为,因此您只需删除 _List 检查:AnyTrue[iter : {var_Symbol, lis_}, expr_] 和类似的 AllTrue,此类用例将被涵盖。

【讨论】:

  • 这简直太棒了。如果必须做这么多工作才能实现everysome,那么内置的 Mathematica 函数式编程结构似乎有点贫乏。
  • @Harold 这些不是常见的everysome,因为它们处理的是未计算的代码块,而不是已经计算的表达式。这并没有太多工作,大部分帖子都是教学性的,致力于解释,而不仅仅是实现。此外,很可能是我的代码过于复杂。
  • 哦? everysome 是否有更多“常规”定义?我没有在官方文档中看到任何内置内容。 (虽然,我仍然在改进在那里寻找东西)也许我应该把这个作为一个问题添加到 Mathematica.stackexchange.com ...
  • @Harold 我不知道更多的标准定义。我同意它们会很有用。
  • @Harold 但正如我所说,那些没有做我的代码正在做的事情。我的代码适用于未评估的代码块,新的控制流构造也是如此。那里的代码只代表便利功能。我建议您仔细考虑其中的区别。
猜你喜欢
  • 2016-09-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-03-10
  • 1970-01-01
  • 2017-10-27
  • 2020-09-17
  • 2019-03-23
相关资源
最近更新 更多