我早些时候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]
我希望allTrue[{x,{1,2,3}},x>0]
返回True
,无论{1,2,3}
是否自动转换为PackedArray
,有什么更好的方式来实现它?
答案 0 :(得分:7)
这是我已经使用了很长一段时间的版本(最初为我的第二版写了它,但我最终使用了它很多)。如果参数代表一些未评估的代码,那么如果我们想要一个代表一个特定子句的单个代码以未评估的形式传递给它,那么测试函数必须具有HoldAll
或HoldFirst
属性(可能或可能不合适。)
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
的子句,如果您不关心它们,则必须将您的值列表包装在Evaluate
中将在开始时进行评估(与上面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;
];
几句解释:我在顶层使用Module,因为我们只需要定义一次自定义异常标记,也可以在定义时执行。突破表的方法是通过例外。不是很优雅并且会导致小的性能损失,但我们购买由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
处理。最后,请注意(正如我在别处评论过的那样),AllTrue
和AnyTrue
的原始签名有点过于严格,因为以下内容不起作用:
In[47]:= lst = Range[5];
AllTrue[{i, lst}, i > 3]
Out[48]= AllTrue[{i, lst}, i > 3]
(由于lst
属性,因此HoldAll
表示列表的事实在模式匹配时是未知的。保留此行为是没有充分理由的,因此您只需删除_List
支票:AnyTrue[iter : {var_Symbol, lis_}, expr_]
,同样删除AllTrue
,并且此类用例将被涵盖。