diff --git a/README.md b/README.md index 73dc637ea..e4b1c5789 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Котлин как первый язык программирования +щ# Котлин как первый язык программирования Решите приведённые в проекте задачи, чтобы научиться программировать на Котлине. Сейчас доступны двенадцать групп задач (уроков). @@ -11,7 +11,7 @@ 7. Работа с файлами. 8. Простые классы. 9. Сложные классы на примере матриц. -10. Cинтаксический разбор. +10. Доп. главы (синтаксический разбор, Kotlin DSL). 11. Классы с арифметическими операциями. 12. Классы-контейнеры. @@ -41,7 +41,7 @@ Руководство по настройке среды программирования: -* http://kspt.icc.spbstu.ru/media/files/2018/kaf/IdeaConfig.pdf +* http://kspt.icc.spbstu.ru/media/files/2021/kaf/KotlinAsFirstConfig.pdf Задачи по алгоритмам (Java/Kotlin): diff --git a/howto.md b/howto.md new file mode 100644 index 000000000..7a60e9135 --- /dev/null +++ b/howto.md @@ -0,0 +1,15 @@ +1 ПОРЯДОК ДЕЙСТВИЙ +2 git remote add upstream-my https://git@github.com/uphoros/KotlinAsFirst2021.git +3 git checkout -b backport +4 затем я в идее черрипикнул все свои коммиты в бранч backport и запушил изменения +5 git remote add upstream-theirs https://git@github.com/elisananeva/KotlinAsFirst2021.git (своим партнером я выбрал Степанову Елизавету) +6 git checkout master +7 git merge upstream-theirs/master +8 git merge origin/backport --strategy-option ours (мои коммиты в приоритете) +9 touch "howto.md" +10 git add "howto.md" +11 git commit -m "file howto.md" +12 touch "remotes.md" +13 git add "remotes.md" +14 git commit -m "file remote.md" +15 git push с токеном diff --git a/input/football.txt b/input/football.txt new file mode 100644 index 000000000..cc4a009f4 --- /dev/null +++ b/input/football.txt @@ -0,0 +1,4 @@ +0 W W D - 1 +L 0 W D - 2 +L L 0 W - 3 +D D L 0 - 4 \ No newline at end of file diff --git a/input/football2.txt b/input/football2.txt new file mode 100644 index 000000000..3108eb9e0 --- /dev/null +++ b/input/football2.txt @@ -0,0 +1,4 @@ +0 W W D - 1 +W 0 W D - 2 +L L 0 W - 3 +D D L 0 - 4 \ No newline at end of file diff --git a/input/govno.txt b/input/govno.txt new file mode 100644 index 000000000..7107cfb7a --- /dev/null +++ b/input/govno.txt @@ -0,0 +1,4 @@ +задание1 -- важность 2, 45 мин +задание2 -- важность 2, 75 мин +задание3 -- важность 3, 30 мин +задание4 -- важность 1, 45 мин diff --git a/input/govno2.txt b/input/govno2.txt new file mode 100644 index 000000000..76e69317d --- /dev/null +++ b/input/govno2.txt @@ -0,0 +1,4 @@ +задание1 -- важность 2, 75 мин +задание2 -- важность 2, 75 мин +задание3 -- важность 3, 30 мин +задание4 -- важность 1, 75 мин diff --git a/input/html1.txt b/input/html1.txt new file mode 100644 index 000000000..f14fce979 --- /dev/null +++ b/input/html1.txt @@ -0,0 +1,3 @@ + = Header1 + === Header3 + == Header2 \ No newline at end of file diff --git a/input/intersec.txt b/input/intersec.txt new file mode 100644 index 000000000..98ef7d038 --- /dev/null +++ b/input/intersec.txt @@ -0,0 +1,3 @@ +A = -1, 5, 43, 938, 202 +B = 50, 49, 5, -1, -90, 202 +C = 239, 928, 23, 445 \ No newline at end of file diff --git a/input/intersec2.txt b/input/intersec2.txt new file mode 100644 index 000000000..7249fcd9a --- /dev/null +++ b/input/intersec2.txt @@ -0,0 +1,3 @@ +A = -1, 5, 43, 938, 202 +B = 50, 49, 5, -1, -90, 202 +C = 239, dsuf, 928, 23.1 \ No newline at end of file diff --git a/input/racing.txt b/input/racing.txt new file mode 100644 index 000000000..8e9ad0654 --- /dev/null +++ b/input/racing.txt @@ -0,0 +1,5 @@ +21fsf dgfv +one, team1, 10 +one, teamMM, 18 +two, team1, 6 +three, team2, 4 \ No newline at end of file diff --git a/pom.xml b/pom.xml index 52fde3281..09e5f1cfa 100644 --- a/pom.xml +++ b/pom.xml @@ -6,22 +6,27 @@ ru.spbstu kfirst - 20.0.0 + 21.0.0 kfirst - bintray-vorpal-research-kotlin-maven + github-kotlin-polytech-kotlin-maven + kotlin-polytech-kotlin-maven + https://maven.polytech.vorpal-research.science + + + github-vorpal-research-kotlin-maven vorpal-research-kotlin-maven - http://dl.bintray.com/vorpal-research/kotlin-maven + https://maven.vorpal-research.science ru.spbstu kfirst-bom - 20.0.0 + 21.0.0 @@ -29,7 +34,7 @@ kfirst-runner-plugin org.jetbrains.research - 20.0.0 + 21.0.0 lesson1.task1 diff --git a/remotes.md b/remotes.md new file mode 100644 index 000000000..c6b97178b --- /dev/null +++ b/remotes.md @@ -0,0 +1,6 @@ +origin https://github.com/uphoros/KotlinAsFirst2020.git (fetch) +origin https://github.com/uphoros/KotlinAsFirst2020.git (push) +upstream-my https://git@github.com/uphoros/KotlinAsFirst2021.git (fetch) +upstream-my https://git@github.com/uphoros/KotlinAsFirst2021.git (push) +upstream-theirs https://git@github.com/elisananeva/KotlinAsFirst2021.git (fetch) +upstream-theirs https://git@github.com/elisananeva/KotlinAsFirst2021.git (push) diff --git a/src/lesson1/task1/Simple.kt b/src/lesson1/task1/Simple.kt index 57d2a0f46..fbb8bf666 100644 --- a/src/lesson1/task1/Simple.kt +++ b/src/lesson1/task1/Simple.kt @@ -2,6 +2,7 @@ package lesson1.task1 + import kotlin.math.* // Урок 1: простые функции @@ -65,7 +66,7 @@ fun main() { * Пользователь задает время в часах, минутах и секундах, например, 8:20:35. * Рассчитать время в секундах, прошедшее с начала суток (30035 в данном случае). */ -fun seconds(hours: Int, minutes: Int, seconds: Int): Int = TODO() +fun seconds(hours: Int, minutes: Int, seconds: Int): Int = (hours * 3600) + (minutes * 60) + seconds /** * Тривиальная (1 балл) @@ -98,7 +99,7 @@ fun trackLength(x1: Double, y1: Double, x2: Double, y2: Double): Double = TODO() * Пользователь задает целое число, большее 100 (например, 3801). * Определить третью цифру справа в этом числе (в данном случае 8). */ -fun thirdDigit(number: Int): Int = TODO() +fun thirdDigit(number: Int): Int = ((number % 1000) - ((number % 1000) % 100)) / 100 /** * Простая (2 балла) diff --git a/src/lesson10/task2/Html.kt b/src/lesson10/task2/Html.kt index 0761ad563..2da967451 100644 --- a/src/lesson10/task2/Html.kt +++ b/src/lesson10/task2/Html.kt @@ -1,3 +1,5 @@ +@file:Suppress("UNUSED_PARAMETER") + package lesson10.task2 import kotlinx.html.* @@ -62,6 +64,36 @@ fun generateSimpleHtml(s: String): String { return sb.toString() } +/** + * Средняя (5 баллов) + * + * Преобразовать заданный список строк в нумерованный список HTML. + * К примеру, из ["Alpha", "Beta", "Omega"] мы должны получить следующее + * + *
    + *
  1. Alpha
  2. + *
  3. Beta
  4. + *
  5. Omega
  6. + *
