Строки
Строки — это конечные последовательности символов. Конечно, настоящие проблемы начинаются, когда спрашивают, что такое символ. Символы, с которыми знакомы носители английского языка, — это буквы A, B, C и т. д., а также цифры и знаки препинания. Эти символы стандартизированы вместе с сопоставлением с целочисленными значениями от 0 до 127 стандартом ASCII. Конечно, существуют и другие символы, используемые в языках, отличных от английского, включая варианты символов ASCII с диакритическими знаками и другими модификациями, родственные системы письменности, такие как кириллица и греческий алфавит, и системы письменности, совершенно не связанные с ASCII и английским языком, включая арабский, китайский, иврит, хинди, японский и корейский. Стандарт Unicode решает сложности, связанные с определением символа, и в целом признан окончательным стандартом, решающим эту проблему. В зависимости от ваших потребностей, вы можете либо полностью игнорировать эти сложности и просто предположить, что существуют только символы ASCII, либо написать код, который может обрабатывать любые символы или кодировки, которые могут встретиться при работе с текстом, отличным от ASCII. Julia делает работу с обычным текстом ASCII простой и эффективной, а обработка Unicode — максимально простой и эффективной. В частности, вы можете написать код строк в стиле C для обработки строк ASCII, и он будет работать как ожидается, как в плане производительности, так и в плане семантики. Если такой код столкнется с текстом, отличным от ASCII, он вежливо завершится с ясным сообщением об ошибке, а не молча введет искаженные результаты. В этом случае изменение кода для обработки данных, отличных от ASCII, является простым.
Вот несколько примечательных функций высокого уровня относительно строк Julia:
- Встроенный конкретный тип, используемый для строк (и строковых литералов) в Julia, — это
String. Он поддерживает весь диапазон символов Unicode через кодировку UTF-8. (Для преобразования в/из других кодировок Unicode предоставлена функцияtranscode.) - Все типы строк являются подтипами абстрактного типа
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+00 до 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[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). Ключевое слово 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)
Использование индекса меньше 1 или больше end вызывает ошибку:
julia> str[0] 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, а также всех стандартных последовательностей C. Аналогично, их можно использовать для написания строковых литералов:
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; -
110xxxxx10xxxxxx; -
1110xxxx10xxxxxx10xxxxxx; -
11110xxx10xxxxxx10xxxxxx10xxxxxx; -
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 (описание синтаксиса можно найти здесь). Регулярные выражения связаны со строками двумя способами: очевидная связь заключается в том, что регулярные выражения используются для поиска регулярных шаблонов в строках; другая связь заключается в том, что регулярные выражения сами вводятся как строки, которые анализируются в автомат, который может эффективно искать шаблоны в строках. В 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, поэтому смещение 0 в строке недопустимо). Вот пара несколько искусственных примеров:
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 manpage:
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и восьмеричные escape-последовательности создают *байт*, соответствующий значению escape. - Unicode-escape-последовательности создают последовательность байтов, кодирующую этот код символа в UTF-8.
Существует некоторое перекрытие между этими правилами, поскольку поведение \x и восьмеричных escape-последовательностей меньше 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-escape \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: первая escape-последовательность кодирует *байт 255*, тогда как последняя escape-последовательность представляет *код символа 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, тогда как последние экранирования представляют все коды символов с двухбайтовыми кодировками.
Если это всё чрезвычайно запутанно, попробуйте прочитать "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.3.1/manual/strings/