Треки
/
R
R
/
Вправи
/
Рівні журналу
Рівні журналу

Рівні журналу

Навчальна вправа

Вступ

Загальні відомості

Рядок тексту (англ. string) - це фрагмент тексту в лапках. Будь-яка версія R, яку зараз підтримують, кодує рядки тексту в UTF-8, тож можна підтримати більшість мов світу.

mystring <- "some text"
mystring
#> [1] "some text"
typeof(mystring)
#> [1] "character"

На жаль, ця термінологія може заплутати програмістів, знайомих з іншими мовами.

Хоча type тут має значення character, R не робить різниці між однією літерою (у кількох інших мовах її називають char, але не в R) і довгим рядком тексту. І те, і те - це один елемент, і його можна записувати в одинарних ' ' або подвійних " " лапках без різниці.

Посібник зі стилю радить використовувати подвійні лапки, крім випадків, коли треба вжити подвійні лапки всередині рядка тексту без екранування.

str2 <- 'String with "quotes"'  # convenience format
str2
#> [1] "String with \"quotes\""    # standard format

Отже, character vector - це вектор рядків тексту, де кожен рядок тексту є одним елементом.

s <- c("some", "strings")
s
#> [1] "some"    "strings"
length(s)
#> [1] 2
length(s[1])  # the number of vector elements, not the string length
#> [1] 1

nchar(s[1])  # the number of characters in the string "some"
#> [1] 4

У наведеному вище прикладі length(s[1]) дорівнює length("some"): це кількість елементів вектора, а не довжина рядка тексту.

Маніпулювати окремим рядком тексту, звісно, можна, як у прикладі вище з nchar(), але для цього потрібні певні функції, про які йтиметься далі.

Функції для роботи з рядками тексту

У base R можливо майже все, але багато програмістів вважають підтримку роботи з рядками тексту доволі обмеженою й заплутаною.

На щастя, є пакети tidyverse, значно інтуїтивніші, гнучкіші та самоузгоджені.

Base R

Досі є багато застарілого коду, що використовує функції для рядків тексту старого зразка. Це стосується більшості рішень спільноти на Exercism.

Крім того, для невеликих завдань буває корисно скористатися вбудованими функціями замість того, щоб імпортувати велику бібліотеку. Супроводжувачі треку R зазвичай роблять саме так, коли пишуть зразкові рішення, щоб перевірити вправи.

Тож знання старих підходів усе ще варто здобути.

nchar

Повертає кількість символів у рядку тексту.

nchar("string")
#> [1] 6

nchar("Hrōðgār") # a person in Beowulf, written in Old English
#> [1] 7

Тема того, що таке «символ», (а вона непроста!) виходить за межі нашого розгляду. Звернімо увагу лише на кілька моментів у прикладах вище:

  • Хоча рядки тексту в R внутрішньо реалізовано мовою C, вони не завершуються символом \0 (це додало б 1 до довжини).
  • Багатобайтові символи Unicode обробляються так, як і можна було б сподіватися. «Hrōðgār» налічує 7 символів (їх також називають «рунами» або «кодовими точками»), але для збереження потребує 10 байтів.
paste і paste0

Поєднує довільну кількість рядків тексту в один рядок тексту.

Для paste можна за бажанням указати роздільник: типове значення - пробіл.

paste0(v) рівнозначне paste(v, sep = "").

paste("bits", "of", "a", "string")
#> [1] "bits of a string"
paste0("bits", "of", "a", "string")
#> [1] "bitsofastring"
sprintf

Для тоншого контролю над складанням рядків тексту R запозичує функцію sprintf з мови C (і з кількох пізніших мов: Вікіпедія наводить близько 30).

r <- 5.3
pi <- 3.14159
sprintf("A circle of radius %.1f has area %.2f", r, pi * r^2)
#> [1] "A circle of radius 5.3 has area 88.25"
trimws

Провідні та/або кінцеві пробіли можна прибрати за допомогою trimws():

s <- "    messy string   "
trimws(s)
#> [1] "messy string"

Є параметр which = "left" (або "right"), щоб не обрізати обидва кінці.

substr

Працює з одним рядком тексту й повертає підрядок між двома межами (включно).

substr("Exercism", 5, 7)
#> [1] "cis"
strsplit

