Каков рекомендуемый способ проверить, является ли список списком чисел в аргументе функции? - PullRequest
14 голосов
/ 09 января 2012

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

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

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

Ответы [ 3 ]

13 голосов
/ 09 января 2012

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

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

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

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

  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 - это постоянное время.

@ ответ Леонида тоже очень актуально, смотрите.

11 голосов
/ 09 января 2012

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

EDIT

Для решения проблемы ошибок в промежуточных функциях, поднятой @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 

до

innerNLPatt = _

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

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

END EDIT

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

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

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

Например:

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}

То же самое, по-видимому, справедливо для таких функций, как 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}
3 голосов
/ 09 января 2012

Поскольку ListQ просто проверяет, что напор равен List, следующее решение является простым:

foo[a:{___?NumberQ}] := Module[{}, a + 1]
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...