Spec-Zone.ru › Julia 1.4

Строки

Строки — это конечные последовательности символов. Конечно, настоящая проблема возникает, когда задаётся вопрос о том, что такое символ. Символы, с которыми знакомы говорящие по-английски, — это буквы A, B, C и т. д., вместе с цифрами и знаками препинания. Эти символы стандартизированы вместе с сопоставлением целочисленным значениям от 0 до 127 стандартом ASCII. Конечно, существуют и многие другие символы, используемые в языках, отличных от английского, включая варианты символов ASCII с акцентами и другими модификациями, родственные системы письма, такие как кириллица и греческий алфавит, и системы письма, совершенно не связанные с ASCII и английским, включая арабский, китайский, иврит, хинди, японский и корейский. Стандарт Unicode решает сложности того, что именно представляет собой символ, и в целом принимается в качестве окончательного стандарта, решающего эту проблему. В зависимости от ваших потребностей, вы можете либо полностью игнорировать эти сложности и просто предположить, что существуют только символы ASCII, либо написать код, который может обрабатывать любые символы или кодировки, с которыми вы можете столкнуться при работе с текстом, не являющимся ASCII. Julia упрощает и эффективно обрабатывает простой текст ASCII, а обработка Unicode — максимально проста и эффективна. В частности, вы можете написать код строк в стиле C для обработки строк ASCII, и он будет работать как ожидается, как по производительности, так и по семантике. Если такой код столкнётся с текстом, не являющимся ASCII, он корректно завершится с ясным сообщением об ошибке, а не будет молча создавать искажённые результаты. В этом случае изменение кода для обработки данных, не являющихся ASCII, является простым.

Есть несколько примечательных высокоуровневых функций строк Julia:

  • Встроенный конкретный тип, используемый для строк (и строковых литералов) в Julia, — это String. Он поддерживает весь диапазон символов Unicode с помощью кодировки UTF-8. (Функция transcode предоставлена для преобразования в другие кодировки Unicode.)
  • Все типы строк являются подтипами абстрактного типа AbstractString, и внешние пакеты определяют дополнительные AbstractString подтипы (например, для других кодировок). Если вы определяете функцию, ожидавшую аргумент строки, вы должны объявить тип как AbstractString, чтобы принять любой тип строки.
  • Как в C и Java, но в отличие от большинства динамических языков, Julia имеет тип первого класса для представления отдельного символа, называемого AbstractChar. Встроенный подтип Char типа AbstractChar — это 32-битный примитивный тип, который может представлять любой символ Unicode (и основан на кодировке UTF-8).
  • Как и в Java, строки неизменяемы: значение объекта AbstractString изменить нельзя. Для построения другого значения строки нужно создать новую строку из частей других строк.
  • Понятийно, строка — это частичная функция от индексов к символам: для некоторых значений индексов не возвращается значение символа, а вместо этого выбрасывается исключение. Это позволяет эффективно индексировать строки по байтовому индексу закодированного представления, а не по индексу символа, что нельзя реализовать одновременно эффективно и просто для кодировок переменной длины строк Unicode.

Символы

Значение Char представляет один символ: это просто 32-битный примитивный тип со специальным представлением литерала и соответствующим поведением арифметических операций, который может быть преобразован в числовое значение, представляющее код Unicode. (Пакеты Julia могут определять другие подтипы AbstractChar, например, для оптимизации операций для других кодировок текста.) Вот как вводятся и отображаются значения Char:

julia> 'x'
'x': ASCII/Unicode U+0078 (category Ll: Letter, lowercase)

julia> typeof(ans)
Char

Вы легко можете преобразовать Char в его целочисленное значение, т. е. код:

julia> Int('x')
120

julia> typeof(ans)
Int64

На 32-битных архитектурах typeof(ans) будет Int32. Вы можете преобразовать целочисленное значение обратно в Char также легко:

julia> Char(120)
'x': ASCII/Unicode U+0078 (category Ll: Letter, lowercase)

Не все целочисленные значения являются допустимыми кодами Unicode, но для производительности преобразование Char не проверяет, что каждое значение символа является допустимым. Если вы хотите проверить, что каждое преобразованное значение является допустимым кодом, используйте функцию isvalid:

julia> Char(0x110000)
'\U110000': Unicode U+110000 (category In: Invalid, too high)

julia> isvalid(Char, 0x110000)
false

На момент написания этого документа допустимые коды Unicode — это U+0000 по U+D7FF и U+E000 по U+10FFFF. Всем этим значениям ещё не присвоены осмысленные значения, и они необязательно интерпретируются приложениями, но все эти значения считаются допустимыми символами Unicode.