+ * + * + * В этом задании вы должны заменить на реальный код содержимое функций myList, myItem, unaryPlus + * и использовать их в функции generateSimpleHtml + * + * Пробелы и переводы строк между тегами в этом задании значения не имеют. + */ +fun generateListHtml(list: List): String { + val sb = StringBuilder() + sb.myHtml { + myBody { + myList { + TODO() + } + } + } + return sb.toString() +} + private class HTML(val sb: StringBuilder) { fun myBody(init: HTMLBody.() -> Unit): HTMLBody { val body = HTMLBody(sb) @@ -76,6 +108,21 @@ private class HTMLBody(val sb: StringBuilder) { operator fun String.unaryPlus() { sb.append(this) } + + fun myList(init: HTMLList.() -> Unit): HTMLList { + TODO() + } +} + +private class HTMLList(val sb: StringBuilder) { + fun myItem(init: HTMLItem.() -> Unit): HTMLItem { + TODO() + } +} + +private class HTMLItem(val sb: StringBuilder) { + operator fun String.unaryPlus() { + } } private fun StringBuilder.myHtml(init: HTML.() -> Unit): HTML { diff --git a/src/lesson11/task1/Complex.kt b/src/lesson11/task1/Complex.kt index edeaf8d36..74ab6b700 100644 --- a/src/lesson11/task1/Complex.kt +++ b/src/lesson11/task1/Complex.kt @@ -2,6 +2,11 @@ package lesson11.task1 +/** + * Фабричный метод для создания комплексного числа из строки вида x+yi + */ +fun Complex(s: String): Complex = TODO() + /** * Класс "комплексное число". * @@ -18,11 +23,6 @@ class Complex(val re: Double, val im: Double) { */ constructor(x: Double) : this(TODO(), TODO()) - /** - * Конструктор из строки вида x+yi - */ - constructor(s: String) : this(TODO(), TODO()) - /** * Сложение. */ diff --git a/src/lesson11/task1/UnsignedBigInteger.kt b/src/lesson11/task1/UnsignedBigInteger.kt index 170a8aa33..88f9bb170 100644 --- a/src/lesson11/task1/UnsignedBigInteger.kt +++ b/src/lesson11/task1/UnsignedBigInteger.kt @@ -1,3 +1,5 @@ +@file:Suppress("UNUSED_PARAMETER") + package lesson11.task1 /** diff --git a/src/lesson2/task1/IfElse.kt b/src/lesson2/task1/IfElse.kt index 777689ac9..152214d49 100644 --- a/src/lesson2/task1/IfElse.kt +++ b/src/lesson2/task1/IfElse.kt @@ -4,6 +4,8 @@ package lesson2.task1 import lesson1.task1.discriminant import kotlin.math.max +import kotlin.math.abs +import kotlin.math.min import kotlin.math.sqrt // Урок 2: ветвления (здесь), логический тип (см. 2.2). @@ -68,7 +70,12 @@ fun minBiRoot(a: Double, b: Double, c: Double): Double { * Мой возраст. Для заданного 0 < n < 200, рассматриваемого как возраст человека, * вернуть строку вида: «21 год», «32 года», «12 лет». */ -fun ageDescription(age: Int): String = TODO() +fun ageDescription(age: Int): String = when { + ((age % 10 == 1) && (age % 100 !in 10..20)) -> "$age год" + ((age % 10 in 2..4) && (age % 100 !in 10..20)) -> "$age года" + else -> "$age лет" + +} /** * Простая (2 балла) @@ -132,4 +139,7 @@ fun triangleKind(a: Double, b: Double, c: Double): Int = TODO() * Найти длину пересечения отрезков AB и CD. * Если пересечения нет, вернуть -1. */ -fun segmentLength(a: Int, b: Int, c: Int, d: Int): Int = TODO() +fun segmentLength(a: Int, b: Int, c: Int, d: Int): Int { + if ((b < c) || (d < a)) return -1 + return (min(d, b) - max(a, c)) +} \ No newline at end of file diff --git a/src/lesson2/task2/Logical.kt b/src/lesson2/task2/Logical.kt index 1e1c862b2..14abbf3c7 100644 --- a/src/lesson2/task2/Logical.kt +++ b/src/lesson2/task2/Logical.kt @@ -2,7 +2,9 @@ package lesson2.task2 + import lesson1.task1.sqr +import kotlin.math.min /** * Пример @@ -59,4 +61,12 @@ fun circleInside( * кирпич 4 х 4 х 4 пройдёт через отверстие 4 х 4. * Вернуть true, если кирпич пройдёт */ -fun brickPasses(a: Int, b: Int, c: Int, r: Int, s: Int): Boolean = TODO() +fun brickPasses(a: Int, b: Int, c: Int, r: Int, s: Int): Boolean { + val minBrick = minOf(a, b, c) + val maxBrick = maxOf(a, b, c) + if (minOf(a, b, c) > minOf(r, s)) return false + return ((a + b + c - maxBrick - minBrick) <= maxOf(r, s)) +} + + + diff --git a/src/lesson3/task1/Loop.kt b/src/lesson3/task1/Loop.kt index 99beabae0..6c74b920b 100644 --- a/src/lesson3/task1/Loop.kt +++ b/src/lesson3/task1/Loop.kt @@ -2,6 +2,8 @@ package lesson3.task1 +import kotlin.math.PI +import kotlin.math.abs import kotlin.math.sqrt // Урок 3: циклы @@ -72,7 +74,16 @@ fun digitCountInNumber(n: Int, m: Int): Int = * * Использовать операции со строками в этой задаче запрещается. */ -fun digitNumber(n: Int): Int = TODO() +fun digitNumber(n: Int): Int { + if (n == 0) return 1 + var a = n + var answer = 0 + while (a != 0) { + answer += 1 + a /= 10 + } + return answer +} /** * Простая (2 балла) @@ -94,7 +105,12 @@ fun minDivisor(n: Int): Int = TODO() * * Для заданного числа n > 1 найти максимальный делитель, меньший n */ -fun maxDivisor(n: Int): Int = TODO() +fun maxDivisor(n: Int): Int { + for (i in (n / 2) downTo sqrt((n * 1.0)).toInt() - 1) { + if ((n % i) == 0) return i + } + return 1 +} /** * Простая (2 балла) @@ -147,7 +163,17 @@ fun squareBetweenExists(m: Int, n: Int): Boolean = TODO() * * Использовать операции со строками в этой задаче запрещается. */ -fun revert(n: Int): Int = TODO() +fun revert(n: Int): Int { + var a = 0 + var n2 = n + while ((n2 / 10) >= 1) { + a += n2 % 10 + a *= 10 + n2 -= n2 % 10 + n2 = n2 / 10 + } + return a + n2 +} /** * Средняя (3 балла) @@ -201,7 +227,23 @@ fun cos(x: Double, eps: Double): Double = TODO() * * Использовать операции со строками в этой задаче запрещается. */ -fun squareSequenceDigit(n: Int): Int = TODO() +fun squareSequenceDigit(n: Int): Int { + var a = 0 + var l = 0 + while (l < n) { + a += 1 + l += digitNumber(a * a) + } + a *= a + if (l == n) return a % 10 + else { + for (i in 1..l - n) { + a -= a % 10 + a /= 10 + } + return a % 10 + } +} /** * Сложная (5 баллов) diff --git a/src/lesson4/task1/List.kt b/src/lesson4/task1/List.kt index ba2154c41..f73c92589 100644 --- a/src/lesson4/task1/List.kt +++ b/src/lesson4/task1/List.kt @@ -241,7 +241,19 @@ fun decimalFromString(str: String, base: Int): Int = TODO() * 90 = XC, 100 = C, 400 = CD, 500 = D, 900 = CM, 1000 = M. * Например: 23 = XXIII, 44 = XLIV, 100 = C */ -fun roman(n: Int): String = TODO() +fun roman(n: Int): String { + val dec = listOf(1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1) + val rom = listOf("M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I") + val answer = StringBuilder() + var a = n + for (i in dec.indices) { + while (a >= dec[i]) { + answer.append(rom[i]) + a -= dec[i] + } + } + return answer.toString() +} /** * Очень сложная (7 баллов) @@ -250,4 +262,92 @@ fun roman(n: Int): String = TODO() * Например, 375 = "триста семьдесят пять", * 23964 = "двадцать три тысячи девятьсот шестьдесят четыре" */ -fun russian(n: Int): String = TODO() \ No newline at end of file +fun russian(n: Int): String { + val list = listOf( + Pair(900, "девятьсот"), + Pair(800, "восемьсот"), + Pair(700, "семьсот"), + Pair(600, "шестьсот"), + Pair(500, "пятьсот"), + Pair(400, "четыреста"), + Pair(300, "триста"), + Pair(200, "двести"), + Pair(100, "сто"), + Pair(90, "девяносто"), + Pair(80, "восемьдесят"), + Pair(70, "семьдесят"), + Pair(60, "шестьдесят"), + Pair(50, "пятьдесят"), + Pair(40, "сорок"), + Pair(30, "тридцать"), + Pair(20, "двадцать"), + Pair(19, "девятнадцать"), + Pair(18, "восемнадцать"), + Pair(17, "семнадцать"), + Pair(16, "шестнадцать"), + Pair(15, "пятнадцать"), + Pair(14, "четырнадцать"), + Pair(13, "тринадцать"), + Pair(12, "двенадцать"), + Pair(11, "одиннадцать"), + Pair(10, "десять"), + Pair(9, "девять"), + Pair(8, "восемь"), + Pair(7, "семь"), + Pair(6, "шесть"), + Pair(5, "пять"), + Pair(4, "четыре"), + Pair(3, "три"), + Pair(2, "два"), + Pair(1, "один"), + Pair(0, ""), + ) + val rus = list.toMap() + var beg = n / 1000 + var end = n % 1000 + var answer = "" + if (end > 0) { + if ((end % 100) in 11..19) { + answer += rus.get(end % 100) + end -= end % 100 + if (end > 0) answer = rus.get(end) + " " + answer + if (answer[answer.lastIndex] == ' ') answer = answer.dropLast(1) + } else { + answer += rus.get(end % 10) + end -= end % 10 + if (end % 100 > 0) answer = rus.get(end % 100) + " " + answer + end -= end % 100 + if (end > 0) answer = rus.get(end) + " " + answer + if (answer[answer.lastIndex] == ' ') answer = answer.dropLast(1) + } + } + if (beg > 0) { + + if ((beg % 100) in 11..19) { + answer = rus.get(beg % 100) + " тысяч $answer" + beg -= beg % 100 + if (beg > 0) answer = rus.get(beg) + " " + answer + return answer + } else { + val a = beg % 10 + //val b = beg % 100 + if (beg % 10 > 0) answer = when { + ((beg % 10 == 0) && (beg % 100 !in 10..20)) -> " тысяч " + ((beg % 10 == 1) && (beg % 100 !in 10..20)) -> "одна тысяча " + ((beg % 10 == 2) && (beg % 100 !in 10..20)) -> "две тысячи " + ((beg % 10 == 3) && (beg % 100 !in 10..20)) -> "три тысячи " + ((beg % 10 == 4) && (beg % 100 !in 10..20)) -> "четыре тысячи " + //((beg % 10 in 2..4) && (beg % 100 !in 10..20)) -> rus.get(beg) + " тысячи " + else -> rus.get(beg % 10) + " тысяч " + } + answer + //answer = rus.get(beg % 10) + " " + answer + beg -= beg % 10 + if (a == 0) answer = "тысяч $answer" + if (beg % 100 > 0) answer = rus.get(beg % 100) + " " + answer + beg -= beg % 100 + if (beg > 0) answer = rus.get(beg) + " " + answer + } + } + if (answer[answer.lastIndex] == ' ') return answer.dropLast(1) + return answer +} \ No newline at end of file diff --git a/src/lesson5/task1/Map.kt b/src/lesson5/task1/Map.kt index 51ab7411f..83470cdaa 100644 --- a/src/lesson5/task1/Map.kt +++ b/src/lesson5/task1/Map.kt @@ -2,6 +2,8 @@ package lesson5.task1 +import ru.spbstu.wheels.NullableMonad.map + // Урок 5: ассоциативные массивы и множества // Максимальное количество баллов = 14 // Рекомендуемое количество баллов = 9 @@ -208,7 +210,12 @@ fun canBuildFrom(chars: List, word: String): Boolean = TODO() * Например: * extractRepeats(listOf("a", "b", "a")) -> mapOf("a" to 2) */ -fun extractRepeats(list: List): Map = TODO() +fun extractRepeats(list: List): Map { + val gr = list.groupBy { it } + val answer = mutableMapOf() + for (x in gr) if (x.value.size > 1) answer.put(x.key, x.value.size) + return answer +} /** * Средняя (3 балла) @@ -277,7 +284,18 @@ fun propagateHandshakes(friends: Map>): Map Pair(0, 2) * findSumOfTwo(listOf(1, 2, 3), 6) -> Pair(-1, -1) */ -fun findSumOfTwo(list: List, number: Int): Pair = TODO() +fun findSumOfTwo(list: List, number: Int): Pair { + var a = -1 + var b = -1 + for (i in 0..list.size - 2) + for (j in i + 1..list.size - 1) + if (list[i] + list[j] == number) { + a = i + b = j + return Pair(a, b) + } + return Pair(a, b) +} /** * Очень сложная (8 баллов) diff --git a/src/lesson6/task1/Parse.kt b/src/lesson6/task1/Parse.kt index ae6226d24..236665bb0 100644 --- a/src/lesson6/task1/Parse.kt +++ b/src/lesson6/task1/Parse.kt @@ -2,6 +2,9 @@ package lesson6.task1 +import java.io.File +import java.io.IOException + // Урок 6: разбор строк, исключения // Максимальное количество баллов = 13 // Рекомендуемое количество баллов = 11 @@ -138,7 +141,149 @@ fun bestHighJump(jumps: String): Int = TODO() * Вернуть значение выражения (6 для примера). * Про нарушении формата входной строки бросить исключение IllegalArgumentException */ -fun plusMinus(expression: String): Int = TODO() +fun plusMinus(expression: String): Int { + if (expression == "") throw IllegalArgumentException("") + var answer = 0 + var sign = '+' + var num = true + for (a in expression.split("\\s+".toRegex())) { + if (a.isEmpty()) throw IllegalArgumentException("") + if (num) { + if (a.matches("^\\d+\$".toRegex())) answer += (sign + a).toInt() + else throw IllegalArgumentException("") + num = false + } else if (!num) { + if (a.matches("^[+-]".toRegex())) sign = a[0] + else throw IllegalArgumentException("") + num = true + } + } + return answer +} + +fun todo(todos: List): List { + val a = todos.groupBy { it.split(" - ")[0] } + for (i in todos) if (i.split(" - ").size != 2) throw IllegalArgumentException() + val answer = mutableListOf() + val days = listOf("понедельник", "вторник", "среда", "четверг", "пятница", "суббота", "воскресенье") + for (i in a.keys) if (!days.contains(i)) throw IllegalArgumentException() + for (day in days) + if (a.containsKey(day)) answer.add(day + " - " + a.getOrDefault(day, emptyList()).size.toString()) + return answer +} + +fun todoList(inputName: String, limit: Int): String { + val tasks = mutableListOf() + val importance = mutableListOf() + val time = mutableListOf() + val reader = File(inputName) + for (line in reader.readLines()) { + tasks.add(line.split(" -- ")[0]) + importance.add(line.split(" -- ")[1].split(",")[0].split(" ")[1].toInt()) + time.add(line.split(" -- ")[1].split(", ")[1].split(" ")[0].toInt()) + } + var max = 0; + var i1 = 0 + var i2 = 0 + for (i in 0..(importance.size - 1)) { + + for (j in i..importance.size - 1) { + if ((importance[i] + importance[j]) > max) + if ((time[i] + time[j]) <= limit) + if (i != j) { + i1 = i + i2 = j + max = importance[i] + importance[j] + } + } + } + if (max == 0) throw IllegalArgumentException() + + return tasks[i1] + " и " + tasks[i2] + ", сумма важности " + max.toString() + ", сумма времени " + + (time[i1] + time[i2]).toString() + " мин." +} + +fun football(inputName: String): String { + val reader = File(inputName) + val teams = mutableListOf() + val results = mutableListOf() + val score = mutableListOf() + + for (line in reader.readLines()) { + results.add(line.split(" - ")[0].replace(" ", "")) + teams.add(line.split(" - ")[1]) + score.add(0) + } + + for (i in 0..results.size - 1) + for (j in 0..results[i].length - 1) { + + if (results[i][j] == 'W') + if (results[j][i] == 'L') score[i] += 3 + else throw IllegalStateException() + + if (results[i][j] == 'D') + if (results[j][i] == 'D') score[i] += 1 + else throw IllegalStateException() + } + return teams[score.indexOf(score.maxOrNull())] +} + +fun racing(inputName: String): String { + val teams = mutableListOf() + val score = mutableListOf() + if (!File(inputName).exists()) throw IOException() + val file = File(inputName).readLines() + for (line in file) { + if (line.split(" ").size > 2) { + if (!teams.contains(line.split(", ")[1])) { + teams.add(line.split(", ")[1]) + score.add(line.split(", ")[2].toInt()) + + } else score[teams.indexOf(line.split(", ")[1])] += line.split(", ")[2].toInt() + } + } + return teams[score.indexOf(score.maxOrNull())] + ", " + score.maxOrNull().toString() +} + +fun intersec(inputName: String, expr: String): MutableList { + val reader = File(inputName) + if (!File(inputName).exists()) throw IOException() + val names = mutableListOf() + val list1 = mutableListOf>() + val answer = mutableListOf() + + for (line in reader.readLines()) { + names.add(line.split(" = ")[0]) + val tmp = mutableListOf() + for (number in line.split(" = ")[1].split(", ")) + if (number.all { Character.isDigit(it) } or number.any() { it == '-' }) tmp.add(number.toInt()) + else throw IllegalArgumentException() + list1.add(tmp) + } + + for (n in list1[names.indexOf(expr.split(" & ")[0])]) + if (list1[names.indexOf(expr.split(" & ")[1])].contains(n) && !answer.contains(n)) + answer.add(n) + + return answer +} + +fun html(inputName: String, outputName: String) { + val writer = File(outputName).bufferedWriter() + val answer = StringBuilder() + answer.append("\n") + for (line in File(inputName).readLines()) { + if (line.substring(0, 5).contains(" === ")) + answer.append("

" + line.substring(6) + "

\n") + else if (line.substring(0, 4).contains(" == ")) + answer.append("

" + line.substring(5) + "

\n") + else answer.append("

" + line.substring(4) + "

\n") + } + answer.append("") + writer.write(answer.toString()) + writer.close() +} /** * Сложная (6 баллов) @@ -149,7 +294,21 @@ fun plusMinus(expression: String): Int = TODO() * Вернуть индекс начала первого повторяющегося слова, или -1, если повторов нет. * Пример: "Он пошёл в в школу" => результат 9 (индекс первого 'в') */ -fun firstDuplicateIndex(str: String): Int = TODO() +fun firstDuplicateIndex(str: String): Int { + val a = mutableListOf() + for (i in str.split("\\s+".toRegex())) a.add(i.lowercase()) + var currentStr = a[0] + for (i in 1..a.size - 1) { + if (a[i] == currentStr) { + var answer = 0 + for (j in 0..i - 2) answer += a[j].length + 1 + return answer + } + currentStr = a[i] + } + + return -1 +} /** * Сложная (6 баллов) diff --git a/src/lesson7/task1/Files.kt b/src/lesson7/task1/Files.kt index 3a75dffbc..4bd8a52da 100644 --- a/src/lesson7/task1/Files.kt +++ b/src/lesson7/task1/Files.kt @@ -63,7 +63,14 @@ fun alignFile(inputName: String, lineLength: Int, outputName: String) { * Подчёркивание в середине и/или в конце строк значения не имеет. */ fun deleteMarked(inputName: String, outputName: String) { - TODO() + val writer = File(outputName).bufferedWriter() + for (line in File(inputName).readLines()) { + if (line.isEmpty() || line[0] != '_') { + writer.write(line) + writer.appendLine() + } + } + writer.close() } /** @@ -75,7 +82,29 @@ fun deleteMarked(inputName: String, outputName: String) { * Регистр букв игнорировать, то есть буквы е и Е считать одинаковыми. * */ -fun countSubstrings(inputName: String, substrings: List): Map = TODO() +fun countSubstrings(inputName: String, substrings: List): Map { + val answer = mutableMapOf() + for (string in substrings) { + answer[string] = 0 + } + var index = 0 + val file = File(inputName).readLines() + val substringSet = substrings.toSet() + for (line in file) { + for (string in substringSet) { + while (index < line.length && index != -1) { + index = line.indexOf(string, index, true) + if (index != -1) { + answer[string] = answer[string]!! + 1 + index++ + } + } + index = 0 + } + } + return answer +} + /** @@ -92,7 +121,27 @@ fun countSubstrings(inputName: String, substrings: List): Map() + for (line in File(inputName).readLines()) s.add(line) + for (line in s) { + var lastChr = line[0] + for (chr in line) { + if ("ЖЧШЩжчшщ".contains(lastChr) && "ЫЯЮыяю".contains(chr)) writer.write(sib.getValue(chr).toString()) + else writer.write(chr.toString()) + lastChr = chr + } + writer.newLine() + } + writer.close() } /** @@ -113,7 +162,18 @@ fun sibilants(inputName: String, outputName: String) { * */ fun centerFile(inputName: String, outputName: String) { - TODO() + val writer = File(outputName).bufferedWriter() + val txt = mutableListOf() + var maxLength = 0 + for (line in File(inputName).readLines()) { + txt.add(line.trim()) + if (line.trim().length > maxLength) maxLength = line.trim().length + } + for (x in txt) { + writer.write(" ".repeat(((maxLength - x.length) / 2)) + x) + writer.newLine() + } + writer.close() } /** @@ -268,15 +328,15 @@ Suspendisse ~~et elit in enim tempus iaculis~~. * * Соответствующий выходной файл: - -

- Lorem ipsum dolor sit amet, consectetur adipiscing elit. - Vestibulum lobortis. Est vehicula rutrum suscipit, ipsum libero placerat tortor. -

-

- Suspendisse et elit in enim tempus iaculis. -

- + +

+Lorem ipsum dolor sit amet, consectetur adipiscing elit. +Vestibulum lobortis. Est vehicula rutrum suscipit, ipsum libero placerat tortor. +

+

+Suspendisse et elit in enim tempus iaculis. +

+ * * (Отступы и переносы строк в примере добавлены для наглядности, при решении задачи их реализовывать не обязательно) @@ -319,65 +379,65 @@ fun markdownToHtmlSimple(inputName: String, outputName: String) { * * Пример входного файла: ///////////////////////////////начало файла///////////////////////////////////////////////////////////////////////////// -* Утка по-пекински - * Утка - * Соус -* Салат Оливье - 1. Мясо - * Или колбаса - 2. Майонез - 3. Картофель - 4. Что-то там ещё -* Помидоры -* Фрукты - 1. Бананы - 23. Яблоки - 1. Красные - 2. Зелёные + * Утка по-пекински + * Утка + * Соус + * Салат Оливье +1. Мясо + * Или колбаса +2. Майонез +3. Картофель +4. Что-то там ещё + * Помидоры + * Фрукты +1. Бананы +23. Яблоки +1. Красные +2. Зелёные ///////////////////////////////конец файла////////////////////////////////////////////////////////////////////////////// * * * Соответствующий выходной файл: ///////////////////////////////начало файла///////////////////////////////////////////////////////////////////////////// - -

-

    -
  • - Утка по-пекински -
      -
    • Утка
    • -
    • Соус
    • -
    -
  • -
  • - Салат Оливье -
      -
    1. Мясо -
        -
      • Или колбаса
      • -
      -
    2. -
    3. Майонез
    4. -
    5. Картофель
    6. -
    7. Что-то там ещё
    8. -
    -
  • -
  • Помидоры
  • -
  • Фрукты -
      -
    1. Бананы
    2. -
    3. Яблоки -
        -
      1. Красные
      2. -
      3. Зелёные
      4. -
      -
    4. -
    -
  • -
-

- + +

+

    +
  • +Утка по-пекински +
      +
    • Утка
    • +
    • Соус
    • +
    +
  • +
  • +Салат Оливье +
      +
    1. Мясо +
        +
      • Или колбаса
      • +
      +
    2. +
    3. Майонез
    4. +
    5. Картофель
    6. +
    7. Что-то там ещё
    8. +
    +
  • +
  • Помидоры
  • +
  • Фрукты +
      +
    1. Бананы
    2. +
    3. Яблоки +
        +
      1. Красные
      2. +
      3. Зелёные
      4. +
      +
    4. +
    +
  • +
+

+ ///////////////////////////////конец файла////////////////////////////////////////////////////////////////////////////// * (Отступы и переносы строк в примере добавлены для наглядности, при решении задачи их реализовывать не обязательно) @@ -404,23 +464,23 @@ fun markdownToHtml(inputName: String, outputName: String) { * Вывести в выходной файл процесс умножения столбиком числа lhv (> 0) на число rhv (> 0). * * Пример (для lhv == 19935, rhv == 111): - 19935 -* 111 +19935 + * 111 -------- - 19935 +19935 + 19935 +19935 -------- - 2212785 +2212785 * Используемые пробелы, отступы и дефисы должны в точности соответствовать примеру. * Нули в множителе обрабатывать так же, как и остальные цифры: - 235 -* 10 +235 + * 10 ----- - 0 +0 +235 ----- - 2350 +2350 * */ fun printMultiplicationProcess(lhv: Int, rhv: Int, outputName: String) { @@ -434,16 +494,16 @@ fun printMultiplicationProcess(lhv: Int, rhv: Int, outputName: String) { * Вывести в выходной файл процесс деления столбиком числа lhv (> 0) на число rhv (> 0). * * Пример (для lhv == 19935, rhv == 22): - 19935 | 22 - -198 906 - ---- - 13 - -0 - -- - 135 - -132 - ---- - 3 +19935 | 22 +-198 906 +---- +13 +-0 +-- +135 +-132 +---- +3 * Используемые пробелы, отступы и дефисы должны в точности соответствовать примеру. * diff --git a/src/lesson8/task3/Graph.kt b/src/lesson8/task3/Graph.kt index e505972b9..70c582435 100644 --- a/src/lesson8/task3/Graph.kt +++ b/src/lesson8/task3/Graph.kt @@ -65,7 +65,7 @@ class Graph { val min = start.neighbors .filter { it !in visited } .mapNotNull { dfs(it, finish, visited + start) } - .min() + .minOrNull() if (min == null) null else min + 1 } } diff --git a/test/lesson10/task2/Tests.kt b/test/lesson10/task2/Tests.kt index 9c06f525a..dee76578e 100644 --- a/test/lesson10/task2/Tests.kt +++ b/test/lesson10/task2/Tests.kt @@ -35,4 +35,20 @@ class Tests { fun generateSimpleHtml() { assertEquals("Hello!", generateSimpleHtml("Hello!")) } + + @Test + @Tag("5") + fun generateListHtml() { + assertEquals( + """ + +
    +
  1. Alpha
  2. +
  3. Beta
  4. +
  5. Omega
  6. +
+ + """.trimIndent().replace("[\\r\\n ]".toRegex(), ""), generateListHtml(listOf("Alpha", "Beta", "Omega")) + ) + } } \ No newline at end of file diff --git a/test/lesson11/task1/ComplexTest.kt b/test/lesson11/task1/ComplexTest.kt index e20cefe5e..2db78a844 100644 --- a/test/lesson11/task1/ComplexTest.kt +++ b/test/lesson11/task1/ComplexTest.kt @@ -21,25 +21,25 @@ internal class ComplexTest { @Test @Tag("2") operator fun unaryMinus() { - assertApproxEquals(Complex(1.0, -2.0), -Complex(2.0, -1.0), 1e-10) + assertApproxEquals(Complex(1.0, -2.0), -Complex(-1.0, 2.0), 1e-10) } @Test @Tag("2") fun minus() { - assertApproxEquals(Complex("4-2i"), Complex("1+2i") + Complex("3-4i"), 1e-10) + assertApproxEquals(Complex("2-6i"), Complex("3-4i") - Complex("1+2i"), 1e-10) } @Test @Tag("4") fun times() { - assertApproxEquals(Complex("11-8i"), Complex("1+2i") + Complex("3-4i"), 1e-10) + assertApproxEquals(Complex("11+2i"), Complex("1+2i") * Complex("3-4i"), 1e-10) } @Test @Tag("4") fun div() { - assertApproxEquals(Complex("1+2i"), Complex("11-8i") + Complex("3-4i"), 1e-10) + assertApproxEquals(Complex("1+2i"), Complex("11+2i") / Complex("3-4i"), 1e-10) } @Test diff --git a/test/lesson11/task1/DimensionalValueTest.kt b/test/lesson11/task1/DimensionalValueTest.kt index 464e5d367..192e30160 100644 --- a/test/lesson11/task1/DimensionalValueTest.kt +++ b/test/lesson11/task1/DimensionalValueTest.kt @@ -16,7 +16,7 @@ internal class DimensionalValueTest { @Tag("12") fun base() { val first = DimensionalValue(1.0, "Kg") - assertEquals(1.0, first.value) + assertEquals(1000.0, first.value) assertEquals(Dimension.GRAM, first.dimension) val second = DimensionalValue("200 m") assertEquals(200.0, second.value) diff --git a/test/lesson2/task1/Tests.kt b/test/lesson2/task1/Tests.kt index 52870a8a5..0fb1876b4 100644 --- a/test/lesson2/task1/Tests.kt +++ b/test/lesson2/task1/Tests.kt @@ -40,6 +40,7 @@ class Tests { assertEquals("12 лет", ageDescription(12)) assertEquals("111 лет", ageDescription(111)) assertEquals("199 лет", ageDescription(199)) + assertEquals("5 лет", ageDescription(5)) } @Test diff --git a/test/lesson2/task2/Tests.kt b/test/lesson2/task2/Tests.kt index 717dfa2c7..b887464be 100644 --- a/test/lesson2/task2/Tests.kt +++ b/test/lesson2/task2/Tests.kt @@ -61,6 +61,7 @@ class Tests { @Tag("3") fun brickPasses() { assertTrue(brickPasses(2, 10, 5, 6, 3)) + assertFalse(brickPasses(2, 1, 2, 1, 1)) assertTrue(brickPasses(4, 4, 4, 4, 4)) assertFalse(brickPasses(6, 5, 4, 3, 6)) assertTrue(brickPasses(3, 2, 1, 1, 2)) diff --git a/test/lesson3/task1/Tests.kt b/test/lesson3/task1/Tests.kt index e8e92778e..c9b9ccf06 100644 --- a/test/lesson3/task1/Tests.kt +++ b/test/lesson3/task1/Tests.kt @@ -157,19 +157,6 @@ class Tests { assertTrue(isCoPrime(2109876543, 1234567891)) } - @Test - @Tag("3") - fun squareBetweenExists() { - assertTrue(squareBetweenExists(1, 1)) - assertTrue(squareBetweenExists(21, 28)) - assertTrue(squareBetweenExists(36, 48)) - assertTrue(squareBetweenExists(50, 64)) - assertFalse(squareBetweenExists(51, 61)) - assertFalse(squareBetweenExists(999, 1001)) - assertTrue(squareBetweenExists(152374337, 152423715)) - assertFalse(squareBetweenExists(2147395601, Int.MAX_VALUE)) - } - @Test @Tag("3") fun revert() { diff --git a/test/lesson4/task1/Tests.kt b/test/lesson4/task1/Tests.kt index b3005269c..f2c17fe29 100644 --- a/test/lesson4/task1/Tests.kt +++ b/test/lesson4/task1/Tests.kt @@ -238,5 +238,7 @@ class Tests { assertEquals("двести тысяч два", russian(200002)) assertEquals("девятьсот тысяч", russian(900000)) assertEquals("двенадцать", russian(12)) + assertEquals("шестьсот десять тысяч девятьсот девяносто восемь", russian(610998)) + assertEquals("двенадцать тысяч четыреста двадцать два", russian(12422)) } } \ No newline at end of file diff --git a/test/lesson6/task1/Tests.kt b/test/lesson6/task1/Tests.kt index c0af8c7e4..5f21cfbf6 100644 --- a/test/lesson6/task1/Tests.kt +++ b/test/lesson6/task1/Tests.kt @@ -4,8 +4,18 @@ import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertThrows import org.junit.jupiter.api.Tag import org.junit.jupiter.api.Test +import java.io.File +import java.io.IOException + class Tests { + + private fun assertFileContent(name: String, expectedContent: String) { + val file = File(name) + val content = file.readLines().joinToString("\n") + assertEquals(expectedContent, content) + } + @Test @Tag("Example") fun timeStrToSeconds() { @@ -89,6 +99,7 @@ class Tests { @Test @Tag("6") fun plusMinus() { + assertThrows(IllegalArgumentException::class.java) { plusMinus(" a") } assertEquals(0, plusMinus("0")) assertEquals(4, plusMinus("2 + 2")) assertEquals(6, plusMinus("2 + 31 - 40 + 13")) @@ -98,6 +109,96 @@ class Tests { assertThrows(IllegalArgumentException::class.java) { plusMinus("4 - -2") } assertThrows(IllegalArgumentException::class.java) { plusMinus("44 - - 12") } assertThrows(IllegalArgumentException::class.java) { plusMinus("4 - + 12") } + + } + + @Test + @Tag("6") + fun todo() { + assertEquals( + listOf("вторник - 1", "пятница - 2", "суббота - 1"), + todo(listOf("вторник - лекции;", "пятница - друзья;", "суббота - кинотеатр;", "пятница - уборка;")) + ) + assertEquals( + listOf("вторник - 2", "суббота - 3"), + todo( + listOf( + "вторник - лекции;", + "суббота - кинотеатр;", + "вторник - лекции;", + "суббота - кинотеатр;", + "суббота - кинотеатр;" + ) + ) + ) + assertThrows(IllegalArgumentException::class.java) { + todo( + listOf( + "вторник - лекции;", + "пятница -j j друзья;", + "субfgбота - кинотеатр;", + "пятница - уборка;" + ) + ) + } + + } + + + @Test + @Tag("1") + fun todoList() { + assertEquals( + "задание1 и задание3, сумма важности 5, сумма времени 75 мин.", + todoList("input/govno.txt", 90) + ) + assertThrows(IllegalArgumentException::class.java) { todoList("input/govno2.txt", 90) } + } + + @Test + @Tag("1") + fun football() { + assertEquals( + "1", + football("input/football.txt") + ) + assertThrows(IllegalStateException::class.java) { lesson6.task1.football("input/football2.txt") } + } + + @Test + @Tag("1") + fun racing() { + assertEquals( + "teamMM, 18", + racing("input/racing.txt") + ) + assertThrows(IOException::class.java) { racing("input/racing2.txt") } + } + + @Test + @Tag("1") + fun intersec() { + assertEquals( + mutableListOf(-1, 5, 202), + intersec("input/intersec.txt", "A & B") + ) + assertThrows(IOException::class.java) { intersec("input/intersec3.txt", "A & J") } + assertThrows(IllegalArgumentException::class.java) { intersec("input/intersec2.txt", "A & J") } + } + + @Test + @Tag("1") + fun html() { + html("input/html1.txt", "tmp.txt") + assertFileContent( + "tmp.txt", + """ +

Header1

+

Header3

+

Header2

+""".trimMargin() + ) + File("tmp.txt").delete() } @Test diff --git a/test/lesson8/task1/HexTests.kt b/test/lesson8/task1/HexTests.kt index 38c02b57f..de4799dd7 100644 --- a/test/lesson8/task1/HexTests.kt +++ b/test/lesson8/task1/HexTests.kt @@ -105,13 +105,7 @@ class HexTests { @Tag("5") fun pathBetweenHexes() { assertEquals( - listOf( - HexPoint(y = 2, x = 2), - HexPoint(y = 2, x = 3), - HexPoint(y = 3, x = 3), - HexPoint(y = 4, x = 3), - HexPoint(y = 5, x = 3) - ), pathBetweenHexes(HexPoint(y = 2, x = 2), HexPoint(y = 5, x = 3)) + 5, pathBetweenHexes(HexPoint(y = 2, x = 2), HexPoint(y = 5, x = 3)).size ) } diff --git a/test/lesson8/task1/Tests.kt b/test/lesson8/task1/Tests.kt index 6051ee6c1..9294aa83d 100644 --- a/test/lesson8/task1/Tests.kt +++ b/test/lesson8/task1/Tests.kt @@ -207,7 +207,7 @@ class Tests { @Test @Tag("3") - fun lineByPoint() { + fun lineByPoints() { assertApproxEquals(Line(Point(0.0, 0.0), PI / 2), lineByPoints(Point(0.0, 0.0), Point(0.0, 2.0))) assertApproxEquals(Line(Point(1.0, 1.0), PI / 4), lineByPoints(Point(1.0, 1.0), Point(3.0, 3.0))) } @@ -254,5 +254,16 @@ class Tests { for (p in listOf(p1, p2, p3, p4, p5, p6)) { assertTrue(result.contains(p)) } + // Набор точек для примера + // A = (-3,-1), B = (0, 3), C = (1.96, -1.6), D = (0, -2.4), тут диаметр это BD = 5.4, но ни окружность по B и D, ни окружность по B, D и A, ни окружность по B, D и C все точки не содержат, причём, с запасом в районе 0.2 - 0.3 + val a = Point(-3.0, -1.0) + val b = Point(0.0, 3.0) + val c = Point(1.96, -1.6) + val d = Point(0.0, -2.4) + val result2 = minContainingCircle(a, b, c, d) + assertEquals(2.89, result2.radius, 0.01) + for (p in listOf(a, b, c, d)) { + assertTrue(result2.contains(p)) + } } } diff --git a/tutorial/chapter00.adoc b/tutorial/chapter00.adoc index 20ced2ae5..2e8d6f39e 100644 --- a/tutorial/chapter00.adoc +++ b/tutorial/chapter00.adoc @@ -35,14 +35,17 @@ Java также включает богатейший набор библиот == Что требуется для начала -Самый простой способ начать программировать на Котлине -- зайти на сайт http://try.kotlinlang.org. +Самый простой способ начать программировать на Котлине -- зайти на сайт https://play.kotlinlang.org/. Имеющаяся там "песочница" позволяет писать программы прямо в браузере, с возможностью выполнять и сохранять свои программы и проходить обучающие курсы. Масштабы песочницы, однако, достаточны только для небольших программ, а более-менее серьёзные программы, как правило, разрабатываются в интегрированной среде (IDE). Разработка под платформу Java в любом случае требует установки пакета JDK, который необходимо скачать с http://www.oracle.com/technetwork/java/javase/downloads/index.html[сайта компании Oracle]. -Первое время вам потребуется Java Platform, Standard Edition, рекомендуется использовать 8-ю или 11-ю редакции. На сентябрь 2020 года последние версии -- это Java SE 8u261 и Java SE 11.0.8 соответственно. Вы можете также попробовать недавно вышедшую 14-ю редакцию: Java SE 14.0.2. +Первое время вам потребуется Java Platform, Standard Edition, рекомендуется использовать 8-ю или 11-ю редакции как более проверенные и гарантирующие LTS (Long-Term Support). +На август 2021 года последние версии -- это Java SE https://www.oracle.com/java/technologies/javase/javase-jdk8-downloads.html[8u301] +и Java SE https://www.oracle.com/java/technologies/javase-jdk11-downloads.html[11.0.12] соответственно. +Вы можете также попробовать недавно вышедшую 16-ю редакцию: Java SE https://www.oracle.com/java/technologies/javase-jdk16-downloads.html[16.0.2]. -В качестве интегрированной среды разработки рекомендую установить IntelliJ IDEA Community Edition, её следует брать https://www.jetbrains.com/idea/download[отсюда]. +В качестве интегрированной среды разработки рекомендую установить IntelliJ IDEA Community Edition, её следует брать https://www.jetbrains.com/idea/download[отсюда], последняя версия на август 2021 года -- 2021.2.1. Community Edition является полностью бесплатной, базовая версия обеспечивает поддержку программирования на Java, Kotlin, Scala, Groovy, поддержку систем контроля версий Git, Mercurial, SVN, интеграцию с системами сборки Maven и Gradle. Для интеграции IDEA с системой контроля версий Git необходимо установить один из клиентов Git. @@ -52,13 +55,13 @@ Community Edition является полностью бесплатной, ба == Учебный проект В ходе обучения мы будем активно использовать проект "Котлин как первый язык программирования", содержащий текст данного пособия и около сотни различных задач на языке Kotlin. -Оригинальный код данного проекта доступен по адресу https://github.com/Kotlin-Polytech/KotlinAsFirst2020 на сайте GitHub, который является специализированным хранилищем программных кодов и основан на системе контроля версий Git. -Для того, чтобы начать работать с этим проектом, Вам необходимо выполнить следующие действия. +Оригинальный код данного проекта доступен по адресу https://github.com/Kotlin-Polytech/KotlinAsFirst2021 на сайте GitHub, который является специализированным хранилищем программных кодов и основан на системе контроля версий Git. +Для того чтобы начать работать с этим проектом, Вам необходимо выполнить следующие действия. 1. Зарегистрироваться на https://github.com/ (в случае, если у Вас еще нет GitHub аккаунта). Далее выбранное Вами имя будет обозначаться как . -1. Создать специальную копию репозитория проекта -- _форк_. Для этого достаточно зайти на страницу проекта https://github.com/Kotlin-Polytech/KotlinAsFirst2020 и нажать кнопку `Fork` в правом верхнем углу страницы. После этого Ваша персональная копия проекта станет доступна по адресу https://github.com//KotlinAsFirst2020, и всю работу по решению различных задач Вы должны выполнять именно с Вашей копией. -1. Для загрузки проекта в IntelliJ IDEA следует выполнить команду `Check out from Version Control` -> `GitHub` из окна `Welcome to Intellij IDEA` (или `File` -> `New` -> `Project from Version Control` -> `GitHub` из окна проекта), в появившемся окне ввести Git Repository URL https://github.com//KotlinAsFirst2020 и место на компьютере, куда будет скачан проект (Parent Directory). -1. Далее следуйте инструкциям среды для настройки проекта. Подробное руководство вы можете найти http://kspt.icc.spbstu.ru/media/files/2018/kaf/IdeaConfig.pdf[здесь]. +2. Создать специальную копию репозитория проекта -- _форк_. Для этого достаточно зайти на страницу проекта https://github.com/Kotlin-Polytech/KotlinAsFirst2021 и нажать кнопку `Fork` в правом верхнем углу страницы. После этого Ваша персональная копия проекта станет доступна по адресу https://github.com//KotlinAsFirst2021, и всю работу по решению различных задач Вы должны выполнять именно с Вашей копией. +3. Для загрузки проекта в IntelliJ IDEA следует выполнить команду `Check out from Version Control` -> `GitHub` из окна `Welcome to Intellij IDEA` (или `File` -> `New` -> `Project from Version Control` -> `GitHub` из окна проекта), в появившемся окне ввести Git Repository URL https://github.com//KotlinAsFirst2021 и место на компьютере, куда будет скачан проект (Parent Directory). +4. Далее следуйте инструкциям среды для настройки проекта. Подробное руководство вы можете найти http://kspt.icc.spbstu.ru/media/files/2021/kaf/KotlinAsFirstConfig.pdf[здесь]. Проект содержит задачи, разбитые на девять уроков (lesson). Тексты задач доступны через окно `Project` в IntelliJ IDEA (открывается комбинацией клавиш `Alt + 1`). @@ -82,9 +85,9 @@ Community Edition является полностью бесплатной, ба Законченное решение задачи (или нескольких задач из одного урока) следует фиксировать в виде коммита в системе контроля версий. Для этого необходимо: 1. Зайти в окно `Version Control` среды Intellij IDEA. Это делается из меню `View` -> `Tool Windows`, либо с помощью комбинации клавиш `Alt + 9`. -1. В нём щелчком мыши выбрать вкладку `Local Changes` (локальные изменения). Убедитесь, что файлы, которые вы меняли, присутствуют в этом окне. -1. В контекстном меню выберите команду `Commit Changes`. В появившемся окне введите осмысленный комментарий к вашему изменению (например, "Решена задача такая-то"), откройте выпадающее меню справа от кнопки `Commit` и в нём выберете команду `Commit and Push`. -1. При появлении соответствующих окон введите своё имя и e-mail для идентификации автора коммита (эти поля заполняются один раз), а также логин и пароль для Вашего аккаунта на GitHub. +2. В нём щелчком мыши выбрать вкладку `Local Changes` (локальные изменения). Убедитесь, что файлы, которые вы меняли, присутствуют в этом окне. +3. В контекстном меню выберите команду `Commit Changes`. В появившемся окне введите осмысленный комментарий к вашему изменению (например, "Решена задача такая-то"), откройте выпадающее меню справа от кнопки `Commit` и в нём выберете команду `Commit and Push`. +4. При появлении соответствующих окон введите своё имя и e-mail для идентификации автора коммита (эти поля заполняются один раз), а также логин и пароль для Вашего аккаунта на GitHub. == Система Kotoed @@ -93,8 +96,8 @@ Community Edition является полностью бесплатной, ба 1. Зарегистрироваться в системе, указав свой никнейм, почту и пароль. * Для упрощения входа в систему можно связать свой аккаут с одним или несколькими OAuth провайдерами при помощи соответствующих ссылок на странице логина. -1. В профиле указать ваше имя (First Name), фамилию (Second Name) и номер группы (Group). -1. На странице нашего курса KotlinAsFirst-2020 создать проект (`Create project`), связанный с вашим репозиторием на GitHub. +2. В профиле указать ваше имя (First Name), фамилию (Second Name) и номер группы (Group). +3. На странице нашего курса KotlinAsFirst-2021 создать проект (`Create project`), связанный с вашим репозиторием на GitHub. В данном проекте вы будете создавать запросы на проверку (submissions), в рамках которых будет осуществляться оценка как корректности вашего решения, так и качества вашего кода. Для создания запроса на проверку вам следует зайти в системе Kotoed на страницу вашего проекта и нажать `Submit`, при этом будет автоматически создан запрос на проверку _последней_ ревизии (версии) вашего репозитория. @@ -104,19 +107,19 @@ Community Edition является полностью бесплатной, ба После этого на странице с результатами запроса (`Results`) вы сможете увидеть следующую информацию: * Какие задачи были решены верно -* Какие задачи были решены неполностью или неправильно, с указанием непрошедших тестов +* Какие задачи были решены не полностью или неправильно, с указанием непрошедших тестов * Статистику решения заданий по всем урокам * Ошибки сборки проекта (если такие имеются) * Ошибки запроса на проверку (если такие имеются) Кроме того, на странице `Review` вы можете как задать преподавателю вопрос по какому-либо заданию в виде комментария к интересующей вас строчке кода, так и увидеть вопросы и замечания преподавателей к вашему коду. -Для того, чтобы начать или продолжить обсуждение, следует нажать на карандаш рядом с интересующей вас строкой или на отметку об имеющихся комментариях. -Для того, чтобы добавить новый комментарий к уже имеющимся, можно воспользоваться формой добавления комментария. +Для того чтобы начать или продолжить обсуждение, следует нажать на карандаш рядом с интересующей вас строкой или на отметку об имеющихся комментариях. +Для того чтобы добавить новый комментарий к уже имеющимся, можно воспользоваться формой добавления комментария. Процесс внесения исправлений в уже созданный запрос заключается в следующем. 1. Поправить найденные ошибки и замечания преподавателя в вашем репозитории, после чего зафиксировать их в виде одного или нескольких коммитов (`Commit and push`). -1. Зайти в текущий активный запрос на исправление и нажать кнопку `Resubmit`. При необходимости проверить конкретную ревизию можно воспользоваться выпадающим меню `Specify revision`. +2. Зайти в текущий активный запрос на исправление и нажать кнопку `Resubmit`. При необходимости проверить конкретную ревизию можно воспользоваться выпадающим меню `Specify revision`. После этого будет создан новый зависимый запрос на исправление, в который автоматически перенесутся все комментарии из его родителя. Он точно так же, как и обычный запрос на исправление, будет проверен, о чем вам придет соответствующее уведомление. @@ -127,7 +130,7 @@ Community Edition является полностью бесплатной, ба Несмотря на то, что у многих из вас будет желание двигаться вперед как можно скорее, мы убедительно просим вас придерживаться следующих трех правил. -* В том и только том случае, если все задачи, которые Вы хотели решить, успешно проверены и ваш запрос закрыт преподавателем, Вы можете приступать к следующим задачам. +* Не переходите к решению новых задач, если у вас имеются неисправленные замечания к старым задачам. * Если часть задач решена неправильно, постарайтесь исправить возможные ошибки при помощи предоставленных Вам тестов. * В случае, если Вы уверены в правильности решения или не можете понять, где Вы ошиблись при решении, можете обратиться к преподавателю. @@ -141,7 +144,7 @@ Community Edition является полностью бесплатной, ба * посмотреть "часто задаваемые вопросы" далее по тексту * поискать ответ на вопрос с помощью поисковой системы в Интернете * почитать разнообразную информацию о Котлине в его http://kotlinlang.org/docs/reference[документации] -* задать нам вопрос в https://kotlinlang.slack.com[Kotlin Slack] (получить приглашение можно http://slack.kotlinlang.org/[здесь]) в канале https://kotlinlang.slack.com/messages/CCH9B1UPJ/convo/C0BQ5GZ0S-1511956674.000289/[russian-kotlinasfirst] +* задать нам вопрос в Телеграм-чате вашего курса или в https://kotlinlang.slack.com[Kotlin Slack] (получить приглашение можно http://slack.kotlinlang.org/[здесь]) в канале https://kotlinlang.slack.com/messages/CCH9B1UPJ/convo/C0BQ5GZ0S-1511956674.000289/[russian-kotlinasfirst] * воспользоваться https://kotlinlang.org/community/[другими ресурсами для общения] Kotlin Slack -- это система общения, созданная специально для программистов на Котлине. @@ -153,7 +156,7 @@ Kotlin Slack -- это система общения, созданная спе * Что делать, если при открытии файла расширением .kt из учебного проекта (например, Simple.kt) вы видите сообщение над ним `Project SDK is not defined`? -Нажмите на ссылку `Setup SDK` в правой части сообщения. Выберете JDK 1.8 (или 11) для работы с проектом в появившемся окне. +Нажмите на ссылку `Setup SDK` в правой части сообщения. Выберете JDK 1.8 (или 11, 16) для работы с проектом в появившемся окне. Если список JDK в окне пуст или не содержит JDK 1.8 (11), следует нажать на клавишу `Configure`, затем зелёный плюс в верхнем левом углу и зарегистрировать установленную на Вашем компьютере JDK 1.8 (11) в Intellij IDEA. Если Вы забыли установить JDK, это следует сделать, предварительно скачав её с сайта Oracle. @@ -165,3 +168,37 @@ Kotlin Slack -- это система общения, созданная спе Если вам не удаётся открыть окно Maven Projects, попробуйте выйти из Intellij IDEA и войти в неё заново. +* Я всё равно не могу настроить учебный проект, что делать? + +Обратитесь к вашему преподавателю практики или к более опытным однокурсникам. + +* Мой Submission долго не проверяют! + +Зайдите на вкладку результатов и убедитесь, что тесты прошли успешно. Если тесты провалены и случай простой, обычно мы ждём, пока вы внесёте соответствующие исправления сами. +Если тесты пройдены и прошло уже более недели после создания Submission – напишите мне (Михаил Глухих) в телеграм, ВКонтакте или на электронную почту. + +* Сколько задач надо решить? + +С точки зрения вашего обучения – чем больше, тем лучше. С точки зрения формального получения зачёта и экзаменационной оценки – в ближайшее время я расскажу на лекциях о правилах игры и выложу соответствующий документ в телеграм-канал. + +* Можно ли пользоваться чужой помощью? + +Да, вы можете обратиться за помощью к преподавателю практики или к более опытным однокурсникам – особенно это касается настройки проекта. Вы можете также попросить совета в решении какой-то сложной задачи, но имейте в виду, что «Совет» не предполагает решение задачи за вас. Не разрешается в процессе решения задачи подглядывать в чужие решения и тем более копировать их! Имейте в виду, что в систему Котоед встроена система обнаружения плагиата. Также, вы должны быть в состоянии объяснить ваши решения преподавателю практики, если он об этом попросит. + +* Мне кажется, что задачи очень простые + +Вы можете не решать задачи низкой сложности. Попробуйте решить две последние задачи в каждом уроке – я вам гарантирую, что это будет непросто (начиная с урока 3), даже если у вас уже есть некоторый опыт программирования. + +* Мне кажется, что задачи очень сложные + +Не предполагается обязательного решения всех задач всеми студентами. Если задача отмечена как «Сложная» или «Очень сложная», её решение необязательно для получения зачёта. Задачи более низкой сложности, однако, вы научиться решать должны. С первыми шагами вам поможет преподаватель практики, далее мы ожидаем от вас самостоятельных решений. + +* Преподаватель требует от меня оформлять код по правилам, а я считаю, что это не нужно! Заставьте его принять мой код как есть! + +Прошу поверить мне как программисту с большим опытом – соблюдение Code Style является очень важной частью нашей работы, так что преподаватель абсолютно прав. Коллеги на вашем первом месте работы тоже будут требовать от вас его соблюдения. Основные соглашения для языка Kotlin можно прочитать https://kotlinlang.org/docs/reference/coding-conventions.html[здесь] (на английском языке). Выдержки из этих соглашений мы рассмотрим на лекциях. + +* Можно ли добавлять в Submission новые задачи, если старые ещё не проверены? + +В идеальном мире – не стоит. В реальном – да, можно, особенно если вас не проверяют долго. Имейте, однако в виду, что вы должны вначале исправить замечания к имеющимся задачам, если они есть, а уже потом добавлять новые. Преподаватель имеет право не проверять ваши новые задачи, если имеются неисправленные замечания к старым задачам. + + diff --git a/tutorial/chapter01.adoc b/tutorial/chapter01.adoc index 6d15ea041..eb15ccec2 100644 --- a/tutorial/chapter01.adoc +++ b/tutorial/chapter01.adoc @@ -77,7 +77,7 @@ fun sqr(x: Int): Int = x * x * Double -- вещественное число в диапазоне (примерно) от -1.7 * 10^308^ до 1.7 * 10^308^, имеющее точность в (примерно) 15 десятичных цифр; * Boolean -- так называемый __логический__ или __булевский__ тип с ровно двумя возможными значениями -- **true** и **false**; * Char -- символ (одиночный), например `Z` или `Я`; значением может являться любой символ, присутствующий в таблице "Unicode", с кодом от 0 до 2^16^-1; - * String -- строка, состоящая из любого количества (в том числе из нуля) одиночных символов. + * String -- строка, состоящая из любого количества одиночных символов. Строка может быть пустой, то есть не содержать ни одного символа `""` Целые и вещественные числа устроены по-разному. Например, целые числа сохраняются в памяти компьютера **точно**, а вещественные числа -- **приближённо**. @@ -297,7 +297,7 @@ fun solveQuadraticEquation(a: Double, b: Double, c: Double) /* no result */ { Обратите внимание, что тип результата функции `solveQuadraticEquation` не указан. Это означает, что функция **не имеет** результата (в математическом смысле). Такие функции встречаются довольно часто, один из примеров -- сама функция `println`, -и их реальный результат сводится к их побочным эффектам -- например, выводу на консоль. +и их реальный результат сводится к побочным эффектам -- например, выводу на консоль. Осталось определить -- что же такое __консоль__? В привычной нам операционной системе Windows __консоль__ -- это окно или же его часть, @@ -324,12 +324,12 @@ fun main(args: Array) { Единственный параметр `args` главной функции имеет тип `Array`, то есть __массив__ строк. О массивах и об использовании параметра `args` главной функции мы поговорим позже. Результата главная функция не имеет. По правилам Котлина (и Java) она всегда обязана называться `main`. -Для быстрого ввода заголовка главной функции в Intellij IDEA можно ввести в редактор специальную строку **psvm** +Для быстрого ввода заголовка главной функции в Intellij IDEA можно ввести в редакторе специальную строку **psvm** с последующим нажатием клавиши **Enter**. Данная короткая программа использует функцию `quadraticRootProduct`, определённую выше, для вычисления произведения корней квадратного уравнения, после чего выводит это произведение на консоль. -Для того, чтобы её запустить, в Intellij IDEA достаточно щёлкнуть мышью на зелёный треугольник +Для того чтобы её запустить, в Intellij IDEA достаточно щёлкнуть мышью на зелёный треугольник слева от заголовка функции `main`. Поскольку корни данного уравнения равны 1.0 и 2.0, после запуска программы на консоли мы увидим строчку @@ -429,16 +429,16 @@ sqr(4) = 16 может содержать любое количество функций и объявлений другого вида (позже мы с ними познакомимся). Объявленные функции могут использовать друг друга. __Точками входа__, то есть функциями, с которых программа __начинает__ выполняться, являются главные функции `main`. -Программа может содержать любое количество главных функций -(в том числе и ни одной, но в этом случае запустить программу вам не удастся -- -её функции, однако, по-прежнему можно тестировать с помощью тестовых функций). +Программа может содержать любое количество главных функций. +Программа может не содержать их вообще, но в этом случае её не удастся запустить -- +функции программы, однако, по-прежнему можно тестировать с помощью тестовых функций. Когда написанная программа передаётся пользователю, она собирается и пакуется, при этом указывается, с какой конкретно главной функции следует начать её исполнение. Подробнее про упаковку программ можно прочитать вот здесь: https://docs.oracle.com/javase/tutorial/deployment/jar/manifestindex.html. == Упражнения -Откройте файл `srс/lesson1/task1/Simple.kt` в проекте `KotlinAsFirst`. +Откройте файл `src/lesson1/task1/Simple.kt` в проекте `KotlinAsFirst`. В файле перечислено некоторое количество задач на этот раздел в форме: [source,kotlin] @@ -459,17 +459,17 @@ fun doSomething(arg: Int): Int = TODO() Если задача решена верно, вы увидите в этом окне зелёную надпись All Tests Passed, в противном случае -- 1 test failed с описанием проблемы вида: -``` +---- java.lang.AssertionError: Expected : Actual : -``` +---- Ниже вы увидите ссылку на строчку тестовой функции, проверка в которой оказалась неудачной. Expected -- это ожидаемое значение результата, а Actual -- реально полученное. Исправьте все ошибки и добейтесь прохождения теста. -Внутри файла `srс/lesson1/task1/Simple.kt` добавьте главную функцию `main`. +Внутри файла `src/lesson1/task1/Simple.kt` добавьте главную функцию `main`. Вызовите в ней написанную вами функцию с произвольными аргументами и выведите результат на консоль с помощью `println`, например: diff --git a/tutorial/chapter02.adoc b/tutorial/chapter02.adoc index 469b1f337..2da06667e 100644 --- a/tutorial/chapter02.adoc +++ b/tutorial/chapter02.adoc @@ -1,6 +1,6 @@ = 2. Ветвления -До этого мы рассматривали ситуации, когда вычисление результата функций происходило без рассмотрения вариантов. +До этого мы рассматривали ситуации, когда вычисление результата функций происходило без анализа вариантов. Это возможный, но сравнительно редкий случай в программировании. Существует очень много задач, где приходится рассматривать различные варианты, и для этой цели программисты придумали __ветвления__. @@ -166,14 +166,22 @@ fun pointInsideCircle() { [source,kotlin] ---- // Фрагмент программы... -val x = 0.5 -val y = 0.5 -// Пересечение: логическое И -if (pointInsideCircle(x, y, 0.0, 0.0, 1.0) && pointInsideCircle(x, y, 1.0, 1.0, 1.0)) { ... } -// Объединение: логическое ИЛИ -if (pointInsideCircle(x, y, 0.0, 0.0, 1.0) || pointInsideCircle(x, y, 1.0, 1.0, 1.0)) { ... } -// Не принадлежит -if (!pointInsideCircle(x, y, 0.0, 0.0, 1.0)) { ... } +fun foo() { + val x = 0.5 + val y = 0.5 + // Пересечение: логическое И + if (pointInsideCircle(x, y, 0.0, 0.0, 1.0) && pointInsideCircle(x, y, 1.0, 1.0, 1.0)) { + doSomething() + } + // Объединение: логическое ИЛИ + if (pointInsideCircle(x, y, 0.0, 0.0, 1.0) || pointInsideCircle(x, y, 1.0, 1.0, 1.0)) { + doSomething() + } + // Не принадлежит + if (!pointInsideCircle(x, y, 0.0, 0.0, 1.0)) { + doSomething() + } +} ---- В этом примере используются логические __операции__: @@ -194,12 +202,12 @@ if (!pointInsideCircle(x, y, 0.0, 0.0, 1.0)) { ... } * при b равном 0 не имеет решений (или имеет бесконечно много) * при c / b > 0 также не имеет решений * в противном случае минимальный корень -- это `x = -sqrt(-c / b)` - 1. Рассчитаем дискриминант `d = b^2^ - 4ac`. - 1. Если d меньше 0, у квадратного уравнения нет решений, как и у биквадратного. - 1. В противном случае найдём корни квадратного уравнения `y~1~ = (-b + sqrt(d))/(2a)` и `y~2~ = (-b - sqrt(d))/(2a)`. - 1. Вычислим `y~3~ = Max(y~1~, y~2~)`. - 1. Если y~3~ < 0, у уравнения `x^2^ = y~3~` нет решений. - 1. В противном случае, минимальный корень биквадратного уравнения -- это `x = -sqrt(y~3~)`. + 2. Рассчитаем дискриминант `d = b^2^ - 4ac`. + 3. Если d меньше 0, у квадратного уравнения нет решений, как и у биквадратного. + 4. В противном случае найдём корни квадратного уравнения `y~1~ = (-b + sqrt(d))/(2a)` и `y~2~ = (-b - sqrt(d))/(2a)`. + 5. Вычислим `y~3~ = Max(y~1~, y~2~)`. + 6. Если y~3~ < 0, у уравнения `x^2^ = y~3~` нет решений. + 7. В противном случае, минимальный корень биквадратного уравнения -- это `x = -sqrt(y~3~)`. Запишем теперь то же самое на Котлине. Для обозначения ситуации, когда решений нет, будем использовать специальную константу `Double.NaN`, так называемое не-число. @@ -240,12 +248,12 @@ fun minBiRoot(a: Double, b: Double, c: Double): Double { Всегда ли может отсутствовать ветка **else**? Нет, не всегда. Это зависит от **контекста**, то есть конкретного варианта использования **if..else**. -В примере вроде `val x = if (condition) 1 else 2` исчезнование ветки **else** не позволит функции "понять", +В примере вроде `val x = if (condition) 1 else 2` исчезновение ветки **else** не позволит функции "понять", чему же должно быть равно значение `x`, что приведёт к ошибке: -``` +---- 'if' must have both main and 'else' branches if used as an expression. -``` +---- В переводе с английского -- оператор **if** должен иметь как главную ветку, так и ветку **else**, если он используется как выражение. @@ -258,18 +266,18 @@ fun minBiRoot(a: Double, b: Double, c: Double): Double { По умолчанию, **if** может иметь только __один__ оператор как в главной ветке, так и в ветке **else**. Если в случае истинности или ложности условия необходимо выполнить несколько операторов, их следует заключить в фигурные скобки, образуя __блок__ операторов. -Блок операторов выполняется последовательно, так же, как и тело функции. +Блок операторов выполняется последовательно, так же как и тело функции. Блок может содержать любые операторы, в том числе и другие операторы **if**. Проверим нашу реализацию `minBiRoot` с помощью тестовой функции. Для этого нам необходимо проверить различные случаи: 1. a = b = 0, например 0x^4^ + 0x^2^ + 1 = 0 -- корней нет. - 1. a = 0, c / b > 0, например 0x^4^ + 1x^2^ + 2 = 0 -- корней нет. - 1. a = 0, c / b < 0, например 0x^4^ + 1x^2^ - 4 = 0 -- корни есть, в данном случае минимальный из них -2. - 1. d < 0, например 1x^4^ -2x^2^ + 4 = 0 -- корней нет. - 1. d > 0, но оба корня y отрицательны, например 1x^4^ + 3x^2^ + 2 = 0, y~1~ = -2, y~2~ = -1, корней нет. - 1. d > 0, хотя бы один корень y положителен, например 1x^4^ - 3x^2^ + 2 = 0, y~1~ = 1, y~2~ = 2, минимальный корень -1.41. + 2. a = 0, c / b > 0, например 0x^4^ + 1x^2^ + 2 = 0 -- корней нет. + 3. a = 0, c / b < 0, например 0x^4^ + 1x^2^ - 4 = 0 -- корни есть, в данном случае минимальный из них -2. + 4. d < 0, например 1x^4^ -2x^2^ + 4 = 0 -- корней нет. + 5. d > 0, но оба корня y отрицательны, например 1x^4^ + 3x^2^ + 2 = 0, y~1~ = -2, y~2~ = -1, корней нет. + 6. d > 0, хотя бы один корень y положителен, например 1x^4^ - 3x^2^ + 2 = 0, y~1~ = 1, y~2~ = 2, минимальный корень -1.41. Тестовая функция может выглядеть так: diff --git a/tutorial/chapter03.adoc b/tutorial/chapter03.adoc index 57d5e0640..503f7dcde 100644 --- a/tutorial/chapter03.adoc +++ b/tutorial/chapter03.adoc @@ -61,9 +61,9 @@ fun factorial(n: Int): Double { Если бы мы объявили переменную `result` как `val result = 1.0`, то в этой строчке функции мы получили бы ошибку: -``` +---- Val cannot be reassigned -``` +---- В последнем операторе `return result` определяется окончательный результат вычисления факториала. @@ -173,7 +173,7 @@ fun isPrime(n: Int): Boolean { так как на большие числа `n` всё равно делится не будет. Более того, достаточно ограничится интервалом от 2 до √n -- если `n` и делится на какое-то большее √n число (например, 50 делится на 10), -то оно будет делится и на какое-то меньшее число (в данном случае, 50 делится на 5=50/10). +то оно будет делиться и на какое-то меньшее число (в данном случае, 50 делится на 5=50/10). [source,kotlin] ---- @@ -191,7 +191,7 @@ fun isPrime(n: Int): Boolean { а после вычисления -- функцией `.toInt()` для получения целого числа из вещественного. Обе эти встроенные в Котлин функции имеют необычную для начинающих форму записи, которая читается как "n преобразовать к Double", "... преобразовать к Int". -Вместо того, чтобы записать аргумент внутри круглых скобок `toDouble(n)`, +Вместо того чтобы записать аргумент внутри круглых скобок `toDouble(n)`, мы записываем его перед именем функции, отделяя его от имени символом точки. Подобный аргумент функции называется её __получателем__ (receiver), в будущем мы ещё неоднократно столкнёмся с подобной формой записи. @@ -342,7 +342,7 @@ fun digitCountInNumber(n: Int, m: Int): Int = == Упражнения -Откройте файл `srс/lesson3/task1/Loop.kt` в проекте `KotlinAsFirst`. Выберите любую из задач в нём. +Откройте файл `src/lesson3/task1/Loop.kt` в проекте `KotlinAsFirst`. Выберите любую из задач в нём. Придумайте её решение (итеративное или рекурсивное) и запишите его в теле соответствующей функции. Откройте файл `test/lesson3/task1/Tests.kt`, diff --git a/tutorial/chapter04.adoc b/tutorial/chapter04.adoc index 0547a3481..aaa944cf5 100644 --- a/tutorial/chapter04.adoc +++ b/tutorial/chapter04.adoc @@ -50,9 +50,9 @@ fun biRoots(a: Double, b: Double, c: Double): List { с той лишь разницей, что здесь мы ищем все имеющиеся корни: 1. Первый **if** рассматривает тривиальный случай a = 0 и более простое уравнение bx^2^ = -c. Оно либо не имеет корней (с / b > 0), либо имеет один корень 0 (c / b = 0), либо два корня (c / b < 0). - 1. Затем мы делаем замену y = x^2^ и считаем дискриминант d = b^2^ - 4ac. Если он отрицателен, уравнение не имеет корней. - 1. Если дискриминант равен 0, уравнение ay^2^ + by + c = 0 имеет один корень. В зависимости от его знака, биквадратное уравнение либо не имеет корней, либо имеет один корень 0, либо имеет два корня. - 1. В противном случае дискриминант положителен и уравнение ay^2^ + by + c = 0 имеет два корня. Каждый из них, в зависимости от его знака, превращается в ноль, один или два корней биквадратного уравнения. + 2. Затем мы делаем замену y = x^2^ и считаем дискриминант d = b^2^ - 4ac. Если он отрицателен, уравнение не имеет корней. + 3. Если дискриминант равен 0, уравнение ay^2^ + by + c = 0 имеет один корень. В зависимости от его знака, биквадратное уравнение либо не имеет корней, либо имеет один корень 0, либо имеет два корня. + 4. В противном случае дискриминант положителен и уравнение ay^2^ + by + c = 0 имеет два корня. Каждый из них, в зависимости от его знака, превращается в ноль, один или два корней биквадратного уравнения. Посмотрите на тип результата функции `biRoots` -- он указан как `List`. `List` в Котлине -- это и есть список. @@ -60,7 +60,7 @@ fun biRoots(a: Double, b: Double, c: Double): List { то есть `List` вместе -- это список вещественных чисел. Для создания списков, удобно использовать функцию `listOf()`. -Аргументы этой функции -- это элементы создаваемого списка, их может быть произвольное количество (в том числе 0). +Аргументы этой функции -- это элементы создаваемого списка, их может быть произвольное количество, в том числе 0. В ряде случаев, когда биквадратное уравнение не имеет корней, функция `biRoots` возвращает пустой список результатов. В последнем, самом сложном случае, когда уравнение ay^2^ + by + c = 0 имеет два корня y~1~ и y~2~, @@ -168,7 +168,6 @@ fun biRoots() { Запустим теперь написанную тестовую функцию. Мы получим проваленный тест из-за последней проверки: -[source,kotlin] ---- org.opentest4j.AssertionFailedError: expected: <[-2.0, -1.0, 1.0, 2.0]> but was: <[-2.0, 2.0, -1.0, 1.0]> ---- @@ -201,22 +200,23 @@ fun biRoots() { Перечислим некоторые операции над списками, имеющиеся в библиотеке языка Котлин: - 1. `listOf(...)` -- создание нового списка. - 1. `list1 + list2` -- сложение двух списков, сумма списков содержит все элементы их обоих. - 1. `list + element` -- сложение списка и элемента, сумма содержит все элементы `list` и дополнительно `element` - 1. `list.size` -- получение размера списка (Int). - 1. `list.isEmpty()`, `list.isNotEmpty()` -- получение признаков пустоты и непустоты списка (Boolean). - 1. `list[i]` -- индексация, то есть получение __элемента__ списка с целочисленным __индексом__ (номером) `i`. По правилам Котлина, в списке из `n` элементов они имеют индексы, начинающиеся с нуля: 0, 1, 2, ..., последний элемент списка имеет индекс `n - 1`. То есть, при использовании записи `list[i]` должно быть справедливо `i >= 0 && i < list.size`. В противном случае выполнение программы будет прервано с ошибкой (использование индекса за пределами границ списка). - 1. `list.sublist(from, to)` -- создание списка меньшего размера (подсписка), в который войдут элементы списка `list` с индексами `from`, `from + 1`, ..., `to - 2`, `to - 1`. Элемент с индексом `to` не включается. - 1. `element in list` -- проверка принадлежности элемента `element` списку `list`. - 1. `for (element in list) { ... }` -- цикл **for**, перебирающий все элементы списка `list`. - 1. `list.first()` -- получение первого элемента списка (если список пуст, выполнение программы будет прервано с ошибкой). - 1. `list.last()` -- получение последнего элемента списка (аналогично). - 1. `list.indexOf(element)` -- поиск индекса элемента `element` в списке `list`. Результат этой функции равен -1, если элемент в списке отсутствует. В противном случае, при обращении к списку `list` по вычисленному индексу мы получим `element`. - 1. `list.min()`, `list.max()` -- поиск минимального и максимального элемента в списке. - 1. `list.sum()` -- сумма элементов в списке. - 1. `list.sorted()`, `list.sortedDescending()` -- построение отсортированного списка (по возрастанию или по убыванию) из имеющегося. - 1. `list1 == list2` -- сравнение двух списков на равенство. Списки равны, если равны их размеры и соответствующие элементы. + * `listOf(...)` -- создание нового списка. + * `list1 + list2` -- сложение двух списков, сумма списков содержит все элементы их обоих. + * `list + element` -- сложение списка и элемента, сумма содержит все элементы `list` и дополнительно `element` + * `list.size` -- получение размера списка (Int). + * `list.isEmpty()`, `list.isNotEmpty()` -- получение признаков пустоты и непустоты списка (Boolean). + * `list[i]` -- индексация, то есть получение __элемента__ списка с целочисленным __индексом__ (номером) `i`. По правилам Котлина, в списке из `n` элементов они имеют индексы, начинающиеся с нуля: 0, 1, 2, ..., последний элемент списка имеет индекс `n - 1`. То есть, при использовании записи `list[i]` должно быть справедливо `i >= 0 && i < list.size`. В противном случае выполнение программы будет прервано с ошибкой (использование индекса за пределами границ списка). + * `list.sublist(from, to)` -- создание списка меньшего размера (подсписка), в который войдут элементы списка `list` с индексами `from`, `from + 1`, ..., `to - 2`, `to - 1`. Элемент с индексом `to` не включается. + * `element in list` -- проверка принадлежности элемента `element` списку `list`. + * `for (element in list) { ... }` -- цикл **for**, перебирающий все элементы списка `list`. + * `list.first()` -- получение первого элемента списка (если список пуст, выполнение программы будет прервано с ошибкой). + * `list.last()` -- получение последнего элемента списка (аналогично). + * `list.indexOf(element)` -- поиск индекса элемента `element` в списке `list`. Результат этой функции равен -1, если элемент в списке отсутствует. В противном случае при обращении к списку `list` по вычисленному индексу мы получим `element`. + * `list.min()`, `list.max()` -- поиск минимального и максимального элемента в списке. + * `list.minOrNull()`, `list.maxOrNull()` -- то же, но возвращает `null` для пустого списка. + * `list.sum()` -- сумма элементов в списке. + * `list.sorted()`, `list.sortedDescending()` -- построение отсортированного списка (по возрастанию или по убыванию) из имеющегося. + * `list1 == list2` -- сравнение двух списков на равенство. Списки равны, если равны их размеры и соответствующие элементы. == Мутирующие списки @@ -225,11 +225,11 @@ __Мутирующий список__ является разновидност мутирующий список может изменяться по ходу выполнения программы или функции. Это означает, что мутирующий список позволяет: - 1. Изменять своё содержимое операторами `list[i] = element`. - 1. **Добавлять** элементы в конец списка, с увеличением размера на 1: `list.add(element)`. - 1. **Удалять** элементы из списка, с уменьшением размера на 1 (если элемент был в списке): `list.remove(element)`. - 1. **Удалять** элементы из списка по индексу, с уменьшением размера на 1: `list.removeAt(index)`. - 1. **Вставлять** элементы в середину списка: `list.add(index, element)` -- вставляет элемент `element` по индексу `index`, сдвигая все последующие элементы на 1, например `listOf(1, 2, 3).add(1, 7)` даст результат `[1, 7, 2, 3]`. + * Изменять своё содержимое операторами `list[i] = element`. + * **Добавлять** элементы в конец списка, с увеличением размера на 1: `list.add(element)`. + * **Удалять** элементы из списка, с уменьшением размера на 1 (если элемент был в списке): `list.remove(element)`. + * **Удалять** элементы из списка по индексу, с уменьшением размера на 1: `list.removeAt(index)`. + * **Вставлять** элементы в середину списка: `list.add(index, element)` -- вставляет элемент `element` по индексу `index`, сдвигая все последующие элементы на 1, например `listOf(1, 2, 3).add(1, 7)` даст результат `[1, 7, 2, 3]`. Для создания мутирующего списка можно использовать функцию `mutableListOf(...)`, аналогичную `listOf(...)`. @@ -468,22 +468,22 @@ __Экранирование__ может применяться и для до Перечислим наиболее распространённые операции над строками: - 1. `string1 + string2` -- сложение или конкатенация строк, приписывание второй строки к первой. - 1. `string + char` -- сложение строки и символа (с тем же смыслом). - 1. `string.length` -- длина строки, то есть количество символов в ней. - 1. `string.isEmpty()`, `string.isNotEmpty()` -- получение признаков пустоты и непустоты строки (Boolean). - 1. `string[i]` -- индексация, то есть получение символа по целочисленному __индексу__ (номеру) `i` в диапазоне от 0 до `string.length - 1`. - 1. `string.substring(from, to)` -- создание строки меньшего размера (подстроки), в который войдут символы строки `string` с индексами `from`, `from + 1`, ..., `to - 2`, `to - 1`. Символ с индексом `to` не включается. - 1. `char in string` -- проверка принадлежности символа `char` строке `string`. - 1. `for (char in list) { ... }` -- цикл **for**, перебирающий все символы строки `string`. - 1. `string.first()` -- получение первого символа строки. - 1. `string.last()` -- получение последнего символа строки. - 1. `string.indexOf(char, startFrom)` -- найти индекс первого символа `char` в строке, начиная с индекса `startFrom`. - 1. `string.lastIndexOf(char, startFrom)` -- найти индекс первого символа `char` в строке, идя с конца и начиная с индекса `startFrom`. - 1. `string.toLowerCase()` -- преобразование строки в нижний регистр (то есть, замена прописных букв строчными). - 1. `string.toUpperCase()` -- преобразование строки в верхний регистр (замена строчных букв прописными). - 1. `string.capitalize()` -- замена ПЕРВОЙ буквы строки прописной. - 1. `string.trim()` -- удаление из строки пробельных символов в начале и конце: `" ab c "` преобразуется в `"ab c"` + * `string1 + string2` -- сложение или конкатенация строк, приписывание второй строки к первой. + * `string + char` -- сложение строки и символа (с тем же смыслом). + * `string.length` -- длина строки, то есть количество символов в ней. + * `string.isEmpty()`, `string.isNotEmpty()` -- получение признаков пустоты и непустоты строки (Boolean). + * `string[i]` -- индексация, то есть получение символа по целочисленному __индексу__ (номеру) `i` в диапазоне от 0 до `string.length - 1`. + * `string.substring(from, to)` -- создание строки меньшего размера (подстроки), в который войдут символы строки `string` с индексами `from`, `from + 1`, ..., `to - 2`, `to - 1`. Символ с индексом `to` не включается. + * `char in string` -- проверка принадлежности символа `char` строке `string`. + * `for (char in list) { ... }` -- цикл **for**, перебирающий все символы строки `string`. + * `string.first()` -- получение первого символа строки. + * `string.last()` -- получение последнего символа строки. + * `string.indexOf(char, startFrom)` -- найти индекс первого символа `char` в строке, начиная с индекса `startFrom`. + * `string.lastIndexOf(char, startFrom)` -- найти индекс первого символа `char` в строке, идя с конца и начиная с индекса `startFrom`. + * `string.toLowerCase()` -- преобразование строки в нижний регистр, то есть замена прописных букв строчными. + * `string.toUpperCase()` -- преобразование строки в верхний регистр, то есть замена строчных букв прописными. + * `string.capitalize()` -- замена ПЕРВОЙ буквы строки прописной. + * `string.trim()` -- удаление из строки пробельных символов в начале и конце: `" ab c "` преобразуется в `"ab c"` В качестве примера рассмотрим функцию, проверяющую, является ли строка палиндромом. В палиндроме первый символ должен быть равен последнему, второй предпоследнему и т.д. @@ -523,7 +523,9 @@ fun List.joinToString( limit: Int = -1, truncated: String = "...", transform: (T) -> String = { "$it" } -): String { ... } +): String { + // ... +} ---- Получателем данной функции может быть список с произвольным типом элементов: `List`. @@ -603,7 +605,7 @@ fun squares(vararg array: Int) = squares(array.toList()) При вызове подобной функции вместо параметра `array` может быть подставлено любое (в том числе ноль) количество аргументов указанного типа, в данном случае -- **Int**. -Я не случайно назвал параметр именно `array`: функция может использовать такой параметр, +Я неслучайно назвал параметр именно `array`: функция может использовать такой параметр, как будто это массив соответствующего типа -- в данном случае **Array**. В теле функции мы используем вызов `array.toList()`, чтобы свести задачу к уже решённой -- функция `squares`, принимающая аргумент типа **List**, у нас уже имеется. @@ -622,7 +624,7 @@ fun squares(array: Array) = squares(*array) == Упражнения -Откройте файл `srс/lesson4/task1/List.kt` в проекте `KotlinAsFirst`. Выберите любую из задач в нём. +Откройте файл `src/lesson4/task1/List.kt` в проекте `KotlinAsFirst`. Выберите любую из задач в нём. Придумайте её решение и запишите его в теле соответствующей функции. Откройте файл `test/lesson4/task1/Tests.kt`, diff --git a/tutorial/chapter04_5.adoc b/tutorial/chapter04_5.adoc index 9f5effaa3..b7dc1773f 100644 --- a/tutorial/chapter04_5.adoc +++ b/tutorial/chapter04_5.adoc @@ -2,14 +2,14 @@ В этом разделе мы поговорим о том, как различные элементы программы сохраняются в памяти PC. Поскольку данное пособие посвящено Котлину как JVM-языку, они сохраняются в памяти JVM как виртуального компьютера. -Тем не менее, организация памяти JVM имеет немало общего с организацией памяти настоящего компьютера. +Тем не менее организация памяти JVM имеет немало общего с организацией памяти настоящего компьютера. Поговорим об этом подробнее. == Биты, байты и килобайты Единицей измерения информации (например, хранящейся в памяти или на диске) является __бит__ -- элементарная ячейка, имеющая всего два состояния (0 и 1, либо **true** и **false**). -Биты объединяеются в __байты__ -- в одном байте 2^3^ = 8 бит. +Биты объединяются в __байты__ -- в одном байте 2^3^ = 8 бит. Байты, в свою очередь, объединяются в __килобайты__ (Кб) -- в одном килобайте 2^10^ = 1024 байт. Обратите внимание -- не 1000 байт, а именно 1024 байт, поскольку компьютеру удобнее, чтобы данные размерности были степенями двойки. @@ -28,13 +28,13 @@ __мегабайт__ (Мб) 1000 килобайт и __гигабайт__ (Гб) == Системы счисления -При хранении чисел в компьютере используется не привычная человеку десятичная система счисления, +При хранении чисел в компьютере используется не привычная для человека десятичная система счисления, а более удобная для компьютера двоичная система. При этом используется всего две цифры: 0 и 1. К примеру, число 75 в двоичной системе представляется как `1001011 = 1 х 64 + 0 х 32 + 0 х 16 + 1 x 8 + 0 x 4 + 1 x 2 + 1 x 1`. Поскольку двоичная запись числа является очень длинной, иногда (для удобства связи между человеком и компьютером) -программисты применяют восьмеричную системму счисления (в ней восемь цифр от 0 до 7) +программисты применяют восьмеричную систему счисления (в ней восемь цифр от 0 до 7) и шестнадцатеричную систему счисления (в ней, помимо традиционных цифр от 0 до 9, используются цифры a = 10, b = 11, c = 12, d = 13, e = 14, f = 15). Удобство состоит в том, что одной восьмеричной цифре соответствует ровно три двоичных -- к примеру, 5 = 101, @@ -61,7 +61,7 @@ __мегабайт__ (Мб) 1000 килобайт и __гигабайт__ (Гб) * **Byte** -- целое число от -128 до 127, занимает один байт; * **Short** -- целое число от -32768 до 32767, занимает два байта; - * **Long** -- целое число от `-2^63^` до `2^63^ - 1`, занимает восемь байт. + * **Long** -- целое число от `-2^63^` до `2^63^ - 1`, занимает восемь байтов. Подобные целые числа устроены по тому же принципу, что и числа типа **Int**. Следует иметь в виду, что при работе с типом **Long** нельзя использовать литералы типа **Int**: @@ -88,12 +88,12 @@ val long2: Long = 42L // OK! Как мантисса, так и порядок хранятся в двоичной системе счисления, причём один бит всегда выделяется под знак. В Котлине имеется два вещественных типа. -Один из них, уже известный нам **Double**, занимает в памяти восемь байт. +Один из них, уже известный нам **Double**, занимает в памяти восемь байтов. При этом 53 бита выделяется на мантиссу, а 11 бит на порядок. Более короткий тип **Float** занимает четыре байта: 24 бита на мантиссу и 8 бит на порядок. Вещественные литералы по умолчанию имеют тип **Double**. -Для того, чтобы создать литерал типа **Float**, необходимо использовать суффикс `F`: +Для того чтобы создать литерал типа **Float**, необходимо использовать суффикс `F`: [source,kotlin] ---- diff --git a/tutorial/chapter05.adoc b/tutorial/chapter05.adoc index 05d335e49..d2981fda5 100644 --- a/tutorial/chapter05.adoc +++ b/tutorial/chapter05.adoc @@ -6,7 +6,7 @@ Понятно, что это далеко не единственный тип структур данных, которые существуют, и сегодня мы с вами познакомимся с двумя новыми структурами данных. Чтобы понять первую структуру данных --- **ассоциативный массив** --- далеко ходить не надо, достаточно вспомнить о такой штуке как толковый словарь. -Он связывает элементы отношением "ключ"-"значение": для определенных слов (ключей) он содержит их описание (значения), для всех остальных --- не содержит ничего. +Словарь связывает элементы отношением "ключ"-"значение": для определенных слов (ключей) он содержит их описание (значения), для всех остальных --- не содержит ничего. Подобной структурой обладают, на самом деле, многие вещи: набор товаров с их ценами, список контактов в телефоне, рестораны и рейтинги, и т.д. Основная операция, которую они поддерживают, --- это достать значение, соответствующее интересующему нас ключу, т.е. то, что вы делаете, когда ищете значение неизвестного вам слова в словаре. @@ -38,14 +38,14 @@ fun shoppingListCost( Данный **параметризованный тип** `Map` и является типом ассоциативного массива, у которого типовой параметр `Key` задает тип ключей, а `Value` --- тип значений. В нашем случае набор товаров с ценами имеет тип `Map`, т.е. для названия товара содержит его цену в виде действительного числа. -Для того, чтобы считать общую стоимость выбранного набора товаров, мы заводим новую изменяемую переменную `totalCost`, которая изначально равна нулю и которую мы возвращаем как результат в конце функции при помощи `return`. +Для того чтобы рассчитать общую стоимость выбранного набора товаров, мы заводим новую изменяемую переменную `totalCost`, которая изначально равна нулю. В конце функции мы вернём значение этой переменной как результат при помощи `return`. После этого мы проходимся по списку покупок при помощи цикла `for` и для каждой покупки пытаемся достать ее цену из нашего ассоциативного массива при помощи операции индексирования. -В отличии от индексирования для списка, операция **индексирования** `map[key]` для ассоциативного массива пытается достать элемент не по какому-то целочисленному индексу, а по ключу соответствующего типа --- в нашем случае, по названию товара, т.е. строке. +В отличие от индексирования для списка, операция **индексирования** `map[key]` для ассоциативного массива пытается достать элемент не по какому-то целочисленному индексу, а по ключу соответствующего типа --- в нашем случае, по названию товара, т.е. строке. А вот дальше мы знакомимся с такой очень интересной вещью как `null`. Как мы отметили раньше, ассоциативный массив содержит пары "ключ"-"значение", однако для некоторых ключей соответствующего им значения может не быть. -Вместе с тем, просто так вернуть *"ничего"* мы не можем. -Как раз для таких ситуаций и необходим объект `null` --- операция индексирования для ассоциативного массива возвращает `null` в случае, если для заданного ключа нет значения. После того, как мы проверили, что для товара есть его стоимость (`itemCost != null`), мы добавляем ее к общей стоимости набора; в противном случае мы считаем, что данная покупка просто игнорируется. +Вместе с тем просто так вернуть *"ничего"* мы не можем. +Как раз для таких ситуаций и необходим объект `null` --- операция индексирования для ассоциативного массива возвращает `null` в случае, если для заданного ключа нет значения. После того как мы проверили, что для товара есть его стоимость (`itemCost != null`), мы добавляем ее к общей стоимости набора; в противном случае мы считаем, что данная покупка просто игнорируется. Попробуем написать тесты для нашей функции. @@ -368,7 +368,7 @@ fun removeFillerWords( Если подумать, то станет понятно, что массив или список с элементами, --- это тоже практически множество, необходимо только каким-либо образом обеспечить уникальность элементов. Тогда наш вопрос еще более актуален --- зачем вообще иметь отдельный, специальный тип для множества? -Тут мы с вами впервые знакомимся с таким понятием, как **эффективность** структуры данных. +Тут мы с вами знакомимся с таким понятием, как **эффективность** структуры данных. Решение на основе списка, конечно, работает, но **сложность** проверки того, входит или нет какой-то элемент в список, значительно больше, чем аналогичная сложность для множества `Set`. Это связано именно с тем, что `Set` специализирован для того, чтобы представлять множество; и все типичные для множества операции реализованы как можно более эффективно. Более подробно вопросы эффективности вы будете изучать дальше на вашем пути обучения программированию, пока что можно запомнить очень простую идею --- множество элементов лучше представлять как множество типа `Set`. @@ -485,7 +485,7 @@ fun addNullables(a: Int?, b: Int?): Int = (a ?: 0) + (b ?: 0) Еще один `null`-специфичный оператор --- это оператор безопасного вызова `?.`. Он используется в случаях, когда необходимо *безопасно* вызвать функцию над объектом, который может быть `null`. Выражение `a?.foo(b, c)` возвращает результат вызова функции `foo` с аргументами `b` и `c` над получателем `a`, если `a` не равен `null`; в противном случае возвращается `null`. -Пусть нам нужно вернуть сумму элементов в `nullable` cписке. +Пусть нам нужно вернуть сумму элементов в `nullable` списке. [source,kotlin] ---- @@ -567,7 +567,7 @@ fun shoppingListCost( == Упражнения -Откройте файл `srс/lesson5/task1/Map.kt` в проекте `KotlinAsFirst`. +Откройте файл `src/lesson5/task1/Map.kt` в проекте `KotlinAsFirst`. Выберите любую из задач в нём. Придумайте её решение и запишите его в теле соответствующей функции. @@ -580,7 +580,8 @@ fun shoppingListCost( Если вы очень хотите решить эти задачи, но самостоятельно у вас это не получается, попробуйте поискать возможные подходы к их решению в Интернете. Убедительная просьба не слепо копировать готовое решение, а постараться разобраться в нем и понять его основную идею. -Отдельно отметим, что, как мы обсуждали с вами в уроке, многие из задач урока 5 могут быть решены без использования множеств или ассоциативных массивов, однако постарайтесь все же использовать именно них. +Многие из задач урока 5 могут быть решены без использования множеств или ассоциативных массивов -- +тем не менее, постарайтесь использовать именно их. Подумайте, какие вычисления вы можете не делать при использовании множеств или ассоциативных массивов? Как вам кажется, делает ли это ваше решение более эффективным? diff --git a/tutorial/chapter07.adoc b/tutorial/chapter07.adoc index a79b489a7..2808b64a5 100644 --- a/tutorial/chapter07.adoc +++ b/tutorial/chapter07.adoc @@ -103,59 +103,66 @@ fun alignFile(inputName: String, lineLength: Int, outputName: String) { Локальными в Котлине называются функции, определённые внутри другой функции. Локальные функции во многих отношениях подобны обычным функциям, за некоторыми исключениями: -* Локальную функцию может вызывать толька та функция, в которой она определена (в примере `alignFile`), и только *после* места определения локальной функции +* Локальную функцию может вызывать только та функция, в которой она определена (в примере `alignFile`), и только *после* места определения локальной функции * Локальная функция имеет право обращаться к параметрам и переменным внешней функции (`alignFile`). В том числе, она имеет право изменять значения переменных `var` == За занавесом: чтение из файла Пакет `java.io` позволяет работать с файлами на трёх разных уровнях: -1. Уровень отдельных байт. В этом случае файл воспринимается как массив или, точнее, как поток байт. Поток, в отличие от массива, можно только перебирать, с сильно ограниченными возможностями по возвращению назад. Для этой цели имеется тип `java.io.InputStream`. -1. Уровень символов. В этом случае файл воспринимается уже как поток символов типа `Char`, то есть каждые несколько байт файла превращаются в определённый символ -- с учётом заданной кодировки файла. Для этой цели имеется тип `java.io.InputStreamReader`, который внутри себя использует `InputStream` для чтения байт. -1. Уровень строк. На этом уровне файл воспринимается как набор строк `String`, составленных из символов по определённым правилам -- чаще всего используется разделение по отдельным строкам файла. Эту роль выполняет тип `java.io.BufferedReader`, использующий внутри себя `InputStreamReader` для чтения символов. +1. Уровень отдельных байтов. В этом случае файл воспринимается как массив или, точнее, как поток байтов. Поток, в отличие от массива, можно только перебирать, с сильно ограниченными возможностями по возвращению назад. Для этой цели имеется тип `java.io.InputStream`. +2. Уровень символов. В этом случае файл воспринимается уже как поток символов типа `Char`, то есть каждые несколько байт файла превращаются в определённый символ -- с учётом заданной кодировки файла. Для этой цели имеется тип `java.io.InputStreamReader`, который внутри себя использует `InputStream` для чтения байт. +3. Уровень строк. На этом уровне файл воспринимается как набор строк `String`, составленных из символов по определённым правилам -- чаще всего используется разделение по отдельным строкам файла. Эту роль выполняет тип `java.io.BufferedReader`, использующий внутри себя `InputStreamReader` для чтения символов. При программировании на Java каждый из этих объектов приходится создавать отдельно -- вначале `InputStream`, потом `InputStreamReader` и, наконец, `BufferedReader`. Библиотека Котлина позволяет создать любой из этих объектов сразу, используя файл-получатель: 1. `file.inputStream()` создаёт байтовый поток. -1. `file.reader()` создаёт читатель символов, используя кодировку по умолчанию. `file.reader(Charset.forName("CP1251"))` создаёт писатель с заданной кодировкой (в данном случае CP1251). -1. Наконец, `file.bufferedReader()` создаёт буферизованный читатель строк. Опять-таки, может быть задана нужная кодировка, иначе используется кодировка по умолчанию. +2. `file.reader()` создаёт читатель символов, используя кодировку по умолчанию. `file.reader(Charset.forName("CP1251"))` создаёт писатель с заданной кодировкой (в данном случае CP1251). +3. Наконец, `file.bufferedReader()` создаёт буферизованный читатель строк. Опять-таки, может быть задана нужная кодировка, иначе используется кодировка по умолчанию. Набор функций у данных трёх объектов различается. У всех у них есть функция `close()`, закрывающая исходный файл в конце работы с потоком. -Также, у них имеется функция высшего порядка `use { ... }`, -выполняющая описанные в лямбде действия и закрывающая файл в конце своей работы автоматически. -Скажем, исходный пример можно было бы переписать с помощью `use` так: +Для гарантированного вызова функции `close()` часто применяется конструкция `try...finally...`: + +[source,kotlin] +---- +fun alignFile(inputName: String, lineLength: Int, outputName: String) { + val writer = File(outputName).bufferedWriter() + try { + doSomethingWithWriter(writer) + } finally { + // Finally-блок выполняется после того, как завершится выполнение try-блока, + // независимо от того, произошло ли в процессе выполнения исключение + // Если мы вошли в блок try, то мы неизбежно выполним и блок finally + writer.close() + } +} +---- + +Ещё лучше с той же целью использовать функцию высшего порядка `use { ... }`. +Эта функция выполняет описанные в лямбде действия и закрывает файл в конце своей работы автоматически. +Скажем, код выше можно было бы переписать с помощью `use` так: [source,kotlin] ---- fun alignFile(inputName: String, lineLength: Int, outputName: String) { File(outputName).bufferedWriter().use { - var currentLineLength = 0 - for (line in File(inputName).readLines()) { - if (line.isEmpty()) { - it.newLine() - if (currentLineLength > 0) { - it.newLine() - currentLineLength = 0 - } - continue - } - for (word in line.split(" ")) { - if (currentLineLength > 0) { - if (word.length + currentLineLength >= lineLength) { - it.newLine() - currentLineLength = 0 - } else { - it.write(" ") - currentLineLength++ - } - } - it.write(word) - currentLineLength += word.length - } - } + doSomethingWithWriter(it) + } +} +---- + +Код самой функции `use` выглядит примерно следующим образом + +[source,kotlin] +---- +fun T.use(f: (T) -> R): R { + return try { + f(this) + } finally { + this.close() } } ---- @@ -167,8 +174,8 @@ fun alignFile(inputName: String, lineLength: Int, outputName: String) { Кроме этого, каждый объект обладает своими методами для чтения информации: 1. `inputStream.read()` читает из `InputStream` очередной байт, возвращая его в виде результата типа `Int`. Если файл закончен, результат этой функции будет -1. `inputStream.read(byteArray)` читает сразу несколько байт, записывая их в массив байт (число прочитанных байт равно размеру массива). `inputStream.read(byteArray, offset, length)` записывает в `byteArray` `length` байт, начиная с индекса `offset`. -1. `reader.read()` читает из `InputStreamReader` очередной символ, возвращая его в виде результата типа `Int`. Здесь используется именно `Int`, а не `Char`, так как, во-первых, символ в общем случае может не поместиться в двухбайтовые тип и, во-вторых, чтобы вернуть -1 в случае неудачи. Есть аналогичные методы для чтения символьного массива (НЕ строки) с возможным указанием смещения и числа символов -- см. выше про байтовый массив. -1. `bufferedReader.readLine()` читает из `BufferedReader` очередную строку (до перевода строки). `bufferedReader.readLines()` читает сразу же все строки. Есть ряд других методов для работы со строками по отдельности. +2. `reader.read()` читает из `InputStreamReader` очередной символ, возвращая его в виде результата типа `Int`. Здесь используется именно `Int`, а не `Char`, так как, во-первых, символ в общем случае может не поместиться в двухбайтовые тип и, во-вторых, чтобы вернуть -1 в случае неудачи. Есть аналогичные методы для чтения символьного массива (НЕ строки) с возможным указанием смещения и числа символов -- см. выше про байтовый массив. +3. `bufferedReader.readLine()` читает из `BufferedReader` очередную строку (до перевода строки). `bufferedReader.readLines()` читает сразу же все строки. Есть ряд других методов для работы со строками по отдельности. Следует отметить, что все функции чтения информации могут бросить исключение `IOException` в том случае, если чтение по какой-либо причине невозможно (например, если файл не существует или недоступен). @@ -178,13 +185,13 @@ fun alignFile(inputName: String, lineLength: Int, outputName: String) { После чтения последней строки файл закрывается. Вариант вызова `file.readLines(charset)` позволяет дополнительно указать кодировку, в которой следует читать файл. Есть и другие варианты высокоуровневых функций чтения файла: 1. `file.forEachLine { line -> ... }`. Эта функция высшего порядка предполагает чтение строк файла по одной, и выполнение операции, указанной в лямбде (...) для каждой из этих строк. Достоинство такого подхода в том, что здесь мы не храним в памяти весь список строк из файла, как делает `file.readLines()`. Это может быть важно, если размер файла большой. С другой стороны, подобный вариант предполагает обработку строк по очереди, от предыдущей к следующей, и не обеспечивает никакой возможности возврата к уже обработанной строке. Вариант вызова `file.forEachLine(charset) { line -> ... }` позволяет дополнительно указать кодировку. -1. `file.useLines { lineSequence -> ... }`. Лямбда в этой функции высшего порядка работает с *последовательностью* строк файла `lineSequence`, тип которой `Sequence`. Тип `Sequence` напоминает типы из библиотеки коллекций `Iterable` и `Collection`, последовательность строк можно перебрать с помощью цикла `for`, на ней можно вызывать ряд функций высшего порядка -- такие, как `map`, `filter`, `forEach` и так далее. Особенность функционирования последовательностей состоит в том, что они *ленивы*. Благодаря этому многие операции с последовательностями -- в частности, `map` и `filter` -- по факту не приводят к чтению их элементов (из файла или, в общем случае, из другого источника). +2. `file.useLines { lineSequence -> ... }`. Лямбда в этой функции высшего порядка работает с *последовательностью* строк файла `lineSequence`, тип которой `Sequence`. Тип `Sequence` напоминает типы из библиотеки коллекций `Iterable` и `Collection`, последовательность строк можно перебрать с помощью цикла `for`, на ней можно вызывать ряд функций высшего порядка -- такие, как `map`, `filter`, `forEach` и так далее. Особенность функционирования последовательностей состоит в том, что они *ленивы*. Благодаря этому многие операции с последовательностями -- в частности, `map` и `filter` -- по факту не приводят к чтению их элементов (из файла или, в общем случае, из другого источника). == За занавесом: запись в файл Запись в файл использует те же три уровня: байты `OutputStream`, символы `OutputStreamWriter` и строки `BufferedWriter`. Для записи байт либо символов используются функции `write`, -аргументом которых может являться целое число (в котором хранится байт или код символа) или массив (опять-таки байт или символов). +аргументом которых может являться целое число (байт или код символа), или массив (опять-таки байтов или символов). Эти функции не имеют результата и бросают `IOException`, если файл недоступен для записи. `BufferedWriter` может использовать функцию `write` также для записи строк. Как и все три вида потоков чтения, @@ -195,15 +202,16 @@ fun alignFile(inputName: String, lineLength: Int, outputName: String) { Поток печати расширяет обычный байтовый поток рядом дополнительных возможностей: 1. `printStream.println(...)` -- вывод заданной строки или строкового представления с последующим переходом на новую строку. -1. `printStream.print(...)` -- то же, но без перехода на новую строку. -1. `printStream.format(formatString, ...)` -- форматированный вывод (происходит по принципу, описанном в разделе 6). +2. `printStream.print(...)` -- то же, но без перехода на новую строку. +3. `printStream.format(formatString, ...)` -- форматированный вывод (происходит по принципу, описанном в разделе 6). == Упражнения -Откройте файл `srс/lesson7/task1/Files.kt` в проекте `KotlinAsFirst`. +Откройте файл `src/lesson7/task1/Files.kt` в проекте `KotlinAsFirst`. Он содержит ряд задач, каждая из которых предполагает наличие входного и/или выходного файла. Решите хотя бы одну-две из имеющихся задач, используя описанные в этом разделе приёмы. Обратите внимание на задачи, помеченные как "Сложная" или "Очень сложная", попробуйте решить одну из них. +Перед решением задач про HTML-файлы полезно прочитать раздел, посвящённый Kotlin DSL (10.2). Протестируйте свою реализацию, используя тесты из `test/lesson7/task1/Tests.kt`. Обратите внимание, что тесты используют готовые входные файлы, расположенные в директории `input` нашего проекта. @@ -228,7 +236,7 @@ fun alignFile(inputName: String, lineLength: Int, outputName: String) { Если вы проходили данный курс на Coursera, после загрузки последнего задания вам придёт письмо от Coursera со ссылкой "Получить сертификат о прохождении курса". -Нажмите на данную ссылку и следуйтё указаниям сайта Coursera. +Нажмите на данную ссылку и следуйте указаниям сайта Coursera. Если вы проходили данный курс в высшей школе https://insys.spbstu.ru[INSYS], вы также можете получить сертификат Coursera, если задания курса выполнены до окончания зачётной недели. diff --git a/tutorial/chapter08_5.adoc b/tutorial/chapter08_5.adoc index 80842b3e7..494752a20 100644 --- a/tutorial/chapter08_5.adoc +++ b/tutorial/chapter08_5.adoc @@ -320,7 +320,7 @@ fun dfs(start: String, finish: String): Int = dfs(this[start], this[finish], set val min = start.neighbors .filter { it !in visited } .mapNotNull { dfs(it, finish, visited + start) } - .min() + .minOrNull() if (min == null) null else min + 1 } ---- @@ -336,7 +336,7 @@ fun dfs(start: String, finish: String): Int = dfs(this[start], this[finish], set private fun dfs(start: Vertex, finish: Vertex): Int? = if (start == finish) 0 else { - val min = start.neighbors.mapNotNull { dfs(it, finish) }.min() + val min = start.neighbors.mapNotNull { dfs(it, finish) }.minOrNull() if (min == null) null else min + 1 } ---- diff --git a/tutorial/chapter10.adoc b/tutorial/chapter10_1.adoc similarity index 97% rename from tutorial/chapter10.adoc rename to tutorial/chapter10_1.adoc index df36a1872..b38ef0547 100644 --- a/tutorial/chapter10.adoc +++ b/tutorial/chapter10_1.adoc @@ -1,4 +1,4 @@ -= 10. Дополнительные главы += 10.1. Дополнительные главы == Разбор и представление арифметических выражений @@ -322,9 +322,3 @@ fun parseExpr(inputName: String, values: List): Map { Откройте файл `srс/lesson10/task1/Regex.kt` в проекте `KotlinAsFirst`. Попробуйте решить единственную задачу в этом разделе про операцию возведения в степень. -== Kotlin DSL - -Раздел о предметно-ориентированных языках на базе Kotlin в этом пособии пока отсутствует. -В качестве свободного чтения вы можете прочитать -https://kotlinlang.org/docs/reference/type-safe-builders.html[раздел официальной справки] -и посмотреть примеры в `srс/lesson10/task2/Html.kt` \ No newline at end of file diff --git a/tutorial/chapter10_2.adoc b/tutorial/chapter10_2.adoc new file mode 100644 index 000000000..f0df25c95 --- /dev/null +++ b/tutorial/chapter10_2.adoc @@ -0,0 +1,192 @@ += 10.2. Дополнительные главы + +== Kotlin DSL + +В этом разделе мы поговорим о так называемых DSL на базе Kotlin. +При изучении современных методов программирования вы будете часто наталкиваться на эту аббревиатуру. +Это может выглядеть страшно, но DSL означает всего лишь "Domain Specific Language" или, +по-русски, предметно-ориентированный язык. По смыслу это значит, что мы на базе Kotlin +создаём какой-то более узкий по объёму возможностей язык для описания какой-то предметной области. + +Классическим примером DSL на базе Kotlin является библиотека `kotlinx.html`. +Она позволяет с помощью языка Kotlin генерировать содержимое HTML-файлов. + +=== Отступление про HTML-файлы + +HTML = HyperText Markup Language = Язык разметки гипертекста. +Это описательный язык (не путать с языком программирования!), +по сути это всего лишь способ задания текстовых файлов с расширенным набором возможностей. +HTML позволяет разбивать "сырой" текст на параграфы, описывать внутри него списки, +таблицы, превращать часть текста в гиперссылки (на странички Интернета), +задавать шрифт и цвет отображаемого текста, +создавать специальные формы для редактирования информации читателем и так далее. + +Основное использование HTML -- Интернет. +Когда браузер (Internet Explorer, Mozilla Firefox, Google Chrome, ...) заходит на Интернет-страничку, +он скачивает с сервера HTML-файл. HTML содержит внутри себя "размеченный текст", а любой из перечисленных +браузеров умеет показывать HTML-файлы пользователю в соответствии с правилами разметки. +Эти правила базируются на так называемых "тегах". Например, тег `

` обозначает начало параграфа, +а тег `

` конец параграфа, при этом между этими тегами находится сам параграф. +Короткий HTML-файл ниже приведёт к изображению в браузере текста, разбитого на два параграфа. + +[source, html] +---- + +

First chapter

+

Second chapter

+ +---- + +Базовые возможности формата HTML как раз и сводятся к использованию подобных пар `` / ``. +Например, в скобки `...` берётся всё содержимое файла, `...` обозначает текст, +отображаемый жирным шрифтом (bold), `...
` таблицу, `
    ...
` непронумерованный список \ +и так далее. Теги могут вкладываться друг в друга по определённым правилам. +Скажем, ряд таблицы `...` существует только внутри таблицы, а ячейка `...` только внутри ряда. + +Посмотрите на пример функции, генерирующей HTML-файл с описанием двумерной таблицы, без помощи DSL. + +[source,kotlin] +---- +fun List>.convertToHtmlTable(): String { + val sb = StringBuilder() + sb.append("") + sb.append("") + sb.append("") + for (row in this) { + sb.append("") + for (data in row) { + sb.append("") + } + sb.append("") + } + sb.append("
") + sb.append(data) + sb.append("
") + sb.append("") + sb.append("") + return sb.toString() +} +---- + +Функция последовательно открывает теги `` и закрывает их ``, вкладывая их друг в друга подобно стеку. +Минус подобного описания "в лоб" состоит в том, что мы сами должны контролировать своевременное закрытие тегов, +а также соблюдение правил их вложения друг в друга. + +=== Структура DSL на примере kotlinx.html + +Библиотека `kotlinx.html` для генерации HTML-файлов использует код следующего вида + +[source,kotlin] +---- +fun List>.convertToHtmlTableUsingKotlinxHtml(): String { + val inputList = this + val sb = StringBuilder() + sb.appendHTML().html { + body { + table { + for (row in inputList) { + tr { + for (data in row) { + td { +data } + } + } + } + } + } + } + return sb.toString() +} +---- + +Эта функция преобразует двумерный список в таблицу, при этом один ряд списка становится одним рядом HTML-таблицы, +а один элемент списка, соответственно, элементом HTML-таблицы. Код построен так, что каждому HTML-тегу, например, +паре `` / `
, соответствует код вида + +[source,kotlin] +---- +table { + // Что-то между тегами +} +---- + +По факту `table { ... }` является вызовом функции высшего порядка, а `{ ... }` лямбдой, передаваемой в эту функцию. + +DSL позволяет возложить контроль за правилами вложения тегов друг в друга и за их закрытием на компилятор Kotlin. +Например, DSL не позволит вам создать ряд таблицы на верхнем уровне, без создания самой таблицы. +Разумеется, работает DSL без помощи колдовства и магии. +В основе описания функций высшего порядка вроде `table { ... }` лежит механизм type-safe builders +(приблизительный перевод -- типо-безопасные построители). + +=== Пример type-safe builder + +Рассмотрим в качестве примера, как работают теги верхнего уровня. + +[source,kotlin] +---- +private class HTML(val sb: StringBuilder) { + fun myBody(init: HTMLBody.() -> Unit): HTMLBody { + val body = HTMLBody(sb) + sb.append("") + body.init() + sb.append("") + return body + } +} +---- + +Это класс, экземпляр которого соответствует содержимому тега ``. +По правилам разметки HTML, внутри данного тега может встретиться тег ``. +Этому тегу будет соответствовать вызов функции + +[source,kotlin] +---- +myBody { + // ... +} +---- + +При этом содержимому тега `...` соответствует параметр `init: HTMLBody.() -> Unit`, а класс +`HTMLBody` определяется подобно классу `HTML`. Любому тегу, который можно создать внутри `...`, +например тегу параграфа `

`, должна соответствовать функция класса `HTMLBody`, описанная подобно `myBody`. + +Таким образом, класс `HTML` создаёт экземпляр класса `HTMLBody`, тот, в свою очередь, экземпляр класса параграф +и так далее. Кто же в DSL отвечает за создание объекта верхнего уровня `HTML`? + +[source,kotlin] +---- +private fun StringBuilder.myHtml(init: HTML.() -> Unit): HTML { + val html = HTML(this) + append("") + html.init() + append("") + return html +} +---- + +А использование этого игрушечного DSL выглядит следующим образом. В начале, как видите, был `StringBuilder`. +А уже из него были сформированы `HTML` и `HTMLBody`. Обратите также внимание на оператор `+s` и соответствующее +ему объявление `operator fun unaryPlus()`. Он предназначен для добавления в определённое место HTML простого текста. + +[source,kotlin] +---- +fun generateSimpleHtml(s: String): String { + val sb = StringBuilder() + sb.myHtml { + myBody { + +s + } + } + return sb.toString() +} +---- + +=== Упражнения + +Вы можете посмотреть примеры в `srс/lesson10/task2/Html.kt` и решить задачу про генерацию нумерованного списка. +Задача довольно лёгкая и её решение не должно вызвать у вас трудностей. +Вы можете также попытаться использовать `kotlinx.html` для решения задач про HTML-файлы в уроке 7. + +В качестве свободного чтения вы можете прочитать +https://kotlinlang.org/docs/reference/type-safe-builders.html[раздел официальной справки] про построение DSL. + +