-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
359 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,345 @@ | ||
--- | ||
keywords: [kotlin string, strings, рядки, строка, isUpperCase, slice, substring, indexOf, для новачків, для початківців, туторіал] | ||
--- | ||
import Tabs from '@theme/Tabs'; | ||
import TabItem from '@theme/TabItem'; | ||
import CodeBlock from '@theme/CodeBlock'; | ||
|
||
# Рядки та Символи | ||
Ми вже розглядали рядки як вбудований тип даних, тепер же прийшов час розібрати їх більш детально. Що ж таке рядок? | ||
:::info | ||
**Рядок** – це структура, що має деяку послідовність (набір) символів, які можуть бути буквами, числами, знаками, тощо. | ||
::: | ||
Тобто за аналогією з [діапазонами](../../kotlin/basics/cycles-and-recursions#for), де інтервал – це по суті набір чисел | ||
між вказаною відстанню чисел, рядок – це деякий набір (список, послідовність) символів, які утворюють рядок. Тобто: | ||
```kotlin | ||
val string: String = "abcdef123" | ||
``` | ||
Це набір (послідовність) з наступних символів: *a*, *b*, *c*, *d*, *e*, *f*, *1*, *2*, *3*. | ||
|
||
:::tip Варто знати | ||
До речі, для символів існує окремий тип – і це `Char`. Взагалі, він може існувати й без рядкаЖ | ||
```kotlin | ||
val character: Char = 'a' | ||
``` | ||
На відміну від рядка, вказуємо ми символ за допомогою одинарних лапок `'`. | ||
::: | ||
|
||
Але навіщо це вам? Вирішім наступне завдання: | ||
:::info завдання | ||
Користувач вводить своє ім'я, перевірте, чи перший буква з великої та виведіть відповідний результат. | ||
::: | ||
І тут постає два питання: як нам перевірити саме перший символ та за допомогою чого. Відповідаю на ваші запитання. | ||
|
||
## Index оператор | ||
Для того, щоб отримати нам деякий конкретний символ з рядка, ми використовуємо index-оператор. Але що це таке? | ||
:::info Термінологія | ||
**Індекс оператор** – це оператор, що отримує з вказаної послідовності (набору, списку) певний елемент за | ||
його порядковим номером (переважно його називають індексом). | ||
|
||
Використання: | ||
```kotlin | ||
val string = "123" | ||
println(string[0]) // виведе 1 | ||
``` | ||
::: | ||
Тобто, у нас є послідовність деяких символів, що ввів користувач, і за допомогою цього оператору `послідовність[індекс]` | ||
ми отримуємо відповідний індексу елемент. | ||
:::danger увага | ||
Важливо враховувати, що індекс (тобто порядковий номер) будь-якої послідовності завжди **починається з нуля**. | ||
Будь-те обережні, щоб запобігти помилок, де ви отримуєте елемент, що не існує: | ||
```kotlin | ||
val string = "123" | ||
// This will error | ||
println(string[3]) // такого елемента не існує | ||
``` | ||
У даному прикладі, ми намагаємось отримати четвертий елемент нашої послідовності, хоча його не існує. | ||
::: | ||
Тож, щоб отримати першу букву з введеного користувачем ім'я ми робимо наступне: | ||
```kotlin | ||
fun main() { | ||
println("Введіть ім'я: ") | ||
val name = readln() | ||
println(name[0]) | ||
} | ||
``` | ||
І при введенні ми отримаємо потрібний нам результат: | ||
```text | ||
Введіть ім'я: | ||
> василь | ||
в | ||
``` | ||
Але як же нам перевірити чи є перший символ великою буквою? | ||
## Вбудовані функції | ||
### isUpperCase() | ||
Для того, щоб перевірити, чи є символ великою буквою, ми можемо скористатись наступною функцією – `Char.isUpperCase()`. | ||
Тобто, наступним чином: | ||
```kotlin | ||
fun main() { | ||
println("Введіть ім'я: ") | ||
val name = readln() | ||
println( | ||
if(name[0].isUpperCase()) | ||
"Перша буква з великої" | ||
else "Перша буква з маленької" | ||
) | ||
} | ||
``` | ||
Нам виведе: | ||
```text | ||
Введіть ім'я: | ||
> василь | ||
Перша буква з маленької | ||
``` | ||
### slice, substring, indexOf | ||
Розберім ще не менш важливі функції: `slice` та `indexOf`. Вирішім наступне завдання: | ||
:::info Завдання | ||
Користувач вводить своє ім'я та прізвище через пробіл. Виведіть окремо ім'я та прізвище. | ||
::: | ||
Щоб вирішити це завдання, перш за все, нам потрібно зрозуміти – а як саме нам отримати з одного рядка ім'я та прізвище? | ||
|
||
Якщо обходитись без цих функцій, ми можемо придумати наступне: а зробім лінійний (один за одним) | ||
перебір символів за їх індексом. Тобто наступне буде для ім'я (не лякайтесь та розберіться, | ||
тут все не так складно, як може здатись, на перший погляд): | ||
```kotlin | ||
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) | ||
} | ||
``` | ||
:::tip Корисно знати | ||
У даному прикладі, аргументи (параметри) функцій мають параметри за замовчуванням. Щоб зробити подібне, | ||
все що вам потрібно, це до параметра додати дорівнює та значення яке вам потрібне за замовчуванням. | ||
|
||
Також використовуються іменовані параметри (коли при виклику функції, ти вказуєш ім'я параметру), тобто: | ||
`foo(parameterName = "smth")`. Використовується, наприклад, коли є параметри за замовчуванням, щоб їх пропускати ( | ||
бо якщо продовжувати перелік, нам знадобиться задати навіть параметер за замовчуванням). | ||
::: | ||
Наш алгоритм отримання індексу пробіла наступний – беремо елемент за його індексом (початковий у нас 0) → | ||
перевіряємо, чи цей елемент (символ) за цим індексом є пробілом → якщо ні, йдемо далі (індекс + 1), | ||
якщо так, закінчуємо. | ||
|
||
Після чого переходимо до отримання всіх символів до отриманого індексу, за наступним алгоритмом: отримуємо | ||
елемент за вказаним початковим індексом `fromIndex` (початковий – це нуль) → перевіряємо чи цей індекс не більше | ||
кінцевого індексу `toIndex` → якщо так, то повертаємо `tempString` (цей аргумент з кожним повторенням поповнювався | ||
по одному символу), якщо ні, то викликаємо всередині ту ж функцію додаючи поточний елемент (символ) у `tempString`. | ||
|
||
Можете погратись з цим кодом [тут](https://pl.kotl.in/gJLhX-ZBe). | ||
|
||
Але, розбивати це на рекурсію трішки заскладно, чи не так? Згадуючи тему про [цикли](../../kotlin/basics/cycles-and-recursions#for) | ||
ми можемо переробити це на цикли. Наприклад, переробимо `indexOf`: | ||
```kotlin | ||
fun indexOf(string: String, symbol: Char) { | ||
for(index in 0..name.length) { | ||
if(name[index] == symbol) | ||
return index | ||
} | ||
|
||
return -1 // немає такого символа в рядку | ||
} | ||
``` | ||
Виглядає більш простіше, чи не так? Насправді це можна ще спростити за допомогою наступного факту: | ||
:::tip Важливо знати | ||
**Рядок**, як і діапазони (прогресії) **також має ітератор**. Що дозволяє нам зробити наступне: | ||
```kotlin | ||
fun indexOf(string: String, symbol: Char) { | ||
for(char in string) { | ||
if(char == symbol) | ||
return index | ||
} | ||
|
||
return -1 // немає такого символа в рядку | ||
} | ||
``` | ||
Тобто, тепер замість числа в змінній перед `in` у нас число (забігаючи наперед, там будуть різні дані в залежності | ||
типів). | ||
::: | ||
Як домашнє завдання залишаю вам переробити `сollectString` на цикл самотужки. | ||
|
||
Але, взагалі, подібні функції вже існують в Kotlin: | ||
#### indexOf | ||
Щоб отримати порядковий номер (індекс) потрібного нам елементу, ми використовуємо вже вбудовану в мову функцію `indexOf`: | ||
```kotlin | ||
fun main() { | ||
val name = "Vadim Yaroschuk" | ||
println("Індекс пробілу: ${name.indexOf(' ')}") | ||
} | ||
``` | ||
У нас виведе наступне: | ||
```text | ||
Індекс пробілу: 5 | ||
``` | ||
Таким чином ми вже знайшли механізм того, як знайти пробіл, тепер дізнаймось про функцію яка «обріже» наш рядок | ||
до потрібного нам. | ||
#### slice / substring | ||
Для того, щоб обрізати рядок ми використовуємо функцію `slice` або `substring`. Ці функції відрізняються лише тим, | ||
що slice копіює рядок у вказаному діапазоні, а `substring` використовує вже створений рядок за допомогою внутрішнього | ||
механізму. | ||
|
||
Тож буде так: | ||
<Tabs> | ||
<TabItem value="slice" default> | ||
|
||
```kotlin | ||
val fullName = "Vadim Yaroschuk" | ||
val firstName = fullName.slice(0..fullName.indexOf(" ") - 1) | ||
println("Ім'я користувача: $firstName") | ||
``` | ||
|
||
</TabItem> | ||
<TabItem value="substring"> | ||
|
||
```kotlin | ||
val fullName = "Vadim Yaroschuk" | ||
val firstName = fullName.substring(0, fullName.indexOf(" ") - 1) | ||
println("Ім'я користувача: $firstName") | ||
``` | ||
|
||
</TabItem> | ||
</Tabs> | ||
|
||
До речі, для `substring` є деякі функції спрощення, наприклад: | ||
- `substringAfter(Char)` – обрізає рядок від першого Сhar (не включно) до кінця рядка | ||
- `substringBefore(Char)` – обрізає рядок до першого відповідного Сhar (не включно) | ||
- `substringAfterLast(Char)` – обрізає рядок від останнього відповідного Сhar (не включно) до кінця рядка | ||
- `substringBeforeLast(Char)` – обрізає рядок до останнього відповідного Сhar (не включно) | ||
|
||
Тобто, в нашому випадку це буде так: | ||
```kotlin | ||
val fullName = "Vadim Yaroschuk" | ||
val firstName = fullName.substringBefore(' ') | ||
val surname = fullName.substringAfter(' ') | ||
println("Ім'я користувача: $firstName") | ||
println("Прізвище користувача: $surname") | ||
``` | ||
Виведе наступне: | ||
```text | ||
Ім'я користувача: Vadim | ||
Прізвище користувача: Yaroschuk | ||
``` | ||
### startsWith, endsWith | ||
Перейдімо до також важливих функцій перевірки рядка: `startsWith` та `endsWith`. Щоб зрозуміти для чого вони | ||
використовуються, вирішимо наступне завдання: | ||
:::info завдання | ||
Користувач вводить посилання на будь-який файл. Визначте, чи є посилання безпечним (перевіривши чи є з'єднання | ||
`https://`) та чи є файл веб-сторінкою (`.html`). | ||
Наприклад: | ||
```text | ||
> http://foo.bar/index.html | ||
З'єднання не є безпечним. | ||
Файл є веб-сторінкою. | ||
``` | ||
Для цього нам знадобляться ці функції. Як вже зрозуміло за назвою: `startsWith` – перевіряє рядок, чи рядок починається | ||
на якийсь довільний рядок, а `endsWith` – чи рядок закінчується на довільний рядок. | ||
::: | ||
Тож, для вирішення нашої задачі, використаємо ці функції: | ||
```kotlin | ||
fun main() { | ||
val link = readln() | ||
|
||
if(link.startsWith("https://")) | ||
println("З'єднання є безпечним.") | ||
else println("З'єднання не є безпечним.") | ||
|
||
if(link.endsWith(".html")) | ||
println("Файл є веб-сторінкою.") | ||
println("Файл не є веб-сторінкою") | ||
} | ||
``` | ||
Все дуже просто! | ||
## Оператор `in` (contains) | ||
А тепер перейдім до не менш важливого оператору `in` (contains, містить українською) – якщо попередні дві функції | ||
перевіряли початок та кінець рядка, то даний оператор перевіряє увесь рядок на присутність вказаного підрядка. Тобто: | ||
```kotlin | ||
fun main() { | ||
val bio = "I am Kotlin developer" | ||
if("Kotlin" in bio) | ||
println("Kotlin присутній в рядку") | ||
else println("Kotlin не присутній в рядку") | ||
} | ||
``` | ||
:::caution | ||
Ви маєте бути обережними та не переплутати порядок: | ||
```kotlin | ||
fun main() { | ||
val bio = "I am Kotlin developer" | ||
// This will error | ||
if(bio in "Kotlin") | ||
// This will error | ||
println("Kotlin присутній в рядку") | ||
... | ||
// This is correct | ||
if("Kotlin" in bio) | ||
// This is correct | ||
println("Kotlin присутній в рядку") | ||
... | ||
``` | ||
::: | ||
Щоб не переплутати, ви можете використовувати функцію-відповідник до цього оператору (якщо ви пам'ятаєте, | ||
в темі про [оператори](../../kotlin/basics/operators.md), я розповідав, що функції можуть виражатись через функцію, | ||
і як раз для подібних речей іноді їх використовують): | ||
```kotlin | ||
fun main() { | ||
val bio = "I am Kotlin developer" | ||
if(bio.contains("Kotlin")) | ||
println("Kotlin присутній в рядку") | ||
else println("Kotlin не присутній в рядку") | ||
} | ||
``` | ||
Що буде трішки очевидніше, мабуть. Чітких правил щодо використань немає, хоча я зазвичай використовую `in`. | ||
## Завдання | ||
Для того, щоб закріпити матеріл, пропоную наступні для вирішення завдання: | ||
:::info завдання №1 | ||
Створіть функції відповідники до substring (substringAfter, subStringAfterLast, substringBefore, substringBeforeLast) | ||
та за допомогою них дізнайтесь з повного користувацького ПІБ окремо ім'я, прізвище, та ім'я по-батькові. | ||
::: | ||
:::info завдання №2 | ||
Користувач вводить рядок з довільним текстом. Перевірте рядок на присутність будь-якої лайки (на ваш розсуд). | ||
::: | ||
:::info завдання №3 | ||
Користувач вводить рядок з розташуванням файлу. Дізнайтесь, який саме тип файлу знаходиться там (картинка, відео, тощо), | ||
враховуйте різні формати одного типу файла. | ||
::: |
Oops, something went wrong.