检查列表是否是函数参数中的数字列表的推荐方法是什么



我一直在寻找检查函数参数的方法。我注意到 MatrixQ需要 2 个参数,第二个是应用于每个元素的测试。

ListQ只需要一个论点。(也是出于某种原因,?ListQ没有像?MatrixQ那样的帮助页面(。

因此,例如,为了检查函数的参数是否是数字矩阵,我写

ClearAll[foo]
foo[a_?(MatrixQ[#, NumberQ] &)] := Module[{}, a + 1]

对列表执行相同操作的好方法是什么?下面仅检查输入是否为列表

ClearAll[foo]
foo[a_?(ListQ[#] &)] := Module[{}, a + 1]

我可以做这样的事情:

ClearAll[foo]
foo[a_?(ListQ[#] && (And @@ Map[NumberQ[#] &, # ]) &)] := Module[{}, a + 1]

这样foo[{1, 2, 3}]就可以工作,但foo[{1, 2, x}]不会(假设x是一个符号(。但在我看来,这是一种复杂的方法。

问题:您是否知道一种更好的方法来检查参数是否为列表,并检查列表内容是否为数字(或数学已知的任何其他头?

还有一个相关的问题:向每个参数添加此类检查有什么主要的运行时性能问题吗?如果是这样,您是否建议在测试和开发完成后删除这些检查,以便最终程序运行得更快?(例如,有一个包含所有签入的代码版本,用于开发/测试,而有一个没有用于生产的版本(。

您可以使用VectorQ,其方式与MatrixQ完全相似。 例如

f[vector_ /; VectorQ[vector, NumericQ]] := ...

另请注意VectorQListQ之间的两个区别:

  1. VectorQ(没有第二个参数(仅在列表本身没有元素时才给出 true(即仅适用于 1D 结构(

  2. VectorQ将处理SparseArray,而ListQ不会


不确定在实践中使用这些对性能的影响,我自己对此非常好奇。

这是一个幼稚的基准。 我正在比较两个函数:一个只检查参数,但不做任何事情,另一个添加两个向量(这是一个非常快的内置操作,即任何比这快的东西都可以被认为是可以忽略不计的(。 我正在使用NumericQ,这是一个比NumberQ更复杂的(因此可能更慢(的检查。

In[2]:= add[a_ /; VectorQ[a, NumericQ], b_ /; VectorQ[b, NumericQ]] :=
  a + b
In[3]:= nothing[a_ /; VectorQ[a, NumericQ], 
  b_ /; VectorQ[b, NumericQ]] := Null

打包阵列。 可以验证检查是否恒定时间(此处未显示(。

In[4]:= rr = RandomReal[1, 10000000];
In[5]:= Do[add[rr, rr], {10}]; // Timing
Out[5]= {1.906, Null}
In[6]:= Do[nothing[rr, rr], {10}]; // Timing
Out[6]= {0., Null}

均匀的非包装阵列。 检查是线性时间,但非常快。

In[7]:= rr2 = Developer`FromPackedArray@RandomInteger[10000, 1000000];
In[8]:= Do[add[rr2, rr2], {10}]; // Timing
Out[8]= {1.75, Null}
In[9]:= Do[nothing[rr2, rr2], {10}]; // Timing
Out[9]= {0.204, Null}

均匀非堆积阵列。 检查花费的时间与上一示例中的时间相同。

In[10]:= rr3 = Join[rr2, {Pi, 1.0}];
In[11]:= Do[add[rr3, rr3], {10}]; // Timing
Out[11]= {5.625, Null}
In[12]:= Do[nothing[rr3, rr3], {10}]; // Timing
Out[12]= {0.282, Null}

基于这个非常简单的例子得出的结论:

  1. VectorQ是高度优化的,至少在使用常见的第二个参数时是这样。 它比添加两个向量要快得多,这本身就是一个优化的操作。
  2. 对于打包数组VectorQ是恒定时间。

@Leonid的回答也很中肯,请看。

关于性能影响(因为您的第一个问题已经回答了( - 无论如何,都要进行检查,但在顶级函数中(直接从功能的用户那里接收数据。用户也可以是另一个独立的模块,由您或其他人编写(。不要把这些检查放在所有的中间函数中,因为这样的检查将是重复的,而且实际上是不合理的。

编辑

为了解决@Nasser在评论中提出的中间函数中的错误问题:有一种非常简单的技术,允许人们在"一键"中打开和关闭模式检查。您可以将模式存储在包内的变量中,这些变量在函数定义之前定义。

下面是一个示例,其中f是顶级函数,而gh是"内部函数"。我们定义了两种模式:主函数和内部函数,如下所示:

Clear[nlPatt,innerNLPatt ];
nlPatt= _?(!VectorQ[#,NumericQ]&);
innerNLPatt = nlPatt;

现在,我们定义我们的函数:

ClearAll[f,g,h];
f[vector:nlPatt]:=g[vector]+h[vector];
g[nv:innerNLPatt ]:=nv^2;
h[nv:innerNLPatt ]:=nv^3;

请注意,模式是在定义时而不是运行时在定义中替换的,因此这完全等同于手动对这些模式进行编码。测试后,您只需更改一行:从

innerNLPatt = nlPatt 

innerNLPatt = _

并重新加载您的包裹。

最后一个问题是 - 如何快速发现错误?我在这里回答了这个问题,在"而不是返回$Failed,可以使用 Throw 抛出异常"和"元编程和自动化">部分。

结束编辑

我在书中简要讨论了这个问题。在该示例中,性能影响是运行时间增加 10%,IMO 可以接受。在手头的情况下,检查更简单,性能损失要小得多。通常,对于任何计算密集型函数,正确编写的类型检查仅花费总运行时间的一小部分。

一些技巧是值得了解的:

  • 当语法使用时,模式匹配器可以非常快(模式中不存在ConditionPatternTest(。

例如:

randomString[]:=FromCharacterCode@RandomInteger[{97,122},5];
rstest = Table[randomString[],{1000000}];
In[102]:= MatchQ[rstest,{__String}]//Timing
Out[102]= {0.047,True}
In[103]:= MatchQ[rstest,{__?StringQ}]//Timing
Out[103]= {0.234,True}

仅仅因为在后一种情况下使用了PatternTest,检查速度要慢得多,因为模式匹配器为每个元素调用评估器,而在第一种情况下,一切都是纯粹的语法,一切都在模式匹配器中完成。


  • 解压缩的数字列表也是如此(时序差异相似(。但是,对于打包的数字列表,MatchQ和其他模式测试函数不会为某些特殊模式解包,此外,对于其中一些模式,检查是即时的

下面是一个示例:

In[113]:= 
test = RandomInteger[100000,1000000];
In[114]:= MatchQ[test,{__?IntegerQ}]//Timing
Out[114]= {0.203,True}
In[115]:= MatchQ[test,{__Integer}]//Timing
Out[115]= {0.,True}
In[116]:= Do[MatchQ[test,{__Integer}],{1000}]//Timing
Out[116]= {0.,Null}

显然,对于像VectorQMatrixQNumericQ ArrayQ这样的函数,这些测试非常有效。


  • 很大程度上取决于你如何编写测试,即你在多大程度上重用了高效的 Mathematica 结构。

例如,我们要测试我们是否有一个真实的数字矩阵:

In[143]:= rm = RandomInteger[10000,{1500,1500}];

这是最直接和最慢的方法:

In[144]:= MatrixQ[rm,NumericQ[#]&&Im[#]==0&]//Timing
Out[144]= {4.125,True}

这样更好,因为我们更好地重用模式匹配器:

In[145]:= MatrixQ[rm,NumericQ]&&FreeQ[rm,Complex]//Timing
Out[145]= {0.204,True}

然而,我们没有利用矩阵的包装性质。这仍然更好:

In[146]:= MatrixQ[rm,NumericQ]&&Total[Abs[Flatten[Im[rm]]]]==0//Timing
Out[146]= {0.047,True}

然而,这还不是结束。以下一个几乎是瞬时的:

In[147]:= MatrixQ[rm,NumericQ]&&Re[rm]==rm//Timing
Out[147]= {0.,True}

由于ListQ只检查头部是否List,因此以下是一个简单的解决方案:

foo[a:{___?NumberQ}] := Module[{}, a + 1]

最新更新