Вы можете ввести любой символ Unicode в одинарных кавычках, используя \u, за которым следуют до четырёх шестнадцатеричных цифр, или \U, за которым следуют до восьми шестнадцатеричных цифр (наиболее длинное допустимое значение требует только шести):

julia> '\u0'
'\0': ASCII/Unicode U+0000 (category Cc: Other, control)

julia> '\u78'
'x': ASCII/Unicode U+0078 (category Ll: Letter, lowercase)

julia> '\u2200'
'∀': Unicode U+2200 (category Sm: Symbol, math)

julia> '\U10ffff'
'\U10ffff': Unicode U+10FFFF (category Cn: Other, not assigned)

Julia использует настройки региональных параметров и языка вашей системы, чтобы определить, какие символы могут быть напечатаны как есть, а какие должны выводиться с помощью универсальных, экранированных \u или \U форм ввода. Кроме этих форм экранирования Unicode, могут быть использованы все традиционные формы экранирования ввода C:

julia> Int('\0')
0

julia> Int('\t')
9

julia> Int('\n')
10

julia> Int('\e')
27

julia> Int('\x7f')
127

julia> Int('\177')
127

Вы можете выполнять сравнения и ограниченное количество арифметических операций со значениями Char:

julia> 'A' < 'a'
true

julia> 'A' <= 'a' <= 'Z'
false

julia> 'A' <= 'X' <= 'Z'
true

julia> 'x' - 'a'
23

julia> 'A' + 1
'B': ASCII/Unicode U+0042 (category Lu: Letter, uppercase)

Основы строк

Строковые литералы ограничены двойными кавычками или тройными двойными кавычками:

julia> str = "Hello, world.\n"
"Hello, world.\n"

julia> """Contains "quote" characters"""
"Contains \"quote\" characters"

Если вы хотите извлечь символ из строки, индексируйте её:

julia> str[begin]
'H': ASCII/Unicode U+0048 (category Lu: Letter, uppercase)

julia> str[1]
'H': ASCII/Unicode U+0048 (category Lu: Letter, uppercase)

julia> str[6]
',': ASCII/Unicode U+002C (category Po: Punctuation, other)

julia> str[end]
'\n': ASCII/Unicode U+000A (category Cc: Other, control)

Многие объекты Julia, включая строки, могут индексироваться целыми числами. Индекс первого элемента (первого символа строки) возвращается функцией firstindex(str), а индекс последнего элемента (символа) — функцией lastindex(str). Ключевые слова begin и end могут использоваться в операциях индексирования в качестве сокращений для первого и последнего индексов соответственно по заданному измерению. Индексирование строк, как и большинство индексирования в Julia, основано на 1-й позиции: firstindex всегда возвращает 1 для любого AbstractString. Однако, как мы увидим ниже, lastindex(str), вообще говоря, не равно length(str) для строки, потому что некоторые символы Unicode могут занимать несколько «единиц кода».

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

julia> str[end-1]
'.': ASCII/Unicode U+002E (category Po: Punctuation, other)

julia> str[end÷2]
' ': ASCII/Unicode U+0020 (category Zs: Separator, space)

Использование индекса меньше begin (1) или больше end вызывает ошибку:

julia> str[begin-1]
ERROR: BoundsError: attempt to access String
  at index [0]
[...]

julia> str[end+1]
ERROR: BoundsError: attempt to access String
  at index [15]
[...]

Вы также можете извлечь подстроку, используя индексирование диапазонами:

julia> str[4:9]
"lo, wo"

Обратите внимание, что выражения str[k] и str[k:k] не дают одинакового результата:

julia> str[6]
',': ASCII/Unicode U+002C (category Po: Punctuation, other)

julia> str[6:6]
","

Первый — это значение одного символа типа Char, а второй — строковое значение, которое, случается, содержит только один символ. В Julia это очень разные вещи.

Индексирование диапазонами создаёт копию выделенной части исходной строки. В качестве альтернативы, можно создать представление в строке, используя тип SubString, например:

julia> str = "long string"
"long string"

julia> substr = SubString(str, 1, 4)
"long"

julia> typeof(substr)
SubString{String}

Несколько стандартных функций, таких как chop, chomp или strip, возвращают SubString.

Unicode и UTF-8

Julia полностью поддерживает символы и строки Unicode. Как обсуждалось выше, в литералах символов коды Unicode могут быть представлены с помощью Unicode \u и \U escape-последовательностями, а также всеми стандартными C escape-последовательностями. Аналогично, они могут быть использованы для написания строковых литералов:

julia> s = "\u2200 x \u2203 y"
"∀ x ∃ y"

