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

Nas*_*ser 14 wolfram-mathematica

我一直在研究检查函数参数的方法.我注意到 MatrixQ有2个参数,第二个是应用于每个元素的测试.

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

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

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

为List做同样的事情会是什么好方法?以下仅检查输入是否为List

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

我可以这样做:

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

所以这foo[{1, 2, 3}]将起作用,但foo[{1, 2, x}]不会(假设x是一个符号).但在我看来,这是一个复杂的方式来做到这一点.

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

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

Sza*_*lcs 13

您可能会VectorQ以完全类似的方式使用MatrixQ.例如,

f[vector_ /; VectorQ[vector, NumericQ]] := ...
Run Code Online (Sandbox Code Playgroud)

还要注意VectorQ和之间的两个区别ListQ:

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

  2. VectorQ将处理SparseArrays而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
Run Code Online (Sandbox Code Playgroud)

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

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}
Run Code Online (Sandbox Code Playgroud)

同构非打包数组.检查是线性时间,但非常快.

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}
Run Code Online (Sandbox Code Playgroud)

非同类非打包阵列.检查与前一个示例中的时间相同.

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}
Run Code Online (Sandbox Code Playgroud)

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

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

@ Leonid的答案也非常相关,请看.


Leo*_*rin 11

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

编辑

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

这是一个例子,其中f是顶级函数,g而且h是"内部函数".我们定义了两种模式:主要功能和内部功能,如下所示:

Clear[nlPatt,innerNLPatt ];
nlPatt= _?(!VectorQ[#,NumericQ]&);
innerNLPatt = nlPatt;
Run Code Online (Sandbox Code Playgroud)

现在,我们定义我们的功能:

ClearAll[f,g,h];
f[vector:nlPatt]:=g[vector]+h[vector];
g[nv:innerNLPatt ]:=nv^2;
h[nv:innerNLPatt ]:=nv^3;
Run Code Online (Sandbox Code Playgroud)

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

innerNLPatt = nlPatt 
Run Code Online (Sandbox Code Playgroud)

innerNLPatt = _
Run Code Online (Sandbox Code Playgroud)

并重新加载你的包.

最后一个问题是 - 你如何快速找到错误?我回答说,在这里,在部分"而不是返回的$Failed,可以抛出一个异常,使用扔了." ,以及"元编程和自动化".

结束编辑

我在这里的书中简要讨论了这个问题.在该示例中,性能损失是在运行时间增加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}
Run Code Online (Sandbox Code Playgroud)

只是因为在后一种情况下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}
Run Code Online (Sandbox Code Playgroud)

显然,同样的函数似乎也是如此VectorQ,MatrixQ并且ArrayQ某些谓词(NumericQ) - 这些测试非常有效.


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

例如,我们想测试我们有一个真正的数字矩阵:

In[143]:= rm = RandomInteger[10000,{1500,1500}];
Run Code Online (Sandbox Code Playgroud)

这是最直接和最缓慢的方式:

In[144]:= MatrixQ[rm,NumericQ[#]&&Im[#]==0&]//Timing
Out[144]= {4.125,True}
Run Code Online (Sandbox Code Playgroud)

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

In[145]:= MatrixQ[rm,NumericQ]&&FreeQ[rm,Complex]//Timing
Out[145]= {0.204,True}
Run Code Online (Sandbox Code Playgroud)

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

In[146]:= MatrixQ[rm,NumericQ]&&Total[Abs[Flatten[Im[rm]]]]==0//Timing
Out[146]= {0.047,True}
Run Code Online (Sandbox Code Playgroud)

但是,这不是结束.以下是近乎瞬间的:

In[147]:= MatrixQ[rm,NumericQ]&&Re[rm]==rm//Timing
Out[147]= {0.,True}
Run Code Online (Sandbox Code Playgroud)