Розбиває рядок тексту на список R за заданим рядком-роздільником. Роздільник обовʼязковий (типового значення немає), але може бути порожнім рядком тексту.

strsplit("R, Python, Julia", ", ")
[[1]]
#> [1] "R"      "Python" "Julia" 

strsplit("Exercism", "")
[[1]]
#> [1] "E" "x" "e" "r" "c" "i" "s" "m"

Списки розглянемо в окремому концепті. Якщо ми ще не дійшли до цієї частини силабусу, поки що досить знати, що функція unlist() (у цьому випадку) перетворить список на вектор.

unlist(strsplit("Exercism", ""))
#> [1] "E" "x" "e" "r" "c" "i" "s" "m"

Tidyverse

Якщо бути поблажливими, можна сказати, що функції для рядків тексту старого зразка - не найкраще, що може запропонувати R (існують і менш поблажливі думки).

Щоб виправити ситуацію, бібліотека stringr розробляється з 2009 року і тепер є ключовою частиною збірки Tidyverse.

Імпортуймо її у свій простір імен за допомогою library(stringr) або library(tidyverse).

Ось деякі з ключових можливостей:

  • Усі назви функцій мають префікс str_ і намагаються зробити призначення зрозумілим.
  • Порядок аргументів намагається бути самоузгодженим, а вхідні дані стоять першим аргументом.
  • Вхідні дані можуть бути від одного рядка тексту до складних структур даних. Функції намагаються підлаштуватися й зробити найрозумніше.

Як і всі пакети Tidyverse, stringr має хорошу документацію, яку зазвичай пишуть самі автори пакета.

Відступ: стислий вступ до конвеєрів

Використання конвеєрів |> для зʼєднання функцій детальніше розглянемо в інших частинах силабусу.

Поки що корисно знати ось що:

  • Результат ліворуч від конвеєра стає першим аргументом функції праворуч (де «праворуч» ігнорує необовʼязкові перенесення рядків).
  • Будь-які інші аргументи можна додати у виклик функції.
  • У R ми маємо спрямовувати конвеєр на виклик функції, а не лише на її назву, тож дужки обовʼязкові завжди (це відрізняється від деяких інших мов).

Приклад нижче ілюструє синтаксис. Окремі функції розглянемо пізніше.

library(stringr)

"Monday-25-February" |> 
  str_split("-") |>     
  unlist() |>               # [1] "Monday"   "25"       "February"
  str_sub(1, 3) |>          # [1] "Mon" "25"  "Feb"
  str_to_upper() |>         # [1] "MON" "25"  "FEB"
  str_flatten(collapse = ", ")
#> [1] "MON, 25, FEB"
Керування довжинами

str_length рахує кодові точки (подібно до nchars).

Різні інші функції працюють із пробілами, зокрема str_trim() і str_squish(). Це часто важливе очищення на початку будь-якого проєкту з аналізу даних.

# The first 3 kernels in JuPyteR notebooks:
str_length(c("Julia", "Python", "R"))           #> [1] 1 6 5
#> [1] 5 6 1
str_pad(c("Julia", "Python", "R"), 8, "right")  # add spaces if necessary
#> [1] "Julia   " "Python  " "R       "

str_trim("Julia    Python    R    ")   # remove leading & trailing whitespace
#> [1] "Julia    Python    R"
str_squish("Julia   Python  R    ")  # trim, collapse multiple spaces 
#> [1] "Julia Python R"
Вибір підрядків

Функція str_sub(string, start, end) поверне підрядок від start до end (включно), а str_sub_all() може працювати з кількома рядками тексту.

Тут багато гнучкості. start і end типово вказують на перший і останній символ відповідно. Вони можуть бути векторами індексів. На приємну відміну від індексації векторів, відʼємні значення відраховуються від кінця рядка тексту, як у Python.

s <- "abcdefgh"
str_sub(s, 2, 4)
#> [1] "bcd"
str_sub(s, 5)
#> [1] "efgh"
str_sub(s, -3, -2)
#> [1] "fg"
str_sub(s, c(1, 4), c(2, 5))
#> [1] "ab" "de"
str_sub(s, c(1, 4, 6), c(2, 5, -1))
#> [1] "ab"  "de"  "fgh"

