Pull to refresh
30
0
Кирилл Белов @KirillBelovTest

Инженер по автоматизации тестирования

Send message

Добрый день! Вы использовали Wolfram Engine?

Спасибо большое за оценку моих трудов =)
Кстати мой любимый книжный пример - это сортировка пузырьком при помощи шаблонов:

{5, 4, 2, 6, 3, 2} //. 
   {fst___, x_Integer, y_Integer, rst___} :> 
     {fst, y, x, rst} /; x > y
  • //. - сокращение для ReplaceRepeated - функция которая выполняет повторяющуюся замену

  • :> - правило замены

  • /; - дополнительное условие для замены, но не обязательное

В итоге этот код на первом шаге делает следующее. Берет весь список и сравнивает его с образцом:

MatchQ[{5, 4, 2, 6, 3, 2}, {fst___, x_Integer, y_Integer, rst___}] /; x > y

связываются fst = <ничего>; x = 5; y = 4; rst = 2, 6, 3, 2. Оказывается что 5 > 4, тогда условие выполнено и на место исходного списка вставляется:

{fst = <ничего>, y = 4, x = 5, rst = 2, 6, 3, 2} == {4, 5, 3, 6, 3, 2}

Далее этот процесс повторяется, но теперь первый и второй элемент не проходят условие и происходит новое связывание: fst = 4; x = 5; y = 2; rst = 6, 3, 2 и т.д. пока не окажется так, что заменять ничего.

Да, конечно. Это такой синтаксис шаблонов/паттернов/образцов в WL. Во многих языках программирования, в которых есть паттерн-матчинг есть такая штука как wildcard. То есть "_". Так вот в WL шаблоны могут состоять из разных вариаций wildcard. Вот несколько примеров. Сравнение происходит при помощи функции MatchQ[expression, pattern]:

MatchQ[x, _] == True (*одиночное подчеркивание - все что угодно*)
MatchQ[f[x], f[_]] == True (*это значит выражение слева это f с любым аргументом*)
MatchQ[f[x, y], f[_]] == False
MatchQ[f[], f[_]] == False

MatchQ[f[1, 2, 3, 4], f[__]] == True (*два подчеркивания от одного до бесконечности элементов*)
MatchQ[f[], f[__]] == False

MatchQ[f[], f[___]] == True
MatchQ[f[x], f[___]] == True
MatchQ[f[x, y, z], f[___]] == True (*три подчеркивания любое число элементов от 0 до беконечности*)

Кроме трех вариантов wildcard есть еще возможность указывать диапазон количества элементов или конкретное значение, но это я оставлю на потом. И так мы рассмотрели возможность матчить произвольное выражение с шаблоном, который соответствует одному "чему угодно", одному и более или нулю и более штук "чего угодно". Но что если я хочу чтобы это было не что угодно, а только выражения/объекты с определенными типами? Тогда после подчеркивания я могу напрямую указать этот тип. Как понять какой тип у выражения в языке с динамической типизацией? При помощи функции Head. Это значит что

Head[1] == Integer
Head[1.0] == Real
Head[1/3] == Rational
Head[1 + I] == Complex
Head["string"] == String
Head[x] == Symbol
Head[f[x]] == f
Head[f[x][y][z]] == f[x][y]
Head[Head[f[x][y][z]]] == f[x]

И я могу взять любой результат, который возвращает Head и вставить его сразу после одного/двух/трех символов подчеркивания. То есть вот так:

MatchQ[1, _Integer] == True
MatchQ[1.5, _Integer] == False

То есть это wildcard с проверкой типов, но не все так просто.

Вот например как можно проверить что у нас массив только целых:

MatchQ[{1, 2, 3, 4}, {__Integer}] == True

А вот так я могу проверить, что у меня есть массив где сначала идут целы числа, потом рациональные, а потом комплексные. При этом комплексных может не быть совсем

MatchQ[{1, 2, 3, 1/3, 1/5, 1/7, I, I/3}, {__Integer, __Ratonal, ___Complex}] == True
MatchQ[{1, 2, 1/5}, {__Integer, __Ratonal, ___Complex}] == True

А еще wildcard можно связать с именем вот так:

MatchQ[f[1], f[x_]] == True

В MatchQ это бесполезно, но в функции, которая вытаскивает что-то по шаблону - это полезно:

Cases[{f[1]}, f[x_] :> x] (* => {1} *)

