Треки
/
Julia
Julia
/
Салабус
/
Рядки тексту
Ря

Рядки тексту у Julia

7 вправ

Про концепцію Рядки тексту

String у Julia - це впорядкована послідовність символів. Як зазначає посібник: «Звісно, справжні труднощі починаються тоді, коли постає питання, що таке символ».

Багато з цієї складності ми докладніше розглянемо в концепції Chars, а поки що коротко:

  • Усі рядки тексту (англ. string) у Julia - це Unicode.
  • Рядки, які до того ж є ASCII (англійські літери від A до Z у верхньому й нижньому регістрі, цифри та кілька розділових знаків), легко опрацьовувати.
  • Більшість інших систем письма у світі можна опрацьовувати, але вони потребують більшої уваги.

Рядкові літерали

Загалом рядки тексту беруть у подвійні лапки " ". Одинарні лапки ' ' використовують лише для Chars.

Також можна використовувати потрійні подвійні лапки """ """, що дає змогу вживати " всередині рядка без екранування. Що важливіше, у багаторядкових рядках прибирають зайвий відступ.

julia> """
       Multiline
       String
       """
"Multiline\nString\n"

Екранування символів

Як і більшість мов, починаючи з C, Julia використовує зворотну скісну риску \ для екранування:

  1. Щоб увести недруковані символи, як-от символ нового рядка \n у попередньому прикладі.
  2. Щоб захистити символи, які інакше мали б особливе значення, як-от \$ (див. нижче).

Ітерація

Рядок тексту - це колекція, яку можна перебирати, тож цикл for ... in працюватиме без потреби перетворювати його на вектор символів.

julia> s = "Julia"
"Julia"

# admittedly, this is a silly example
julia> for c in s; print(c, "; "); end
J; u; l; i; a; 

Такий перебір має ту перевагу, що Julia правильно визначає межі символів, навіть для не-ASCII рядків. Значення цього стане зрозумілішим у наступному розділі.

Індексування

У принципі, рядки тексту можна індексувати точно так само, як вектори:

julia> s[1], s[5]
('J', 'a')

Це, звісно, працює для ASCII-рядка на кшталт «Julia», але так ми нехтуємо більшістю мов світу.

Погляньмо на один із символів у творі Beowulf, написаному давньоанглійською близько 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"

Дужки потрібні, коли їх пропуск створив би неоднозначність, але необовʼязкові для одного ідентифікатора, за яким іде пробіл.

Щоб уставити $ у рядок, його екранують як \$.

Зауважмо на обмеження: інтерполяція наразі не дає змоги форматувати, наприклад, кількість десяткових знаків для показу.

Обхідні шляхи такі:

  • Обгорнути обчислення у функцію round().
  • Поза межами засобу запуску тестів Exercism пакет PyFormattedStrings емулює f-рядки Python.
  • Макрос @sprintf() описано нижче.

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

Ці приклади розбивають рядок на менші рядки тексту. Розбиття рядка на символи - це інша операція.

julia> collect("input string")
12-element Vector{Char}:
 'i': ASCII/Unicode U+0069 (category Ll: Letter, lowercase)
 'n': ASCII/Unicode U+006E (category Ll: Letter, lowercase)
 'p': ASCII/Unicode U+0070 (category Ll: Letter, lowercase)
 'u': ASCII/Unicode U+0075 (category Ll: Letter, lowercase)
 't': ASCII/Unicode U+0074 (category Ll: Letter, lowercase)
 ' ': ASCII/Unicode U+0020 (category Zs: Separator, space)
 's': ASCII/Unicode U+0073 (category Ll: Letter, lowercase)
 't': ASCII/Unicode U+0074 (category Ll: Letter, lowercase)
 'r': ASCII/Unicode U+0072 (category Ll: Letter, lowercase)
 'i': ASCII/Unicode U+0069 (category Ll: Letter, lowercase)
 'n': ASCII/Unicode U+006E (category Ll: Letter, lowercase)
 'g': ASCII/Unicode U+0067 (category Ll: Letter, lowercase)

Пошук і заміна

Чи містить рядок заданий підрядок? Як не дивно, для цього є дві функції: 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

Щоб отримати індекси початку й кінця підрядка, у нас є кілька функцій:

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 "

Розбір

Взаємне перетворення чисел та їхнього рядкового подання часто буває корисним.

Перетворити число на рядок просто, хоч основа, відмінна від типової 10, потребує додаткового параметра:

julia> string(42)
"42"

julia> string(42, base=16)
"2a"

julia> string(42, base=2)
"101010"

Розбір рядка для отримання числового значення трохи складніший і має більше можливостей для помилки. Указати цільовий числовий тип обовʼязково. Указувати основу необовʼязково, а її типове значення - 10.

julia> parse(Int, "42")
42

julia> parse(Float64, "42")
42.0

julia> parse(Int, "42?")
ERROR: ArgumentError: invalid base 10 digit '?' in "42?"

julia> parse(Int, "42 ")  # whitespace may be tolerated
42

Особливі рядки

Іноді корисно вимкнути звичайну інтерполяцію та екранування всередині рядків. Для цього перед відкривною лапкою ставлять raw.

julia> x = 10
10

julia> "x = $x if we use \$ for interpolation"
"x = 10 if we use \$ for interpolation"

julia> raw"x = $x if we use \$ for interpolation"
"x = \$x if we use \\\$ for interpolation"

Регулярні вирази мають власний складний синтаксис, і Julia надає два корисні типи рядків: r" " для Regex і s" " для роботи із захопленими фрагментами.

Різні інші особливі рядки рідше трапляються в програмуванні у стилі Exercism, хоч і широко використовуються під час створення бібліотек.

  • Рядки семантичного версіонування v"x.y.z" описують нумерацію major.minor.patch для релізів програмного забезпечення.
  • Байтові масиви-літерали b" " обходять межі символів Unicode і працюють на рівні байтів.
Редагувати через GitHub Посилання відкривається в новому вікні або вкладці

Вивчити концепцію Рядки тексту

Практика заблокована

Розблокуйте ще 2 вправи, щоб практикувати концепцію Рядки тексту