Отображение этих символов Unicode в виде экранирования или как особых символов зависит от настроек региональных параметров вашего терминала и его поддержки Unicode. Строковые литералы кодируются с помощью кодировки UTF-8. UTF-8 — это кодировка переменной длины, что означает, что не все символы кодируются одинаковым количеством байтов («единиц кода»). В UTF-8 символы ASCII, т. е. те, у которых коды меньше 0x80 (128), кодируются так же, как и в ASCII, с использованием одного байта, а коды 0x80 и выше кодируются с использованием нескольких байтов — до четырёх на символ.

Индексы строк в Julia относятся к единицам кода (= байтам для UTF-8), фиксированным единицам, используемым для кодирования произвольных символов (кодов). Это означает, что не каждый индекс в String обязательно является допустимым индексом для символа. Если вы индексируете строку с недопустимым байтовым индексом, выбрасывается ошибка:

julia> s[1]
'∀': Unicode U+2200 (category Sm: Symbol, math)

julia> s[2]
ERROR: StringIndexError("∀ x ∃ y", 2)
[...]

julia> s[3]
ERROR: StringIndexError("∀ x ∃ y", 3)
Stacktrace:
[...]

julia> s[4]
' ': ASCII/Unicode U+0020 (category Zs: Separator, space)

В данном случае символ ∀ — это трёхбайтовый символ, поэтому индексы 2 и 3 недопустимы, а индекс следующего символа — 4; этот следующий допустимый индекс может быть вычислен с помощью nextind(s,1), а следующий после него — nextind(s,4) и так далее.

Так как end всегда является последним допустимым индексом в коллекции, end-1 ссылается на недопустимый байтовый индекс, если предпоследний символ является многобайтовым.

julia> s[end-1]
' ': ASCII/Unicode U+0020 (category Zs: Separator, space)

julia> s[end-2]
ERROR: StringIndexError("∀ x ∃ y", 9)
Stacktrace:
[...]

julia> s[prevind(s, end, 2)]
'∃': Unicode U+2203 (category Sm: Symbol, math)

Первый случай работает, потому что последний символ y и пробел являются однобайтовыми символами, тогда как end-2 индексирует середину ∃ многобайтового представления. Правильный способ в этом случае — использовать prevind(s, lastindex(s), 2) или, если вы используете это значение для индексирования s, вы можете написать s[prevind(s, end, 2)], и end расширяется до lastindex(s).

Извлечение подстроки с помощью индексирования диапазонами также ожидает допустимые байтовые индексы или же выбрасывается ошибка:

julia> s[1:1]
"∀"

julia> s[1:2]
ERROR: StringIndexError("∀ x ∃ y", 2)
Stacktrace:
[...]

julia> s[1:4]
"∀ "

Из-за кодировок переменной длины количество символов в строке (указанное функцией length(s)) не всегда совпадает с последним индексом. Если вы итерируетесь по индексам от 1 до lastindex(s) и обращается к s, то последовательность символов, возвращаемая без исключений, — это последовательность символов, составляющих строку s. Таким образом, у нас есть тождество length(s) <= lastindex(s), так как каждый символ в строке должен иметь свой собственный индекс. Следующее — неэффективный и громоздкий способ итерирования по символам строки s:

julia> for i = firstindex(s):lastindex(s)
           try
               println(s[i])
           catch
               # ignore the index error
           end
       end
∀

x

∃

y

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

julia> for c in s
           println(c)
       end
∀

x

∃

y

Если вам нужно получить допустимые индексы для строки, вы можете использовать функции nextind и prevind для инкремента/декремента до следующего/предыдущего допустимого индекса, как указано выше. Вы также можете использовать функцию eachindex для итерирования по допустимым индексам символов:

julia> collect(eachindex(s))
7-element Array{Int64,1}:
  1
  4
  5
  6
  7
 10
 11

Для доступа к исходным кодовым единицам (байтам для UTF-8) кодирования можно использовать функцию codeunit(s,i), где индекс i последовательно меняется от 1 до ncodeunits(s). Функция codeunits(s) возвращает обёртку AbstractVector{UInt8}, которая позволяет получить доступ к этим исходным кодовым единицам (байтам) как массиву.

Строки в Julia могут содержать недопустимые последовательности кодовых единиц UTF-8. Эта конвенция позволяет рассматривать любую последовательность байтов как String. В таких ситуациях правило состоит в том, что при разборе последовательности кодовых единиц слева направо символы образуются самой длинной последовательностью 8-битовых кодовых единиц, которая соответствует началу одной из следующих битовых схем (каждая x может быть 0 или 1):

  • 0xxxxxxx;
  • 110xxxxx 10xxxxxx;
  • 1110xxxx 10xxxxxx 10xxxxxx;
  • 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx;
  • 10xxxxxx;
  • 11111xxx.

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

julia> s = "\xc0\xa0\xe2\x88\xe2|"
"\xc0\xa0\xe2\x88\xe2|"