Т.е. выше я связал значение 1 из выражения с символом x и смог извлечь это значение. Если использовать несколько подчеркиваний, то будет вот так:

Cases[{f[1, 2, 4]}, f[x__] :> {x}] (* => {{1, 2, 4}}*)

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

f[x_] := x + 1

Я говорю математике о том, что как только она встречает выражение, которое матчится с:

MatchQ[f[1], f[x_]] == True

То сразу же происходит замена на правую часть определения функции - т.е. на x + 1. Определив так функция - я отсекаю все шаблоны, которые не матчатся с шаблоном f[x_]. Вызвав функцию на том, что не матчится ни с одним из шаблонов, которые были определены - ядро WL вернет результат ввода как есть. Т.е. например:

f[x_, y_] := x + y

f[1, 2, 3] (* => f[1, 2, 3] без изменений*)

Ну и возвращаясь к примеру из моего комментария выше шаблон

f[arr: {a1_Integer, ___Integer}, x_Integer] /; x < a1 := ...

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

  1. Первый аргумент список, где первый элемент списка обязательно целое число, а дальше может быть от нуля до бесконечности только целы чисел. Но я не стал писать a__Integer, чтобы связать ТОЛЬКО первый элемент списка с переменной a1, а не всю последовательность.

  2. Второй аргумент - просто целое число.

  3. После знака /; идет условие, которое может использовать связанные переменные

Если у вас возникнут вопросы после моего объяснения или я объяснил слишком запутанно - то напишите пожалуйста, я готов ответить на любые вопросы!

В общем хоть меня и удивляет популярность такой достаточно простой задачи, но я сначала дошел до условия, решил задачку, а потом продолжил читать. Не думал, что мое наивное решение и будет тем самым, которое ожидает автор статьи. Но хочу показать свое решение в коментах, так как оно оригинально тем, что сделано на WL:

f[arr : {a1_Integer, ___Integer}, x_Integer, default_Integer : -1] /; x < a1 := default

f[arr : {___Integer, an_Integer}, x_Integer, default_Integer : -1] /; x >= an := an

f[arr : {__Integer}, x_Integer, default_Integer : -1] := 
With[{halfLen = Round[Length[arr]/2]}, 
  If[arr[[halfLen]] < x, 
    f[arr[[halfLen + 1 ;; ]], x, arr[[halfLen]]], 
    f[arr[[ ;; halfLen]], x, default]
  ]
]

f::argex = "Argument exception"; 

f[args___] := (Message[f::argex]; Defer[f[args]])

На скриншоте код и результаты тестов. Результаты не уместились, но тестов больше чем в самой статье, т.е. они те же самые, а еще включают то, что не было учтено.

Я когда начинал читать - то ожидал не два уровня, а побольше. Что на первом решают в лоб за один проход. На втором - двоичным поиском, а на третьем какая-нибудь магия вроде кода Грея или еще какой-то малоизвестной штуки. Увы, ожидания не оправдались.

Я вернулся. Честно сказать не знаю, что такое DSP-процессор, хоть и нашел расшифровку. НО! Я понимаю, что скомпилированный вариант всегда будет быстрее и лучше работать для обработки сигнала в реальном времени, но в теории на WL можно сделать такое же приложение. Я не дам прямо сейчас конкретный пример, но вы можете:

  • Использовать stream = AudioStream[] чтобы получить текущий поток с устройства ввода - например с микрофона

  • Получить его свойства:

    • Текущий кусок stream["CurrentAudio"]

    • Изменить размер буфера stream["BufferSize"] = n

    • Получить текущую позицию stream["Position"]

    • Если вы читаете из файла, то позицию в потоке можно изменять

    • Аудио поток можно записывать.

    • Потому записанный обработать.

  • Объект Audio ведет себя примерно как обычный массив точек, который представляет собой сигнал. Его можно легко изменить применив любую математическую функцию. Сигналы можно делить и объединять.

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

Я вообще думаю вы слишком мягко прошлись по этим алгоритмам. На мой взгляд в принципе все программисты не важны! Зачем они вообще? Чем занимаются? Только плодят баги и доставляют нам трудности! Без них жизнь была бы проще и собеседования легче проходить. Это я вам как QA говорю, мне можно верить.

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

