Skip to main content

Рядки та Символи

Ми вже розглядали рядки як вбудований тип даних, тепер же прийшов час розібрати їх більш детально. Що ж таке рядок?

info

Рядок – це структура, що має деяку послідовність (набір) символів, які можуть бути буквами, числами, знаками, тощо.

Тобто за аналогією з діапазонами, де інтервал – це по суті набір чисел між вказаною відстанню чисел, рядок – це деякий набір (список, послідовність) символів, які утворюють рядок. Тобто:

val string: String = "abcdef123"

Це набір (послідовність) з наступних символів: a, b, c, d, e, f, 1, 2, 3.

Варто знати

До речі, для символів існує окремий тип – і це Char. Взагалі, він може існувати й без рядкаЖ

val character: Char = 'a'

На відміну від рядка, вказуємо ми символ за допомогою одинарних лапок '.

Але навіщо це вам? Вирішім наступне завдання:

завдання

Користувач вводить своє ім'я, перевірте, чи перший буква з великої та виведіть відповідний результат.

І тут постає два питання: як нам перевірити саме перший символ та за допомогою чого. Відповідаю на ваші запитання.

Index оператор

Для того, щоб отримати нам деякий конкретний символ з рядка, ми використовуємо index-оператор. Але що це таке?

Термінологія

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

Використання:

val string = "123"
println(string[0]) // виведе 1

Тобто, у нас є послідовність деяких символів, що ввів користувач, і за допомогою цього оператору послідовність[індекс] ми отримуємо відповідний індексу елемент.

увага

Важливо враховувати, що індекс (тобто порядковий номер) будь-якої послідовності завжди починається з нуля. Будь-те обережні, щоб запобігти помилок, де ви отримуєте елемент, що не існує:

val string = "123"
println(string[3]) // такого елемента не існує

У даному прикладі, ми намагаємось отримати четвертий елемент нашої послідовності, хоча його не існує.

Тож, щоб отримати першу букву з введеного користувачем ім'я ми робимо наступне:

fun main() {
println("Введіть ім'я: ")
val name = readln()
println(name[0])
}

І при введенні ми отримаємо потрібний нам результат:

Введіть ім'я:
> василь
в

Але як же нам перевірити чи є перший символ великою буквою?

Вбудовані функції

isUpperCase()

Для того, щоб перевірити, чи є символ великою буквою, ми можемо скористатись наступною функцією – Char.isUpperCase(). Тобто, наступним чином:

fun main() {
println("Введіть ім'я: ")
val name = readln()
println(
if(name[0].isUpperCase())
"Перша буква з великої"
else "Перша буква з маленької"
)
}

Нам виведе:

Введіть ім'я:
> василь
Перша буква з маленької

slice, substring, indexOf

Розберім ще не менш важливі функції: slice та indexOf. Вирішім наступне завдання:

Завдання

Користувач вводить своє ім'я та прізвище через пробіл. Виведіть окремо ім'я та прізвище.

Щоб вирішити це завдання, перш за все, нам потрібно зрозуміти – а як саме нам отримати з одного рядка ім'я та прізвище?

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

fun main() {
println(getFirstName("Вадим Ярощук"))
}

/**
* Функція, що отримує ім'я з рядка.
* Параметр [string] – рядок з якого отримуємо ім'я.
*
*/
fun getFirstName(string: String): String {
return collectString(string, toIndex = indexOf(string, ' '))
}

/**
* Отримує індекс першого елемента з [string], який відповідає [symbol].
* Параметр [fromIndex] – вказує, з якого індекса починати перевірку на відповідність.
*
* З кожним викликом функції, якщо елемент за індексом [fromIndex] не відповідає [symbol],
* [fromIndex] збільшується на один, щоб перевірити наступний елемент при наступному повторенні.
*/
fun indexOf(string: String, symbol: Char, fromIndex: Int = 0): Int {
val nextIndex = fromIndex + 1

return if(string[fromIndex] == symbol)
fromIndex - 1
else indexOf(string, symbol, nextIndex)
}

/**
* Отримує рядок з рядка [string] з початкового елемента за індекcом [fromIndex]
* до елемента за індексом [toIndex].
* Отримується за допомогою рекурсії.
*
* Параметр [string] – це оригінальний рядок, з якого будемо отримувати елементи (символи).
* Параметр [tempString] – це тимчасовий рядок задіяний в рекурсії, який збирає символи
* при кожному виклику функції (отримує рядок з [fromIndex], який збільшується з кожним викликом)
* Параметр [fromIndex] – початковий індекс, з якого будемо отримувати рядок
* Параметр [toIndex] – кінцевий індекс, до якого буде продовжуватись рекурсія.
*/
fun collectString(string: String, tempString: String = "", fromIndex: Int = 0, toIndex: Int): String {
val result = tempString + string[fromIndex]

return if(fromIndex == toIndex)
result
else collectString(string, result, fromIndex + 1, toIndex)
}
Корисно знати

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