julia> foreach(display, s)
'\xc0\xa0': [overlong] ASCII/Unicode U+0020 (category Zs: Separator, space)
'\xe2\x88': Malformed UTF-8 (category Ma: Malformed, bad data)
'\xe2': Malformed UTF-8 (category Ma: Malformed, bad data)
'|': ASCII/Unicode U+007C (category Sm: Symbol, math)

julia> isvalid.(collect(s))
4-element BitArray{1}:
 0
 0
 0
 1

julia> s2 = "\xf7\xbf\xbf\xbf"
"\U1fffff"

julia> foreach(display, s2)
'\U1fffff': Unicode U+1FFFFF (category In: Invalid, too high)

Можно увидеть, что первые две кодовые единицы в строке s образуют перекодированное представление пробела. Она недопустима, но принимается в строке как один символ. Следующие две кодовые единицы образуют допустимое начало трехбайтовой последовательности UTF-8. Однако пятая кодовая единица \xe2 не является ее допустимым продолжением. Поэтому кодовые единицы 3 и 4 также интерпретируются как некорректные символы в этой строке. Аналогично, кодовая единица 5 образует некорректный символ, потому что | не является допустимым продолжением для неё. Наконец, строка s2 содержит слишком большое кодовое значение.

Julia по умолчанию использует кодировку UTF-8, а поддержка новых кодировок может быть добавлена пакетами. Например, пакет LegacyStrings.jl реализует типы UTF16String и UTF32String. Дополнительное обсуждение других кодировок и способов реализации их поддержки на данный момент выходит за рамки этой документации. Для дальнейшего обсуждения проблем кодирования UTF-8 см. раздел ниже о литералах массивов байтов. Функция transcode предоставляется для преобразования данных между различными кодировками UTF-xx, в основном для работы с внешними данными и библиотеками.

Конкатенация

Одной из самых распространённых и полезных операций со строками является конкатенация:

julia> greet = "Hello"
"Hello"

julia> whom = "world"
"world"

julia> string(greet, ", ", whom, ".\n")
"Hello, world.\n"

Важно быть осведомлённым о потенциально опасных ситуациях, таких как конкатенация строк с недопустимыми кодировками UTF-8. Результирующая строка может содержать другие символы, чем входные строки, и её количество символов может быть меньше суммы количества символов конкатенированных строк, например:

julia> a, b = "\xe2\x88", "\x80"
("\xe2\x88", "\x80")

julia> c = a*b
"∀"

julia> collect.([a, b, c])
3-element Array{Array{Char,1},1}:
 ['\xe2\x88']
 ['\x80']
 ['∀']

julia> length.([a, b, c])
3-element Array{Int64,1}:
 1
 1
 1

Эта ситуация может произойти только для строк с недопустимой кодировкой UTF-8. Для строк с допустимой кодировкой UTF-8 конкатенация сохраняет все символы в строках и аддитивность длин строк.

Julia также предоставляет * для конкатенации строк:

julia> greet * ", " * whom * ".\n"
"Hello, world.\n"

Хотя * может показаться неожиданным выбором для пользователей языков, предоставляющих + для конкатенации строк, это использование * имеет прецедент в математике, особенно в абстрактной алгебре.

В математике + обычно обозначает коммутативную операцию, где порядок операндов не имеет значения. Примером этого является сложение матриц, где A + B == B + A для любых матриц A и B одинаковой формы. В отличие от этого, * обычно обозначает некоммутативную операцию, где порядок операндов имеет значение. Примером этого является умножение матриц, где вообще A * B != B * A. Как и при умножении матриц, конкатенация строк некоммутативна: greet * whom != whom * greet. Таким образом, * является более естественным выбором для инфиксного оператора конкатенации строк, согласующимся с общим математическим использованием.

Более точно, множество всех строк конечной длины S вместе с оператором конкатенации строк * образуют свободный моноид (S, *). Элемент тождества этого множества — пустая строка, "". Всякий раз, когда свободный моноид не коммутативен, операция обычно представляется как \cdot, * или подобным символом, а не +, которое, как уже отмечалось, обычно подразумевает коммутативность.

Интерполяция

Конструирование строк с помощью конкатенации может стать немного утомительным. Чтобы уменьшить необходимость этих громоздких вызовов string или многократных умножений, Julia позволяет интерполяцию в строковые литералы с помощью $, как в Perl:

julia> "$greet, $whom.\n"
"Hello, world.\n"

Это более удобочитаемо и удобно, эквивалентно вышеприведённой конкатенации строк — система переписывает этот, казалось бы, единый строковый литерал в вызов string(greet, ", ", whom, ".\n").

Наиболее короткое полное выражение после $ берётся как выражение, значение которого должно быть интерполировано в строку. Таким образом, вы можете интерполировать любое выражение в строку, используя скобки:

