String у Julia - це впорядкована послідовність символів. Як зазначає посібник: «Звісно, справжні труднощі починаються тоді, коли постає питання, що таке символ».
Багато з цієї складності буде докладніше розглянуто в концепті Chars, але коротко:
Загалом рядки тексту беруться в подвійні лапки " ".
Одинарні лапки ' ' використовуються лише для Chars.
Також можна використовувати потрійні подвійні лапки """ """, що дає змогу вживати " усередині рядка тексту без екранування.
Що важливіше, у багаторядкових рядках тексту прибирається зайвий відступ.
julia> """
Multiline
"""
"Multiline\nString\n"
Як і більшість мов, починаючи з C, Julia використовує зворотну скісну риску \ для екранування:
\n у попередньому прикладі.\$ (див. нижче).У принципі, рядки тексту можна індексувати так само, як масиви:
julia> s[1], s[5]
('J', 'a')
Це чудово працює для ASCII-рядка, як-от "Julia", але так ми ігноруємо більшість мов світу.
Розгляньмо один із символів у «Беовульфі», написаному староанглійською близько 15 століть тому.
"Hrōðgār" явно складається з 7 символів, але не всі вони належать до ASCII.
julia> king = "Hrōðgār"
"Hrōðgār"
julia> length(king)
7
julia> isascii(king)
false
# collect() converts to a Char vector:
julia> collect(king)
7-element Vector{Char}:
'H': ASCII/Unicode U+0048 (category Lu: Letter, uppercase)
'r': ASCII/Unicode U+0072 (category Ll: Letter, lowercase)
'ō': Unicode U+014D (category Ll: Letter, lowercase)
'ð': Unicode U+00F0 (category Ll: Letter, lowercase)
'g': ASCII/Unicode U+0067 (category Ll: Letter, lowercase)
'ā': Unicode U+0101 (category Ll: Letter, lowercase)
'r': ASCII/Unicode U+0072 (category Ll: Letter, lowercase)
Індексування такого рядка тексту спочатку працює добре, але в якийсь момент ламається:
julia> king[3]
'ō': Unicode U+014D (category Ll: Letter, lowercase)
julia> king[4]
ERROR: StringIndexError: invalid index [4], valid nearby indices [3]=>'ō', [5]=>'ð'
Проблема в тому, що таке індексування рахує байти, а в не-ASCII рядках тексту вони не мають співвідношення 1:1 із символами.
Символ Unicode/UTF-8 може потребувати до 4 байтів для свого подання, і концепт Chars розгляне це докладніше.
Багато мов використовують + як оператор конкатенації рядків тексту, але не Julia.
Принаймні повідомлення про помилку тут корисне.
julia> "s1 " + "s2"
ERROR: MethodError: no method matching +(::String, ::String)
The function `+` exists, but no method is defined for this combination of argument types.
String concatenation is performed with *
julia> "s1 " * "s2"
"s1 s2"
Функція string() конкатенує довільну кількість рядків тексту.
julia> string("lots ", "of ", "bits")
"lots of bits"
Функція join() робить те саме, а також приймає масив рядків тексту й конкатенує їх із заданим роздільником (з типовим значенням "").
julia> join(["lots", "of", "bits"], "_")
"lots_of_bits"
Зауважмо, що рядки тексту незмінні, а конкатенація передбачає копіювання. Тож багаторазова конкатенація всередині циклу є неефективною, і краще зібрати фрагменти в масив і зʼєднати їх усі наприкінці.
Просто:
julia> repeat("Abc", 5)
"AbcAbcAbcAbcAbc"
У багатьох мовах є спосіб вставляти обчислені значення в рядки тексту, хоча щодо найкращого синтаксису для цього існує надзвичайна розбіжність думок.
У Julia перед інтерпольованими значеннями ставиться $.
julia> myvar = 42
42
julia> "myvar evaluates to $myvar"
"myvar evaluates to 42"
julia> r = 5.3
5.3
julia> "A circle of radius $r has area $(π * r^2)"
"A circle of radius 5.3 has area 88.24733763933729"
Дужки обовʼязкові, коли їх пропуск створив би неоднозначність, але необовʼязкові для одного ідентифікатора, за яким іде пробільний символ.
Щоб включити $ у рядок тексту, екрануймо його як \$.
Зауважмо обмеження: інтерполяція наразі не дозволяє форматування, як-от задання кількості десяткових знаків для виведення.
@sprintf()Для тоншого контролю над збиранням рядків тексту Julia запозичує функцію sprintf із C (і кількох пізніших мов: Вікіпедія наводить близько 30).
У Julia це реалізовано як macro у модулі Printf, звідси префікс @.
julia> using Printf
julia> r = 5.3
5.3
julia> @sprintf("A circle of radius %.1f has area %.2f", r, π * r^2)
"A circle of radius 5.3 has area 88.25"
Для простоти цей розділ зосередиться на функціях, які повертають новий рядок тексту і залишають вхідні дані без змін.
Багато з них мають відповідник із суфіксом !, який змінює вхідні дані на місці.
Якої довжини рядок тексту? Іноді це залежить від того, як ми рахуємо.
У найпростішому випадку це ASCII-рядки, де кожен символ займає 1 байт.
julia> string_asc = "Julia"
"Julia"
julia> length(string_asc)
5
julia> sizeof(string_asc)
5
Функція length() повертає кількість символів, а функція sizeof() - кількість байтів.
Ця різниця стає важливою з іншими наборами символів:
julia> king = "Hrōðgār"
"Hrōðgār"
julia> length(king)
7
julia> sizeof(king)
10
Поширена операція в програмуванні - узяти довгий рядок тексту й розбити його на частини за роздільником з одного або кількох символів.
Найзагальніше ми використовуємо функцію split().
Типовим роздільником є (будь-який) пробільний символ, але можна вказати й інші.
julia> split("my little string")
3-element Vector{SubString{String}}:
"my"
"little"
"string"
julia> split("My_snake_case_string", "_")
4-element Vector{SubString{String}}:
"My"
"snake"
"case"
"string"
У цих прикладах рядок тексту розбивається на менші рядки тексту. Розбиття рядка тексту на символи - це інша операція.
Чи містить рядок тексту заданий підрядок?
Як не дивно, для цього є дві функції: occursin() і contains() відрізняються лише порядком своїх аргументів.
julia> needle = "Julia"
"Julia"
julia> haystack = "Python, Julia and R"
"Python, Julia and R"
julia> occursin(needle, haystack)
true
julia> contains(haystack, needle)
true
Щоб точніше вказати позицію:
julia> startswith(haystack, "Python")
true
julia> endswith(haystack, "Python")
false
Щоб отримати індекси початку й кінця підрядка, у нас є кілька функцій:
findfirst()findlast()findprev()findnext()julia> findfirst(needle, haystack)
9:13
Щоб замінити підрядки, перелічімо зміни за допомогою синтаксису x => y.
julia> replace("abc", "a"=>"b", "b"=>"c", "c"=>"a")
"bca"
Починаючи з Julia 1.7, функція replace приймає довільну кількість змін із гарантією, що жоден символ не буде змінено більше ніж один раз під час заміни.
Синтаксис x => y називається pair. Це важливий тип у Julia, який докладніше розглядається в концепті Dicts and Pairs.
Часто нам потрібно прибрати пробільні символи на початку та/або в кінці рядка тексту.
Для цього є функції lstrip() (зліва), rstrip() (справа), strip() (з обох боків).
julia> strip("\n\t useful_bit\n\n")
"useful_bit"
Назви цих функцій доволі промовисті:
julia> s = repeat("aBcD ", 5)
"aBcD aBcD aBcD aBcD aBcD "
julia> uppercase(s)
"ABCD ABCD ABCD ABCD ABCD "
julia> lowercase(s)
"abcd abcd abcd abcd abcd "
julia> titlecase(s) # Initial letter of each word uppercase
"Abcd Abcd Abcd Abcd Abcd "
У цій вправі ми будемо обробляти рядки логу.
Кожен рядок логу записано як рядок тексту (англ. string) у такому форматі: "[<LEVEL>]: <MESSAGE>".
Є три різні рівні логу:
INFOWARNINGERRORНа нас чекають три завдання, кожне з яких отримає рядок логу й попросить нас щось із ним зробити.
Реалізуйте функцію message, щоб вона повертала повідомлення рядка логу:
julia> message("[ERROR]: Invalid operation")
"Invalid operation"
Усі пробіли на початку й у кінці потрібно прибрати:
julia> message("[WARNING]: Disk almost full\r\n")
"Disk almost full"
Реалізуйте функцію log_level, щоб вона повертала рівень логу з рядка логу, причому в нижньому регістрі:
julia> log_level("[ERROR]: Invalid operation")
"error"
Реалізуйте функцію reformat, яка переформатовує рядок логу: ставить повідомлення першим, а рівень логу після нього в дужках:
julia> reformat("[INFO]: Operation completed")
"Operation completed (info)"
Примітка: Усі рядки тексту в цій вправі записані англійською й обмежені набором символів ASCII. Пізніші концепції дадуть змогу попрацювати з символами Unicode.
Зареєструйтеся на Exercism, щоб вивчати й опановувати Julia, а також 35 концепцій128 вправ та справжнє наставництво від людей, і все це безкоштовно.