Каков рекомендуемый способ проверить, что список - это список чисел в аргументе функции?

Я рассматривал способы проверки аргументов функций. Я заметил, что MatrixQ принимает 2 аргумента, второй - тест для применения к каждому элементу.

Но ListQ принимает только один аргумент. (также по какой-то причине ?ListQ не имеет справочной страницы, например ?MatrixQ).

Итак, например, чтобы проверить, что аргумент функции является матрицей чисел, я пишу

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

Что было бы хорошим способом сделать то же самое для List? Это ниже только проверяет, что вход представляет собой List

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 является символом). Но мне кажется, что это сложный способ сделать это.

Вопрос: Знаете ли вы, что лучший способ проверить, что аргумент является списком, а также проверить содержимое списка как Numbers (или любой другой главы, известной Mathematica?)

И связанный с этим вопрос: Какие-либо серьезные проблемы производительности во время выполнения с добавлением таких проверок для каждого аргумента? Если да, рекомендуете ли вы, чтобы эти проверки были удалены после завершения тестирования и разработки, чтобы конечная программа выполнялась быстрее? (например, иметь версию кода со всеми проверками, для разработки/тестирования и версии без производства).

Ответы

Ответ 1

Что касается производительности (с момента ответа на ваш первый вопрос) - всеми способами выполняйте проверки, но в своих функциях верхнего уровня (которые получают данные непосредственно от пользователя вашей функциональности. Пользователь также может быть другой независимый модуль, написанный вами или кем-то еще). Не ставьте эти проверки во все ваши промежуточные функции, так как такие проверки будут повторяться и действительно необоснованны.

ИЗМЕНИТЬ

Чтобы устранить проблему ошибок в промежуточных функциях, поднятых @Nasser в комментариях: существует очень простая методика, позволяющая включать и выключать проверки шаблонов одним щелчком мыши. Вы можете хранить ваши шаблоны в переменных внутри вашего пакета, определенных перед определениями ваших функций.

Вот пример, где f - функция верхнего уровня, а g и h - "внутренние функции". Мы определяем два шаблона: для основной и для внутренних, так:

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 

to

innerNLPatt = _

и перезагрузите пакет.

Последний вопрос: как вы быстро находите ошибки? Я ответил, что здесь, в разделах "Вместо возврата $Failed можно сделать исключение, используя Throw." И "Meta-programming and automation".

END EDIT

Я включил краткое обсуждение этой проблемы в моей книге здесь. В этом примере показатель производительности был на уровне 10% увеличения времени выполнения, что ИМО является приемлемым на границе. В данном случае проверка проще, а штраф за производительность намного меньше. Как правило, для функции, которая является любой вычислительно-интенсивной, корректно написанные проверки типов стоят лишь малую долю от общего времени выполнения.

Несколько трюков, которые хорошо знать:

  • Шаблон-сопряжение может быть очень быстрым, когда синтаксически используется (нет Condition или PatternTest).

Например:

randomString[]:[email protected][{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}

То же самое, по-видимому, справедливо для таких функций, как VectorQ, MatrixQ и ArrayQ с определенными предикатами (NumericQ) - эти тесты чрезвычайно эффективны.


  • Многое зависит от того, как вы пишете тест, то есть в какой степени вы повторно используете эффективные структуры 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}

Ответ 2

Вы можете использовать VectorQ таким образом, который полностью аналогичен MatrixQ. Например,

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

Также обратите внимание на два различия между VectorQ и ListQ:

  • Простой VectorQ (без второго аргумента) дает только true, если ни один элемент списка не является списком (т.е. только для 1D структур)

  • 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`[email protected][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}

Заключение на основе этого очень простого примера:

  • VectorQ сильно оптимизирован, по крайней мере, при использовании общих вторых аргументов. Это намного быстрее, чем, например, добавляя два вектора, который сам является хорошо оптимизированной операцией.
  • Для упакованных массивов VectorQ используется постоянное время.

@Leonid answer тоже очень уместен, см. его.

Ответ 3

Так как ListQ просто проверяет, что голова List, следующее простое решение:

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