julia> "1 + 2 = $(1 + 2)"
"1 + 2 = 3"

Как конкатенация, так и интерполяция строк вызывают string для преобразования объектов в строковый вид. Однако, string фактически просто возвращает результат print, поэтому новые типы должны добавлять методы к print или show вместо string.

Большинство объектов, не являющихся AbstractString, преобразуются в строки, тесно соответствующие тому, как они вводятся как литеральные выражения:

julia> v = [1,2,3]
3-element Array{Int64,1}:
 1
 2
 3

julia> "v: $v"
"v: [1, 2, 3]"

string является тождеством для AbstractString и AbstractChar значений, поэтому они интерполируются в строки как есть, без кавычек и экранирования:

julia> c = 'x'
'x': ASCII/Unicode U+0078 (category Ll: Letter, lowercase)

julia> "hi, $c"
"hi, x"

Чтобы включить буквальный $ в строковый литерал, экранируйте его обратной косой чертой:

julia> print("I have \$100 in my account.\n")
I have $100 in my account.

Строковые литералы с тройными кавычками

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

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

julia> str = """
           Hello,
           world.
         """
"  Hello,\n  world.\n"

В этом случае последняя (пустая) строка перед закрывающей """ задаёт уровень отступа.

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

julia> """    This
         is
           a test"""
"    This\nis\n  a test"

Далее, если открывающая """ следует за новой строкой, новая строка удаляется из результирующей строки.

"""hello"""

эквивалентно

"""
hello"""

но

"""

hello"""

будет содержать буквальный символ новой строки в начале.

Удаление новой строки выполняется после отступа. Например:

julia> """
         Hello,
         world."""
"Hello,\nworld."

Конец строки сохранён без изменений.

Строковые литералы с тройными кавычками могут содержать символы " без экранирования.

Обратите внимание, что переводы строк в строковых литералах, как с одинарными, так и с тройными кавычками, приводят к символу новой строки (LF) \n в строке, даже если ваш редактор использует сочетание возврата каретки \r (CR) или CRLF для завершения строк. Чтобы включить CR в строку, используйте явное экранирование \r; например, вы можете ввести строку-литерал "a CRLF line ending\r\n".

Общие операции

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

julia> "abracadabra" < "xylophone"
true

julia> "abracadabra" == "xylophone"
false

julia> "Hello, world." != "Goodbye, world."
true

julia> "1 + 2 = 3" == "1 + 2 = $(1 + 2)"
true

Вы можете искать индекс определённого символа, используя функции findfirst и findlast:

julia> findfirst(isequal('o'), "xylophone")
4

julia> findlast(isequal('o'), "xylophone")
7

julia> findfirst(isequal('z'), "xylophone")

Вы можете начать поиск символа с заданного смещения, используя функции findnext и findprev:

julia> findnext(isequal('o'), "xylophone", 1)
4

julia> findnext(isequal('o'), "xylophone", 5)
7

julia> findprev(isequal('o'), "xylophone", 5)
4

julia> findnext(isequal('o'), "xylophone", 8)

Вы можете использовать функцию occursin, чтобы проверить, содержится ли подстрока в строке:

julia> occursin("world", "Hello, world.")
true

julia> occursin("o", "Xylophon")
true

julia> occursin("a", "Xylophon")
false

julia> occursin('o', "Xylophon")
true

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

Две другие полезные функции для строк — repeat и join:

julia> repeat(".:Z:.", 10)
".:Z:..:Z:..:Z:..:Z:..:Z:..:Z:..:Z:..:Z:..:Z:..:Z:."

julia> join(["apples", "bananas", "pineapples"], ", ", " and ")
"apples, bananas and pineapples"

Некоторые другие полезные функции включают:

  • firstindex(str) предоставляет минимальный (байтовый) индекс, который можно использовать для индексирования в str (всегда 1 для строк, но не обязательно для других контейнеров).
  • lastindex(str) предоставляет максимальный (байтовый) индекс, который можно использовать для индексирования в str.
  • length(str) количество символов в str.
  • length(str, i, j) количество допустимых индексов символов в str от i до j.
  • ncodeunits(str) количество кодовых единиц в строке.
  • codeunit(str, i) возвращает значение кодовой единицы в строке str по индексу i.
  • thisind(str, i) по произвольному индексу в строке находит первый индекс символа, на который указывает индекс.
  • nextind(str, i, n=1) находит начало n-го символа, начиная с индекса i.
  • prevind(str, i, n=1) находит начало n-го символа, начиная перед индексом i.

Нестандартные строковые литералы