Второй порог входа как раз выше чем в среднем, так как там очень мало руководств и примеров действительно больших проектов и того, как эти большие проекты делать правильно. Даже если посмотреть код самой математики или посмотреть репозитории WRI - там в основном просто жуткая мешанина и говнокодище. Всякий раз когда я лезу смотреть исходники - я просто в ужасе. Так что неудивительно, что те кто говнокодит сам не делает хорошие руководства для разработки крупных проектов.

Я правильно понимаю, что в C# есть оператор деления и для его переопределения вы написали класс с тремя методами. Более читабельно там только то, что в сигнатуре написано “operator /“. Плюс это конкретный класс, который может использовать только конкретные типы, а если делать обобщенный тип, то использовать его будет еще чуть-чуть сложнее. И последнее - вы не написали переопределение для “+=“, а ведь это еще один метод.

Для того же самого в WL я сделал две функциии, которые одновременно изменили поведение AddTo/Power/Times. В принципе это можно было бы и в одном определении сделать. Но я сделал так, чтобы при применении к выражению возвращалось новое выражение, а при применении к символу - изменялся сам символ. И все это с учетом того, что оператора или функции деления в WL нет.

Далее наоборот. Я предложил переопределить оператор «которого нет» в C#, вы сказали это невозможно.

Так в итоге в чью пользу складывается «зачет» по «гибкости синтаксиса»?

В общем я понимаю всю непродуктивность этого спора, да я и не спорю - просто продолжаю дискуссию. Вы спросили как перегрузить деление, а я показал. Вам этот способ показался не таким красивым вот и все. На мой взгляд там все довольно хорошо получилось, хотя опять же я понимаю, что далеко не каждый пользователь WL способен сам до такого додуматься - ну например до того, чтобы делать вложенный UpSet во время присваивания. И вот эта сложность может отпугивать.

И на счет символа деления. Не знаю ни одного популярного языка программирования, который использует двоеточие в качестве оператора деления или умеет использовать горизонтальную черту. Горизонтальную черту только математические пакеты используют в лучшем случае.

В общем в очередной раз спасибо вам за обсуждение. Мне с вами очень интересно беседовать. Если можете - поделитесь пожалуйста ссылкой на свои проекты на GitHub или еще где-нибудь, которые связаны с WL. Я всегда в поисках интересного кода и с большим интересом изучаю другие проекты.

Я все таки за то, чтобы была точность в формулировках. Когда вы говорите «хочу переопределять одно, а переопределяли другое» это неверно. Этого «одного» вообще нет в языке. В WL нет операторов и в том числе нет оператора деления. Это такой синтаксический Сахар для записи математических выражений. Если бы вы хотели переопределять то, что существует в том виде в каком записывается - это делает намного проще чем я показал и чем это сделано в c#. Например Plus или Times

Но ведь можно предложить и обратную ситуацию. Как в C# переопределять «//.» Или «/*» или “@@@“или нижний индекс?

Автоматическое в каком смысле? Как я сказал выше - для StringExpression или для RegExp нужно сначала создать правила преобразования и тогда можно будет все делать автоматически. По умолчанию аналогов Factor или Expand для StringExpression нет

Я совсем забыл, что есть DatePattern

Не спорю с полезностью регулярных выражений, но вы слишком конкретно подходите к задаче из примера. Я пытался показать возможность манипулировать самим шаблоном, а не демонстрировал краткость и производительность. И у того и у другого подхода есть свои плюсы и минусы. Давайте с другой стороны зайдем. Допустим в регулярных выражениях есть заранее подготовленные шаблоны для дат. Но вот теперь мне пришла в голову цель выбрать из списка номеров телефонов только те, которые заканчиваются на любое простое число из последовательности Фибоначчи. Какая быстрая и короткая запись для этого шаблона?

Думаю это дело привычки. Довольно часто строковые выражения длиннее, т.к. в WL принято встроенные функции называть полными словами на английском, но для меня на данный момент намного читабельнее, плюс еще раз хочу отметить, что манипулировать ими можно как обычными выражениями без парсинга строк. Ну и я специально долго не думал, а побольше накидал. Можете быстро написать RegEx, который проверяет месяц от 1 до 12? Самый простой вариант на WL будет

month = ToString /@ Range[12]

В принципе я вас понял. Это вполне можно реализовать, только прежде всего я должен уточнить, что для StringExpression не существует встроенных правил упрощения. Какие вы сами придумаете правила - такие и будут работать. Еще это на мой взгляд довольно нетривиальная задача, но вполне решаемая. Смотрите, в строковых выражениях альтернатива - это список выражений, т.е. записывается вот так:

