Поиск строки в Rebol или Red

Я заинтересован в поиске большого количества длинных строк, чтобы попытаться взломать sed-подобную утилиту в rebol в качестве учебного упражнения. В детстве я решил поискать персонажа:

>> STR: "abcdefghijklmopqrz"

>> pos: index? find STR "z"
== 18

>> pos
== 18

Здорово! Поищем что-нибудь еще ...

>> pos: index? find STR "n"
** Script Error: index? expected series argument of type: series port
** Where: halt-view
** Near: pos: index? find STR "n"

>> pos
== 18

Какие? :-(

Да, в строке, которую я искал, не было "n". Но какая польза от взрыва интерпретатора вместо того, чтобы делать что-то разумное, например, возвращать проверяемый "нулевой" символ в pos?

Мне сказали, что я должен был сделать это:

>> if found? find STR "z" [pos: index? find STR "z"]
== 18

>> if found? find STR "n" [pos: index? find STR "n"]
== none

>> pos
== 18

Действительно? Я должен искать строку ДВАЖДЫ; в первый раз просто для того, чтобы убедиться, что "безопасно" искать СНОВА?

Итак, у меня вопрос из трех частей:

  1. Как мастер реализует мою функцию поиска? Я полагаю, что есть волшебный способ лучше, чем этот ...

  2. Красный собирается это изменить? В идеале я бы подумал, что find должен возвращать действительную позицию строки или NULL, если он попадает в конец строки (с разделителем NULL, могу ли я предположить?). NULL имеет значение FALSE, так что это может быть очень простой тест if.

  3. Каков наиболее эффективный для ЦП способ произвести замену, если у меня есть действительный индекс? Кажется, что в Rebol так много вариантов выбора (это хорошо), что можно застрять в выборе или застрять в неоптимальном выборе.


person gnat    schedule 28.02.2015    source источник


Ответы (3)


Мне сказали, что я должен был сделать это:

>> if found? find STR "z" [pos: index? find STR "z"]
== 18

>> if found? find STR "n" [pos: index? find STR "n"]
== none

>> pos
== 18

Действительно? Я должен искать строку ДВАЖДЫ; в первый раз просто для того, чтобы убедиться, что "безопасно" искать СНОВА?

Вам, конечно, не нужно искать строку дважды. Но index? (вероятное будущее имя, поскольку оно не возвращает да / нет: index-of) не возвращает NONE! значение, если задано NONE! Вход. Предполагается, что вызывающий хочет вернуть целочисленную позицию, и выдает ошибку, если не может ее предоставить.

Как мастер реализует мою функцию поиска?

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

>> all [pos: find STR "z" pos: index? pos]
== 18

>> pos
== 18

>> all [pos: find STR "n" pos: index? pos]
== none

>> pos
== none

Но учтите, что без введения второй переменной вы перезапишете предыдущий pos. Допустим, вы вместо этого вызываете свою переменную index, а pos является временным:

>> all [pos: find STR "z" index: index? pos]
== 18

>> index
== 18

>> all [pos: find STR "n" index: index? pos]
== none

>> index
== 18

Возможность бросать установочные слова в произвольные точки в середине выражения является довольно мощной, и именно поэтому такие вещи, как множественная инициализация (a: b: c: 0), не являются особенностями языка, а являются чем-то, что выпадает из модели оценщика.

Красный собирается это изменить?

Маловероятно, что преимущества index? (кашель index-of), возвращающие НЕТ! значение, если задано NONE! вклад перевешивает проблемы, которые он может вызвать из-за своей терпимости. Это всегда баланс.

Обратите внимание, что FIND действительно ведет себя так, как вы ожидаете. НАШЕЛ? это просто синтаксическое удобство, которое преобразует найденную позицию в истинное значение, а NONE! вернулся в ложный. Это эквивалентно вызову ИСТИНА? (но чуть грамотнее при чтении). Нет необходимости использовать его в условиях IF, UNLESS или EITHER ... поскольку они будут обрабатывать результат NONE, как если бы он был ложным, и любую позицию, как если бы он был истинным.

Каков наиболее эффективный для ЦП способ произвести замену, если у меня есть действительный индекс?

Быстрее всего, вероятно, было бы удержаться на позиции и сказать change pos #"x". (Хотя внутренне «позиции» реализуются последовательностью индекса плюс, а не независимым указателем. Таким образом, преимущество не является таким значительным в мире микрооптимизации, где мы считаем такие вещи, как добавление смещений. ..)

Что касается того, какая операция с индексом: я бы сказал, выберите, как вам больше всего нравится, и выполните микрооптимизацию позже.

Лично я не думаю, что STR/:index: #"x" выглядит так уж хорошо, но по персонажам он самый короткий.

STR/(index): #"x" делает то же самое и выглядит лучше IMO. Но за счёт этого структура исходного кода немного взорвалась. Это УСТАНОВЛЕННЫЙ ПУТЬ! серия, содержащая ПАРЕН! series, за которой следует CHAR! ... все они встроены в исходный "вектор" серии, содержащий код. Под капотом будут проблемы с местностью. И мы знаем, насколько это важно в наши дни ...

Наверное, наивный на вид POKE самый быстрый. poke STR index #"x". Это может выглядеть как «4 элемента вместо 2», но «2 элемента» в вариантах пути - это иллюзия.

В Rebol всегда сложно угадать, поэтому вам нужно собирать данные. Чтобы выяснить это, вы можете запустить несколько повторяющихся итеративных тестов. Чтобы рассчитать время блока кода, см. Встроенную функцию delta-time.

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

person HostileFork says dont trust SE    schedule 28.02.2015

Нет ничего удивительного в том, что ответ HostileFork прекрасно все покрывает! +1

Просто хотел добавить альтернативное решение к пункту 1, которое я регулярно использую:

>> attempt [index? find STR "z"]   
== 18

>> attempt [index? find STR "n"] 
== none

Электронная документация для Rebol 2 attempt и Rebol 3 _ 3_

person draegtun    schedule 28.02.2015
comment
Красиво и лаконично! Но вы заплатите в 3 раза или больше за эту ошибку, сравните delta-time [loop 100000 [all [pos: find STR "n" pos: index? pos]]] с delta-time [loop 100000 [attempt [index? find STR "n"]]]. (Конечно, в большинстве случаев чем короче, тем лучше для того, для чего нужен Ребол ... просто играть в адвоката дьявола ...) - person HostileFork says dont trust SE; 28.02.2015
comment
Я должен пояснить выше, что снижение производительности - это проблема Rebol2, которой был отмечен этот вопрос, и где я проводил тесты для этого ответа. Использование попытки больше не должно рассматриваться как проблема производительности ... скорее проблема семантики, потому что подавление всех ошибок как равное - это очень широкая сеть, на которую можно положиться! - person HostileFork says dont trust SE; 28.11.2015

Искать строки в Red / Rebol очень просто и удобно. Что касается проблем, с которыми вы столкнулись, позвольте мне рассказать вам подробности:

Прежде всего, интерпретатор дает вам хорошую подсказку о том, что вы делаете неправильно, в виде сообщения об ошибке: index? expected series argument of type: series port. Это означает, что вы использовали index? неверный тип данных. Как это произошло? Просто потому, что функция find возвращает значение none в случае неудачного поиска:

>> str: "abcdefghijklmopqrz"
>> find str "o"
== "pqrz"
>> type? find str "o"
== string!

>> find str "n"
== none
>> type? find str "n"
== none!

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

>> all [pos: find str "o" index? pos]
== 14
>> all [pos: find str "n" index? pos]
== none
>> if pos: find str "o" [print index? pos]
== 14
>> print either pos: find str "n" [index? pos][-1]
== -1

Это были лишь примеры безопасных способов достижения этой цели в зависимости от ваших потребностей. Обратите внимание, что none действует как false для условных тестов в if или either, поэтому использование found? в таком случае излишне.

Теперь давайте пролим свет на основную проблему, которая вас смутила.

В языках Rebol есть фундаментальная концепция, называемая series, из которой происходит string! тип данных. Понимание и правильное использование серий - ключевая часть умения использовать языки Rebol идиоматическим образом. Серии выглядят как обычные списки и строковые типы данных на других языках, но они не одинаковы. В серию входят:

  • список значений (для строк это список символов)
  • неявный индекс (для простоты мы можем назвать его курсором)

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

По умолчанию, когда вы создаете новую строку, курсор находится в позиции head:

>> s: "hello"
>> head? s
== true
>> index? s
== 1

Но курсор можно перемещать так, чтобы он указывал на другие места в строке:

>> next s
== "ello"
>> skip s 3
== "lo"
>> length? skip s 3
== 2

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

Кроме того, вы также можете установить курсор для каждой ссылки, указывающей на строку:

>> a: next s
== "ello"
>> b: skip s 3
== "lo"
>> s: at s 5
== "o"
>> reduce [a b s]
== ["ello" "lo" "o"]
>> reduce [index? a index? b index? s]
== [2 4 5]

Как видите, у вас может быть столько разных ссылок на данную строку (или серию), сколько вам нужно, каждая из которых имеет собственное значение cursor, но все они указывают на тот же базовый список значения.

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

Теперь вернемся к вашему варианту использования поиска в строках.

>> STR: "abcdefghijklmopqrz"
>> find STR "z"
== "z"
>> find STR "n"
== none

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

>> pos: find STR "o"
>> if pos [print "found"]
found
>> print ["sub-string from `o`:" pos]
sub-string from `o`: opqrz
>> length? pos
== 5
>> index? pos
== 14
>> back pos
== "mopqrz"
>> skip pos 4
== "z"

>> pos: find STR "n"
>> print either pos ["found"]["not found"]
not found
>> print either pos [index? pos][-1]
-1

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

>> s: "The score is 1:2 after 5 minutes"
>> if pos: find/tail s "score is " [print copy/part pos find pos " "]
1:2

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

А теперь я отвечу на ваши вопросы:

  1. Никакого волшебства не требуется, просто используйте серию и find функцию адекватно, как показано выше.

  2. Красный не собирается этого менять. Серии - это краеугольный камень того, что делает языки Rebol простыми и мощными.

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

person DocKimbel    schedule 14.12.2015