Существуют ситуации, когда необходимо создать строку или использовать семантику строк, но поведение стандартного конструктора строк не соответствует требованиям. Для таких случаев Julia предоставляет нестандартные строковые литералы. Нестандартный строковый литерал выглядит как обычный строковый литерал в двойных кавычках, но сразу же предваряется идентификатором и ведет себя не совсем как обычный строковый литерал. Регулярные выражения, литералы массивов байтов и литералы номеров версий, как описано ниже, являются примерами нестандартных строковых литералов. Другие примеры приведены в разделе Метапрограммирование.

Регулярные выражения

Julia использует Perl-совместимые регулярные выражения (regex), предоставляемые библиотекой PCRE (PCRE) (описание синтаксиса можно найти здесь). Регулярные выражения связаны со строками двумя способами: очевидная связь заключается в том, что регулярные выражения используются для поиска регулярных шаблонов в строках; другая связь состоит в том, что регулярные выражения сами вводятся как строки, которые анализируются в конечный автомат, который можно использовать для эффективного поиска шаблонов в строках. В Julia регулярные выражения вводятся с помощью нестандартных строковых литералов, предваряемых различными идентификаторами, начинающимися с r. Самый базовый литерал регулярного выражения без включенных опций просто использует r"...":

julia> r"^\s*(?:#|$)"
r"^\s*(?:#|$)"

julia> typeof(ans)
Regex

Для проверки соответствия regex строке используйте occursin:

julia> occursin(r"^\s*(?:#|$)", "not a comment")
false

julia> occursin(r"^\s*(?:#|$)", "# a comment")
true

Как можно увидеть здесь, occursin просто возвращает true или false, указывая, происходит ли совпадение с заданным regex в строке. Однако обычно требуется не только узнать, совпала ли строка, но и как она совпала. Для захвата этой информации о совпадении используйте функцию match:

julia> match(r"^\s*(?:#|$)", "not a comment")

julia> match(r"^\s*(?:#|$)", "# a comment")
RegexMatch("#")

Если регулярное выражение не соответствует данной строке, match возвращает nothing — специальное значение, которое ничего не выводит в интерактивном приглашении. Помимо того, что оно ничего не выводит, это полностью нормальное значение, и вы можете проверить его программно:

m = match(r"^\s*(?:#|$)", line)
if m === nothing
    println("not a comment")
else
    println("blank or comment")
end

Если регулярное выражение совпадает, значение, возвращаемое match, является объектом RegexMatch. Эти объекты записывают, как выражение совпадает, включая подстроку, с которой совпадает шаблон, и любые захваченные подстроки, если они есть. Этот пример захватывает только часть подстроки, которая совпадает, но, возможно, мы хотим захватить любой непустой текст после символа комментария. Мы могли бы сделать следующее:

julia> m = match(r"^\s*(?:#\s*(.*?)\s*$|$)", "# a comment ")
RegexMatch("# a comment ", 1="a comment")

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

julia> m = match(r"[0-9]","aaaa1aaaa2aaaa3",1)
RegexMatch("1")

julia> m = match(r"[0-9]","aaaa1aaaa2aaaa3",6)
RegexMatch("2")

julia> m = match(r"[0-9]","aaaa1aaaa2aaaa3",11)
RegexMatch("3")

Вы можете извлечь следующую информацию из объекта RegexMatch:

  • всю сопоставленную подстроку: m.match
  • захваченные подстроки в виде массива строк: m.captures
  • смещение, с которого начинается все совпадение: m.offset
  • смещения захваченных подстрок в виде вектора: m.offsets

Если захват не соответствует, вместо подстроки в m.captures содержится nothing в этой позиции, а m.offsets имеет смещение ноль (напоминаем, что индексы в Julia основаны на 1, поэтому смещение ноль в строке недопустимо). Вот пара несколько искусственных примеров:

julia> m = match(r"(a|b)(c)?(d)", "acd")
RegexMatch("acd", 1="a", 2="c", 3="d")

julia> m.match
"acd"

julia> m.captures
3-element Array{Union{Nothing, SubString{String}},1}:
 "a"
 "c"
 "d"

julia> m.offset
1

julia> m.offsets
3-element Array{Int64,1}:
 1
 2
 3

julia> m = match(r"(a|b)(c)?(d)", "ad")
RegexMatch("ad", 1="a", 2=nothing, 3="d")

julia> m.match
"ad"

julia> m.captures
3-element Array{Union{Nothing, SubString{String}},1}:
 "a"
 nothing
 "d"

julia> m.offset
1

julia> m.offsets
3-element Array{Int64,1}:
 1
 0
 2

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

julia> first, second, third = m.captures; first
"a"

К захватам также можно обратиться, проиндексировав объект RegexMatch с номером или именем группы захвата:

julia> m=match(r"(?<hour>\d+):(?<minute>\d+)","12:45")
RegexMatch("12:45", hour="12", minute="45")

