PatternTest не оптимизирован? - PullRequest
       18

PatternTest не оптимизирован?

8 голосов
/ 13 декабря 2011

При подготовке ответа на Неожиданное поведение PatternTest в Mathematica Я столкнулся с неожиданным Mathematica собственным поведением.

Пожалуйста, примите во внимание:

test = (Print[##]; False) &;
MatchQ[{1, 2, 3, 4, 5}, {x__?test, y__}]
During evaluation of In[1]:= 1

During evaluation of In[1]:= 1

During evaluation of In[1]:= 1

During evaluation of In[1]:= 1
False

Поскольку, как кратко цитирует Саймон документацию,

В такой форме, как __?test каждый элемент в последовательности соответствует __ должен дать True при применении теста.

Мне интересно, почему Mathematica тестирует первый элемент списка четыре раза. Конечно, есть четыре способа создать базовый шаблон {x__, y__}, и если бы это был Condition тест, то все элементы, составляющие последовательность x, должны были бы быть протестированы, но я не думаю, что это дело здесь.

Разве логика не состоит в том, что если первый элемент списка выходит из строя PatternTest, то данный шаблон не может совпадать?

Если это так, почему Mathematica не выполняет эту простую оптимизацию?


Заимствуя пример из ответа Йоды, вот еще один пример того, что представляется избыточной оценкой:

In[1]:= test2 = (Print@##; Positive@##) &;
MatchQ[{1, 2, 3, 4, -5}, {x__?test2, y__?Negative}]

During evaluation of In[1]:= 1

During evaluation of In[1]:= 1

During evaluation of In[1]:= 2

During evaluation of In[1]:= 1

During evaluation of In[1]:= 2

During evaluation of In[1]:= 3

During evaluation of In[1]:= 1

During evaluation of In[1]:= 2

During evaluation of In[1]:= 3

During evaluation of In[1]:= 4

Out[2]= True

Я признаю, что никогда раньше не исследовал этот аспект сопоставления с образцом, и меня беспокоит кажущаяся неэффективность этого. Это действительно так плохо, как кажется, или происходит какое-то автоматическое кэширование? Print указывает на это.

  • Есть ли более эффективный способ на основе шаблона способ написать это?

  • Требуется ли этот уровень избыточности для правильного поведения сопоставления с образцом и почему?


Я поспешно сделал ложное утверждение, но оставляю его, потому что хорошие ответы ниже обращаются к нему. Пожалуйста, игнорируйте это в будущих ответах.

Легко показать, что аналогичная оптимизация проводится в других случаи:

MatchQ[{-1, 2, 3, 4, 5}, {__?Positive, y__?test}]

(Ничего не печатается.)

False

Здесь Mathematica правильно никогда даже не проверяет какие-либо элементы на y.

Ответы [ 4 ]

7 голосов
/ 13 декабря 2011

Я думаю, что все забывают о побочных эффектах, которые возможны в тестовых функциях. Вот что, я думаю, происходит: как упомянули Mr.Wizard и другие, существует несколько способов, которыми шаблон может соответствовать, просто комбинаторно. Для каждой комбинации {x} и {y} сначала проверяется шаблон x. Кстати, нет смысла определять функции нескольких аргументов (##), поскольку, как объяснил @Simon, тестовая функция применяется отдельно к каждому элементу в последовательности. И это также объясняет, почему печатается только первый элемент (-1): как только первый несоответствующий элемент найден, сопоставление с образцом останавливается и продолжается для проверки следующей доступной комбинации.

Вот более наглядный пример:

In[20]:= 
MatchQ[{-1,2,3,4,5},{_,x__?(Function[Print[#];Positive[#]])}]
During evaluation of In[20]:= 2
During evaluation of In[20]:= 3
During evaluation of In[20]:= 4
During evaluation of In[20]:= 5

Out[20]= True

Теперь он печатает все из них, поскольку функция применяется к ним один за другим, как и в этом случае.

Теперь к сути дела. Здесь я настроил тестовую функцию с побочным эффектом, которая решает передумать после того, как проверит самый первый элемент:

Module[{flag = False}, 
  ClearAll[test3]; 
  test3[x_] := 
     With[{fl = flag}, 
        If[! flag, flag = True]; 
        Print[x]; 
        fl
     ]
];

Первая комбинация (которая {-1},{2,3,4,5} будет отклонена, так как функция сначала выдаст False. Однако будет принята вторая ({-1,2},{3,4,5}). И это именно то, что мы наблюдаем:

In[22]:= 
MatchQ[{-1,2,3,4,5},{x__?test3,y__}]
During evaluation of In[22]:= -1
During evaluation of In[22]:= -1
During evaluation of In[22]:= 2

Out[22]= True

Печать остановилась, как только сопоставитель шаблонов обнаружил совпадение.

Теперь, отсюда, должно быть очевидно, что оптимизация, упомянутая в вопросе и некоторых других ответах, в общем случае невозможна, поскольку средство сопоставления с образцом не контролирует изменяемое состояние, возможно присутствующее в функциях тестирования.

Когда мы думаем о сопоставлении с образцом, мы обычно рассматриваем его как процесс, отдельный от оценки, что в значительной степени верно, так как сопоставление с образцом является встроенным компонентом системы, который после оценки шаблонов и выражений принимает и в значительной степени обходит основной цикл оценки. Однако есть заметные исключения, которые делают сопоставление с образцом более мощным за счет запутывания его с оценщиком. Они включают в себя использование Condition и PatternTest, поскольку эти два являются «точками входа» основного процесса оценки в иным образом изолированный от него процесс сопоставления с образцом. Как только шаблонное совпадение достигает одного из них, оно вызывает главного оценщика для условия, подлежащего проверке, и тогда все возможно. Что еще раз подводит меня к наблюдению, что средство сравнения шаблонов наиболее эффективно, когда тесты с использованием PatternTest и Condition отсутствуют, а шаблоны полностью синтаксические - в случае , в случае , он может оптимизироваться.

3 голосов
/ 13 декабря 2011

Я думаю, что ваша предпосылка о том, что Mathematica оптимизирует тестирование по шаблону, неверна. Рассмотрим шаблон {x__, y__}. Как вы правильно сказали, есть 4 способа, которыми {1,2,3,4,5} может вписаться в этот шаблон. Если вы теперь добавите к этому шаблонный тест с помощью ?test, MatchQ должен вернуть True, если любой из 4-х способов, соответствующих шаблону. Поэтому он обязательно должен проверить все возможные варианты , пока совпадение не будет найдено. Таким образом, оптимизация возможна только при наличии положительных результатов, а не при сбое шаблона.

Вот модификация вашего примера с Positive, которая показывает, что Mathematica не выполняет оптимизацию, как вы утверждаете:

test2 = (Print@##; Positive@##) &;
MatchQ[{-1, 2, 3, 4, 5}, {x__?test2, y__}]

Это печатает это 4 раза! Он корректно завершается одним отпечатком, если первый элемент действительно положительный (тем самым не нужно проверять остальные). Тест для второго элемента применяется только , если первый True, поэтому тест для y не был применен в вашем. Вот другой пример:

test3 = (Print@##; Negative@##) &; 
MatchQ[{1, 2, 3, 4, -5}, {x__?test2, y__?test3}]

Я согласен с вашей логикой в ​​том, что если первый сбой, то, учитывая, что каждый элемент должен совпадать, все последующие возможности также потерпят неудачу. Я предполагаю, что Mathematica еще не , что умный, и проще реализовать идентичное поведение для любого из _, __ или ___, а не по-разному для каждого. Реализация другого поведения в зависимости от того, используете ли вы __ или ___, маскирует истинную природу теста и усложняет отладку.

3 голосов
/ 13 декабря 2011

Просто быстрое предположение здесь, но {__?Positive, y__?test} должно соответствовать началу списка до конца.Таким образом, {-1, 2, 3, 4, 5} терпит неудачу на первом элементе, следовательно, не распечатывается.Попробуйте заменить Positive на ((Print[##];Positive[#])&), и вы увидите, что он ведет себя так же, как и первый шаблон.

2 голосов
/ 13 декабря 2011

хорошо, я пойду.

MatchQ[{1, 2, 3, 4, 5}, {x__?test, y__}]//Trace

показывает, что последовательность слотов избыточна, потому что тестируется только один элемент, в данном случае первый в последовательности.например,

MatchQ[{1, 2, 3, 4, 5}, {_, x__?test, y__}] // Trace

Теперь печатает 3 пары.Если вы изменяете первый шаблон на BlankSequence, генерируется больше перестановок, и некоторые из них имеют разные первые элементы

MatchQ[{1, 2, 3, 4, 5}, {__, x__?test, y__}] // Trace

В вашем последнем примере

MatchQ[{-1, 2, 3, 4, 5}, {__?Positive, y__?test}]

первый элемент всегда не проходит тест.Если вы измените это значение на

MatchQ[{1, 2, -3, 4, 5}, {__?Positive, y__?test}]

, вы увидите нечто большее, чем ожидалось.

...