StringMatchQ["123", {"123", "456"}] === True
StringMatchQ["456", {"123", "456"}] === True

А конкатенация записывается собственно вот так:

StringMatchQ["1.2", DigitCharacter ~~ "." ~~ DigitCharacter] === True
StringMatchQ["2.1", DigitCharacter ~~ "." ~~ DigitCharacter] === True

Тогда можно создать функцию, которая "выносит" за скобочки общий "множитель". Назовем ее sexFactor, по аналогии со встроенной функций Factor, но с указанием того, что она специфична для StringEXpression:

ClearAll[lstseq]
ClearAll[sexFactor]

lstseq[l_List] := Sequence @@ l
lstseq[e_] := e
lstseq[e__] := StringExpression[e]

sexFactor[{e1___, Longest[x__] ~~ e2__, e3___, Longest[x__] ~~ e4__, e5___}] := 
 sexFactor[{x ~~ {lstseq@e2, lstseq@e4}, e1, e3, e5}]

sexFactor[{e1___, e2__ ~~ Longest[x__], e3___, e4__ ~~ Longest[x__], e5___}] := 
 sexFactor[{{lstseq@e2, lstseq@e4} ~~ x, e1, e3, e5}]

sexFactor[s : {__}] := s

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

date = {
  year ~~ "." ~~ month ~~ "." ~~ day, 
  year ~~ "." ~~ month, 
  year
}

И попробуем применить вынесение за скобки:

sexFactor[date]

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

day = {{"1", "2"} ~~ DigitCharacter, Except["0", DigitCharacter], "30", "31"};
month = {{Except["0", DigitCharacter], ""} ~~ Except["0", DigitCharacter], "10"};
year = {"", "19", "20"} ~~ Repeated[DigitCharacter, 2];

Они конечно переусложнены, зато имеют больше ограничений ну и показывают дополнительные примеры использования StringExpression. Собственно на этом все. Теперь мы можем проверять строки на соответствие датам:

Кстати подобным образом можно сделать не только вынесение за скобки множителя, но раскрытие скобок - нужно только придумать дополнительные правила.

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

Ого! Вот это да! Неужели на гифке происходит real time calculation, т.е. в каждый момент времени после обновления кадра браузер получает новые данные для отрисовки и все изменения из UI пересылаются так же? Но ведь мне выше говорили, что WL медленный!!! ;-)

Можно сделать всё то же самое, что здесь, и даже больше, да ещё и быстрее.

А что больше-то? Можете пожалуйста сказать, чего такого нет в WL, но есть в Julia?
Ну то есть простой пример на Julia, а я постараюсь ответить - есть ли подобное в WL.

Ну вообще все это можно делать, только я пока не могу придумать задачу на которой это можно показать. StringExpression точно такое же выражение как и все остальное. В WL есть регулярные выражения, но они классические и представляются в виде строки, поэтому как с символами с ними не поработаешь. У вас есть какой-нибудь пример чтобы я мог показать как можно изменять сами строковые выражения?

На самом деле я использовал слово "парадигма" чтобы избежать тавтологии, но на Хабре любят обсуждать парадигмы и подходы, так как это такая тема, которая не имеет четкого определения. Если бы я писал более точно, то это было бы во-первых скучно, во-вторых здесь в комментариях не собралось бы столько замечательных людей. Вы правы на счет того, что в один подход невозможно втиснуть абсолютно все. Но этого и нет. Я пытался рассказать только о том, почему WL называют символьным языком программирования. Но это не исключает всего остального. Язык так же называют процедурным и функциональным. А еще на нем можно сделать ООП.

Про последнюю часть вашего комментария. Примерно так я и поступаю на самом деле со своими пет-проектами. Мне нравится делать то, чего в языке еще нет, но очень хочется. Часто я решаю это в виде API стороннему сервису, или в виде использования скомпилированной библиотеки на другом языке. В первую очередь я всегда решаю практическую задачу, но мои предпочтения на стороне WL благодаря его дизайну. Например, я сделал клиент для WebSocket протокола. Им можно пользоваться как в других языках, но с особенностями WL. Вот только конкретно в этой библиотеке я не делал сам протокол, а взял готовую реализацию на Java.

Information

Rating
Does not participate
Location
Саратов, Саратовская обл., Россия
Registered
Activity