julia> m[:minute]
"45"

julia> m[2]
"45"

К захватам можно обратиться в строке подстановки при использовании replace с помощью \n для ссылки на n-ю группу захвата и предваряя строку подстановки s. Группа захвата 0 относится к объекту всего совпадения. К именованным группам захвата можно обратиться в подстановке с помощью \g<groupname>. Например:

julia> replace("first second", r"(\w+) (?<agroup>\w+)" => s"\g<agroup> \1")
"second first"

К пронумерованным группам захвата также можно обратиться как \g<n> для разбиения, как в:

julia> replace("a", r"." => s"\g<0>1")
"a1"

Вы можете изменить поведение регулярных выражений с помощью сочетания флагов i, m, s и x после закрывающей двойной кавычки. Эти флаги имеют то же значение, что и в Perl, как объясняется в этом отрывке из страницы справки perlre:

i   Do case-insensitive pattern matching.

    If locale matching rules are in effect, the case map is taken
    from the current locale for code points less than 255, and
    from Unicode rules for larger code points. However, matches
    that would cross the Unicode rules/non-Unicode rules boundary
    (ords 255/256) will not succeed.

m   Treat string as multiple lines.  That is, change "^" and "$"
    from matching the start or end of the string to matching the
    start or end of any line anywhere within the string.

s   Treat string as single line.  That is, change "." to match any
    character whatsoever, even a newline, which normally it would
    not match.

    Used together, as r""ms, they let the "." match any character
    whatsoever, while still allowing "^" and "$" to match,
    respectively, just after and just before newlines within the
    string.

x   Tells the regular expression parser to ignore most whitespace
    that is neither backslashed nor within a character class. You
    can use this to break up your regular expression into
    (slightly) more readable parts. The '#' character is also
    treated as a metacharacter introducing a comment, just as in
    ordinary code.

Например, следующее регулярное выражение включает все три флага:

julia> r"a+.*b+.*?d$"ism
r"a+.*b+.*?d$"ims

julia> match(r"a+.*b+.*?d$"ism, "Goodbye,\nOh, angry,\nBad world\n")
RegexMatch("angry,\nBad world")

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

julia> x = 10
10

julia> r"$x"
r"$x"

julia> "$x"
"10"

julia> r"\x"
r"\x"

julia> "\x"
ERROR: syntax: invalid escape sequence

Также поддерживаются строковые regex с тройными кавычками в формате r"""...""" (и могут быть удобны для регулярных выражений, содержащих кавычки или новые строки).

Конструктор Regex() может использоваться для создания допустимой строки regex программно. Это позволяет использовать содержимое строковых переменных и другие строковые операции при построении строки regex. Любой из вышеперечисленных кодов regex может быть использован внутри единственного строкового аргумента для Regex(). Вот несколько примеров:

julia> using Dates

julia> d = Date(1962,7,10)
1962-07-10

julia> regex_d = Regex("Day " * string(day(d)))
r"Day 10"

julia> match(regex_d, "It happened on Day 10")
RegexMatch("Day 10")

julia> name = "Jon"
"Jon"

julia> regex_name = Regex("[\"( ]$name[\") ]")  # interpolate value of name
r"[\"( ]Jon[\") ]"

julia> match(regex_name," Jon ")
RegexMatch(" Jon ")

julia> match(regex_name,"[Jon]") === nothing
true

Литералы массивов байтов

Еще один полезный нестандартный строковый литерал — литерал массива байтов: b"...". Эта форма позволяет использовать синтаксис строк для выражения массивов байтов с чтением только из литералов — то есть массивов значений UInt8. Тип этих объектов — CodeUnits{UInt8, String}. Правила для литералов массивов байтов следующие:

  • Символы ASCII и ASCII-экранирование создают один байт.
  • \x и восьмеричные последовательности экранирования создают байт, соответствующий значению экранирования.
  • Последовательности экранирования Unicode создают последовательность байтов, кодирующих этот код символа в UTF-8.

Существует некоторое перекрытие между этими правилами, поскольку поведение \x и восьмеричных экранирований меньше 0x80 (128) покрывается первыми двумя правилами, но здесь эти правила согласуются. Вместе эти правила позволяют легко использовать символы ASCII, произвольные значения байтов и последовательности UTF-8 для создания массивов байтов. Вот пример, использующий все три:

julia> b"DATA\xff\u2200"
8-element Base.CodeUnits{UInt8,String}:
 0x44
 0x41
 0x54
 0x41
 0xff
 0xe2
 0x88
 0x80

ASCII-строка «DATA» соответствует байтам 68, 65, 84, 65. \xff создает один байт 255. Последовательность экранирования Unicode \u2200 закодирована в UTF-8 как три байта 226, 136, 128. Обратите внимание, что полученный массив байтов не соответствует допустимой строке UTF-8:

