Spec-Zone.ru › Julia 1.1

Строки

Строки — это конечные последовательности символов. Конечно, настоящие проблемы возникают, когда задаётся вопрос о том, что такое символ. Символы, знакомые говорящим по-английски, — это буквы 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: первый элемент многих индексируемых целыми числами объектов находится по индексу 1. (Как мы увидим ниже, это не обязательно означает, что последний элемент находится по индексу n, где n — длина строки.)

Вы можете выполнять арифметические и другие операции со значением 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 "Hello, world.\n"
  at index [0]
[...]

julia> str[end+1]
ERROR: BoundsError: attempt to access "Hello, world.\n"
  at index [15]
Stacktrace:
[...]

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

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 и выше кодируются с использованием нескольких байт — до четырёх на символ. Это означает, что не каждый байтовый индекс в строке UTF-8 обязательно является допустимым индексом символа. Если вы индексируете строку в таком недопустимом байтовом индексе, выбрасывается ошибка:

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) и так далее.

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

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

Строки в 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}:
 false
 false
 false
  true

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"

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

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

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

И конкатенация, и строковая интерполяция вызывают 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 в строке. Однако, как правило, требуется узнать не только соответствует ли строка, но и как она соответствует. Для получения этой информации о совпадении используйте функцию nothing вместо этого:

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 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

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

Конструктор 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.
  • Последовательности escape Unicode дают последовательность байтов, кодирующую этот код символа в 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. Последовательность escape 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: первая последовательность 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 escape-последовательностью, поэтому этим различием можно пренебречь. Однако для escape-последовательностей \x80 по \xff по сравнению с \u80 по \uff существует существенная разница: первые escape-последовательности кодируют все одиночные байты, которые — если за ними не следуют очень конкретные продолжения — не образуют действительных данных UTF-8, тогда как последние escape-последовательности представляют все коды символов с кодированием в два байта.

Если это все очень запутанно, попробуйте прочитать "Абсолюльный минимум, который каждый разработчик программного обеспечения должен знать о Unicode и наборах символов". Это отличное введение в 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–2019 Jeff Bezanson, Stefan Karpinski, Viral B. Shah, and other contributors
Licensed under the MIT License.
https://docs.julialang.org/en/v1.1.1/manual/strings/

Spec-Zone.ru

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