Також використовуються іменовані параметри (коли при виклику функції, ти вказуєш ім'я параметру), тобто: foo(parameterName = "smth"). Використовується, наприклад, коли є параметри за замовчуванням, щоб їх пропускати ( бо якщо продовжувати перелік, нам знадобиться задати навіть параметер за замовчуванням).

Наш алгоритм отримання індексу пробіла наступний – беремо елемент за його індексом (початковий у нас 0) → перевіряємо, чи цей елемент (символ) за цим індексом є пробілом → якщо ні, йдемо далі (індекс + 1), якщо так, закінчуємо.

Після чого переходимо до отримання всіх символів до отриманого індексу, за наступним алгоритмом: отримуємо елемент за вказаним початковим індексом fromIndex (початковий – це нуль) → перевіряємо чи цей індекс не більше кінцевого індексу toIndex → якщо так, то повертаємо tempString (цей аргумент з кожним повторенням поповнювався по одному символу), якщо ні, то викликаємо всередині ту ж функцію додаючи поточний елемент (символ) у tempString.

Можете погратись з цим кодом тут.

Але, розбивати це на рекурсію трішки заскладно, чи не так? Згадуючи тему про цикли ми можемо переробити це на цикли. Наприклад, переробимо indexOf:

fun indexOf(string: String, symbol: Char) {
for(index in 0..name.length) {
if(name[index] == symbol)
return index
}

return -1 // немає такого символа в рядку
}

Виглядає більш простіше, чи не так? Насправді це можна ще спростити за допомогою наступного факту:

Важливо знати

Рядок, як і діапазони (прогресії) також має ітератор. Що дозволяє нам зробити наступне:

fun indexOf(string: String, symbol: Char) {
for(char in string) {
if(char == symbol)
return index
}

return -1 // немає такого символа в рядку
}

Тобто, тепер замість числа в змінній перед in у нас число (забігаючи наперед, там будуть різні дані в залежності типів).

Як домашнє завдання залишаю вам переробити сollectString на цикл самотужки.

Але, взагалі, подібні функції вже існують в Kotlin:

indexOf

Щоб отримати порядковий номер (індекс) потрібного нам елементу, ми використовуємо вже вбудовану в мову функцію indexOf:

fun main() {
val name = "Vadim Yaroschuk"
println("Індекс пробілу: ${name.indexOf(' ')}")
}

У нас виведе наступне:

Індекс пробілу: 5

Таким чином ми вже знайшли механізм того, як знайти пробіл, тепер дізнаймось про функцію яка «обріже» наш рядок до потрібного нам.

slice / substring

Для того, щоб обрізати рядок ми використовуємо функцію slice або substring. Ці функції відрізняються лише тим, що slice копіює рядок у вказаному діапазоні, а substring використовує вже створений рядок за допомогою внутрішнього механізму.

Тож буде так:

val fullName = "Vadim Yaroschuk"
val firstName = fullName.slice(0..fullName.indexOf(" ") - 1)
println("Ім'я користувача: $firstName")

До речі, для substring є деякі функції спрощення, наприклад:

  • substringAfter(Char) – обрізає рядок від першого Сhar (не включно) до кінця рядка
  • substringBefore(Char) – обрізає рядок до першого відповідного Сhar (не включно)
  • substringAfterLast(Char) – обрізає рядок від останнього відповідного Сhar (не включно) до кінця рядка
  • substringBeforeLast(Char) – обрізає рядок до останнього відповідного Сhar (не включно)

Тобто, в нашому випадку це буде так:

val fullName = "Vadim Yaroschuk"
val firstName = fullName.substringBefore(' ')
val surname = fullName.substringAfter(' ')
println("Ім'я користувача: $firstName")
println("Прізвище користувача: $surname")

Виведе наступне:

Ім'я користувача: Vadim
Прізвище користувача: Yaroschuk

startsWith, endsWith

Перейдімо до також важливих функцій перевірки рядка: startsWith та endsWith. Щоб зрозуміти для чого вони використовуються, вирішимо наступне завдання:

завдання

Користувач вводить посилання на будь-який файл. Визначте, чи є посилання безпечним (перевіривши чи є з'єднання https://) та чи є файл веб-сторінкою (.html). Наприклад:

> http://foo.bar/index.html
З'єднання не є безпечним.
Файл є веб-сторінкою.

Для цього нам знадобляться ці функції. Як вже зрозуміло за назвою: startsWith – перевіряє рядок, чи рядок починається на якийсь довільний рядок, а endsWith – чи рядок закінчується на довільний рядок.

Тож, для вирішення нашої задачі, використаємо ці функції:

fun main() {
val link = readln()

if(link.startsWith("https://"))
println("З'єднання є безпечним.")
else println("З'єднання не є безпечним.")

if(link.endsWith(".html"))
println("Файл є веб-сторінкою.")
println("Файл не є веб-сторінкою")
}

Все дуже просто!

Оператор in (contains)

А тепер перейдім до не менш важливого оператору in (contains, містить українською) – якщо попередні дві функції перевіряли початок та кінець рядка, то даний оператор перевіряє увесь рядок на присутність вказаного підрядка. Тобто:

fun main() {
val bio = "I am Kotlin developer"
if("Kotlin" in bio)
println("Kotlin присутній в рядку")
else println("Kotlin не присутній в рядку")
}
caution

Ви маєте бути обережними та не переплутати порядок:

fun main() {
val bio = "I am Kotlin developer"
if(bio in "Kotlin")
println("Kotlin присутній в рядку")
...
if("Kotlin" in bio)
println("Kotlin присутній в рядку")
...

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

fun main() {
val bio = "I am Kotlin developer"
if(bio.contains("Kotlin"))
println("Kotlin присутній в рядку")
else println("Kotlin не присутній в рядку")
}

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

Завдання

Для того, щоб закріпити матеріл, пропоную наступні для вирішення завдання:

завдання №1

Створіть функції відповідники до substring (substringAfter, subStringAfterLast, substringBefore, substringBeforeLast) та за допомогою них дізнайтесь з повного користувацького ПІБ окремо ім'я, прізвище, та ім'я по-батькові.

завдання №2

Користувач вводить рядок з довільним текстом. Перевірте рядок на присутність будь-якої лайки (на ваш розсуд).

завдання №3

Користувач вводить рядок з розташуванням файлу. Дізнайтесь, який саме тип файлу знаходиться там (картинка, відео, тощо), враховуйте різні формати одного типу файла.