julia> isvalid("DATA\xff\u2200")
false

Как упоминалось, тип CodeUnits{UInt8,String} ведет себя как массив для чтения только UInt8, и если вам нужен стандартный вектор, вы можете преобразовать его с помощью Vector{UInt8}:

julia> x = b"123"
3-element Base.CodeUnits{UInt8,String}:
 0x31
 0x32
 0x33

julia> x[1]
0x31

julia> x[1] = 0x32
ERROR: setindex! not defined for Base.CodeUnits{UInt8,String}
[...]

julia> Vector{UInt8}(x)
3-element Array{UInt8,1}:
 0x31
 0x32
 0x33

Также обратите внимание на существенную разницу между \xff и \uff: первая последовательность экранирования кодирует байт 255, а вторая последовательность экранирования представляет код символа 255, который кодируется двумя байтами в UTF-8:

julia> b"\xff"
1-element Base.CodeUnits{UInt8,String}:
 0xff

julia> b"\uff"
2-element Base.CodeUnits{UInt8,String}:
 0xc3
 0xbf

Символьные литералы используют то же поведение.

Для кодов символов меньше \u80, оказывается, что кодирование каждого кода символа в UTF-8 — это просто единственный байт, полученный от соответствующего \x экранирования, поэтому разницей можно безопасно пренебречь. Однако для экранирований \x80 по \xff по сравнению с \u80 по \uff существует существенная разница: первые экранирования кодируют все одиночные байты, которые — если не следуют за очень специфическими байтами продолжения — не образуют допустимых данных UTF-8, в то время как последние экранирования представляют все коды символов с двухбайтовым кодированием.

END_OF_DOCUMENT_MARKER

Если всё это сильно запутанно, попробуйте прочитать «The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets». Это отличное введение в Unicode и UTF-8, которое может помочь развеять некоторые сомнения по этому поводу.

Литералы номеров версий

Номера версий можно легко выразить с помощью нестандартных строковых литералов в формате v"...". Литералы номеров версий создают объекты VersionNumber, которые следуют спецификациям семантической версии, и поэтому состоят из числовых значений основной, дополнительной и исправленной версий, за которыми следуют префиксные и сборные буквенно-цифровые аннотации. Например, v"0.2.1-rc1+win64" разделяется на основную версию 0, дополнительную версию 2, исправленную версию 1, префикс rc1 и сборку win64. При вводе литерала версии всё, кроме номера основной версии, является необязательным, поэтому, например, v"0.2" эквивалентно v"0.2.0" (с пустыми аннотациями префикса/сборки), v"2" эквивалентно v"2.0.0" и так далее.

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

if v"0.2" <= VERSION < v"0.3-"
    # do something specific to 0.2 release series
end

Обратите внимание, что в приведённом выше примере используется нестандартный номер версии v"0.3-" с последующим -: эта запись является расширением Julia стандартного формата, и используется для указания версии, которая ниже, чем любая версия 0.3, включая все ее предварительные версии. Таким образом, в приведённом выше примере код будет выполняться только с версиями стабильной 0.2, и исключит такие версии, как v"0.3.0-rc1". Чтобы также разрешить нестабильные (т.е. предварительные) версии 0.2, проверка нижней границы должна быть изменена следующим образом: v"0.2-" <= VERSION.

Другое расширение спецификации нестандартных версий позволяет использовать последующий +, чтобы выразить верхний предел версий сборки, например, VERSION > v"0.2-rc1+" может означать любую версию выше 0.2-rc1 и любые её сборки: она вернёт false для версии v"0.2-rc1+win64" и true для v"0.2-rc2".

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

Помимо использования в константе VERSION, объекты VersionNumber широко используются в модуле Pkg для указания версий пакетов и их зависимостей.

Необработанные строковые литералы

Необработанные строки без интерполяции или разбора спецсимволов могут быть выражены с помощью нестандартных строковых литералов в формате raw"...". Необработанные строковые литералы создают обычные String объекты, которые содержат заключённое содержимое точно так, как оно было введено, без интерполяции или разбора спецсимволов. Это полезно для строк, содержащих код или разметку на других языках, в которых $ или \ используются как специальные символы.

Исключением является то, что кавычки всё ещё должны быть экранированы, например, raw"\"" эквивалентно "\"". Чтобы можно было выразить все строки, обратные слэши также должны быть экранированы, но только когда они появляются непосредственно перед символом кавычки:

julia> println(raw"\\ \\\"")
\\ \"

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

© 2009–2020 Jeff Bezanson, Stefan Karpinski, Viral B. Shah, and other contributors
Licensed under the MIT License.
https://docs.julialang.org/en/v1.4.2/manual/strings/

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API