s |> str_sub(2, 4) # these functions are designed for pipes
#> [1] "bcd"
Зʼєднання рядків тексту

str_flatten перетворює вектор символів на один рядок тексту. Аргумент collapse вставляється між елементами, а типове його значення - "".

c("R", "Julia", "Python") |> str_flatten()
#> [1] "RJuliaPython"
c("R", "Julia", "Python") |> str_flatten(collapse = " - ")
#> [1] "R - Julia - Python"

str_c перетворює кілька векторів символів на один вектор символів, з повторенням елементів за потреби.

str_c(LETTERS[1:8], 1:8, sep = ":")
#> [1] "A:1" "B:2" "C:3" "D:4" "E:5" "F:6" "G:7" "H:8"

str_glue приносить у R просту інтерполяцію рядків. Вирази у фігурних дужках { } обчислюються, а замість них підставляється їхній текстовий запис.

r <- 5.3
pi <- 3.14159
str_glue("A circle of radius {r} has area {pi * r^2}")
A circle of radius 5.3 has area 88.2472631
Розбиття рядків тексту

Дуже часте застосування R - приводити неохайні дані до єдиного формату для аналізу. Щоб допомогти з цим, є кілька способів розбити текст на підрядки. Більшість із них виходять за межі цього матеріалу.

str_split_1 - найпростіший: він перетворює один рядок тексту на вектор частин, розбиваючи його за вказаним шаблоном (типового шаблону немає).

s <- "R Julia Python"
str_split_1(s, " ")
#> [1] "R"      "Julia"  "Python"
Зміна рядків тексту

Деякі мови (переважно європейські) розрізняють великі та малі літери. У stringr є набір функцій, щоб перетворювати їх одне на одного з урахуванням локалі:

  • str_to_lower()
  • str_to_upper()
  • str_to_title() : перша літера кожного слова - у верхній регістр
  • str_to_sentence() : перша літера речення - у верхній регістр
str_to_upper("elixir")    # defaults to locale = "en"
#> [1] "ELIXIR"
str_to_upper("ελληνικά")  # infers Greek
#> [1] "ΕΛΛΗΝΙΚΆ"

str_to_title("the cat sat on the mat")
#> [1] "The Cat Sat On The Mat"
str_to_sentence("the cat sat on the mat")
#> [1] "The cat sat on the mat"

Для заміни підрядків за збігом із шаблоном str_replace() змінює перше входження шаблону, а str_replace_all() - усі входження.

Для заміни підрядків за індексами символів є форма присвоєння для str_sub().

 s <- "abcde"
str_sub(s, 3, 4) <- "yz"
s
#> [1] "abyze"

Вказівки

У цій вправі ми обробляємо рядки логу.

Кожен рядок логу - це рядок тексту (англ. string), відформатований так: "[<LEVEL>]: <MESSAGE>".

Існує три різні рівні логу:

  • INFO
  • WARNING
  • ERROR

На нас чекають три завдання, і в кожному потрібно взяти рядок логу та щось із ним зробити.

Можна вільно використовувати як застарілі функції з базового R, так і бібліотеку stringr, і обидва підходи дають короткі робочі рішення.

1. Отримати повідомлення з рядка логу

Реалізуйте функцію message, яка повертає повідомлення з рядка логу:

message("[ERROR]: Invalid operation")
#> "Invalid operation"

Будь-які пробільні символи на початку й у кінці рядка потрібно прибрати:

message("[WARNING]:  Disk almost full\r\n")
#> "Disk almost full"

2. Отримати рівень логу з рядка логу

Реалізуйте функцію log_level, яка повертає рівень логу з рядка логу; рівень має бути в нижньому регістрі:

log_level("[ERROR]: Invalid operation")
#> "error"

3. Переформатувати рядок логу

Реалізуйте функцію reformat, яка переформатовує рядок логу, ставлячи повідомлення першим, а рівень логу після нього в дужках:

reformat("[INFO]: Operation completed")
#> "Operation completed (info)"
Редагувати через GitHub Посилання відкривається в новому вікні або вкладці
R Exercism

Час розпочати Рівні журналу?

Зареєструйтеся на Exercism, щоб вивчати й опановувати R, а також 21 концепція111 вправ та справжнє наставництво від людей, і все це безкоштовно.