Fast way to type check Symbol in a function with held arguments
One can test if an argument is a Symbol without a explicit value using:
func[s_Symbol] = ...
If the function has a Hold attribute however, that pattern will match all Symbols, not only those without an explicit value. I could use:
func[s_] /; Head[s] === Symbol = ...
but this comes with a greater performance penalty than I would like. Adding a rule for _Symbol
has a fairly small impact on开发者_如何学Python performance, and HoldFirst
does not appear to have any performance penalty, however Head[s] === Symbol
has a significant overhead on a simple function. Tests with ValueQ
and MatchQ[s, _Symbol]
are even slower.
To clarify, I want to have two different definitions for func
, with one for unassigned Symbols, and one for other arguments.
Is there a faster way?
Timings:
f[x_] = 0;
f /@ Range@1*^6; // Timing
f[s_Symbol] = 1;
f /@ Range@1*^6; // Timing
{0.391, Null}
{0.531, Null}
Remove[f]
SetAttributes[f, HoldFirst]
f[x_] = 0;
f /@ Range@1*^6; // Timing
f[s_] /; Head[s] === Symbol = 1;
f /@ Range@1*^6; // Timing
{0.39, Null}
{1.157, Null}
You can get performance comparable to the fastest exhibited running times by delegating held symbol arguments to a non-held helper function g
:
Remove[f, g]
SetAttributes[f, HoldFirst]
f[_] = 0;
f[s_Symbol] := g[s]
g[_Symbol] = 1;
g[_] = 0;
Using pattern s_Symbol
in the code with HoldFirst
attribute will improve the performance:
In[121]:= Remove[f]
SetAttributes[f, HoldFirst]
f[s_Symbol] /; Head[s] === Symbol = 1;
f[_] = 0;
In[125]:= f /@ Range@1*^6; // Timing
Out[125]= {1.217, Null}
In[130]:= Remove[f2]
f2[s_Symbol] = 1;
f2[_] = 0;
In[133]:= f2 /@ Range@1*^6; // Timing
Out[133]= {1.123, Null}
You can get it faster with this:
ClearAll[f];
SetAttributes[f, HoldFirst]
f[x_] = 0;
f[s_Symbol] /; OwnValues[s] =!= {} = 1;
To compare, here is the one you used:
ClearAll[ff];
SetAttributes[ff, HoldFirst]
ff[x_] = 0;
ff[s_] /; Head[s] === Symbol = 1;
Now:
In[30]:= f /@ Range@1*^6; // Timing
Out[30]= {0.719, Null}
In[56]:= ff /@ Range@1*^6; // Timing
Out[56]= {1.25, Null}
This will be more effective when your arguments will mostly be non-symbols, and the reason that it is faster is that you can still use the _Symbol
pattern to filter them out. For lists of symbols only, it may actually be slower:
symbTest = Table[ToExpression["sym" <> ToString[i]], {i, 100000}];
MapIndexed[If[OddQ[First@#2], #1 = First@#2] &, symbTest];
In[54]:= ReleaseHold[Map[f,Hold[symbTest]/.OwnValues[symbTest],{2}]]//Short//Timing
Out[54]= {0.234,{1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,<<99964>>,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0}}
In[58]:= ReleaseHold[Map[ff,Hold[symbTest]/.OwnValues[symbTest],{2}]]//Short//Timing
Out[58]= {0.141,{0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,<<99964>>,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1}}
精彩评论