├── .gitignore ├── 001-basics-hello-world ├── basics-hello-world.kt ├── practice-test-1.kt ├── practice-test-answer-1.kt └── screenshot.png ├── 002-basics-data-types ├── data-types.kt ├── practice-test-1.kt ├── practice-test-answer-1.kt └── screenshot.png ├── 003-basics-collections ├── collections-list.kt ├── collections-map.kt ├── collections-set.kt ├── practice-test-1.kt ├── practice-test-2.kt ├── practice-test-3.kt ├── practice-test-answer-1.kt ├── practice-test-answer-2.kt ├── practice-test-answer-3.kt ├── screenshot-list.png ├── screenshot-map-1.png ├── screenshot-map-2.png └── screenshot-set.png ├── 004-control-flow ├── flow-conditional-expressions.kt ├── flow-loops.kt ├── practice-ce-test-1.kt ├── practice-ce-test-2.kt ├── practice-ce-test-answer-1.kt ├── practice-ce-test-answer-2.kt ├── practice-loops-test-1.kt ├── practice-loops-test-2.kt ├── practice-loops-test-3.kt ├── practice-loops-test-answer-1.kt ├── practice-loops-test-answer-2.kt ├── practice-loops-test-answer-3.kt └── screenshot-conditional-expressions.png ├── 005-basics-functions ├── 1-functions-basics.kt ├── 2-functions-named-arguments.kt ├── 3-functions-default-parameter-values.kt ├── 4-functions-without-return.kt ├── 5-functions-single-expression.kt ├── 6-functions-early-returns.kt ├── practice-functions-test-1.kt ├── practice-functions-test-2.kt ├── practice-functions-test-answer-1.kt └── practice-functions-test-answer-2.kt ├── 006-lambda-expressions ├── 1-basics.kt ├── 2-structure.kt ├── 3-pass-to-another-function.kt ├── 4-returning-lambda-expressions.kt ├── 5-invoke-separately.kt ├── practice-test-1.kt ├── practice-test-2.kt ├── practice-test-answer-1.kt └── practice-test-answer-2.kt ├── 007-basics-classes ├── 1-basics.kt ├── 2-structure.kt ├── 3-data-classes.kt ├── practice-test-1.kt ├── practice-test-2.kt ├── practice-test-3.kt ├── practice-test-answer-1.kt ├── practice-test-answer-2.kt └── practice-test-answer-3.kt ├── 008-basics-null-safety ├── 1-basics.kt ├── 2-nullable-types.kt ├── 3-check-for-null-values.kt ├── 4-use-safe-calls.kt ├── 5-use-elvis-operator.kt ├── practice-test-1.kt └── practice-test-answer-1.kt ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | *.jar 2 | -------------------------------------------------------------------------------- /001-basics-hello-world/basics-hello-world.kt: -------------------------------------------------------------------------------- 1 | fun main() { 2 | println("\n~~~~~~~~~~~~~ SECTION 1 ~~~~~~~~~~~~~\n") 3 | 4 | println("Hello, world!") 5 | // Output: Hello, world! 6 | 7 | println("\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n") 8 | 9 | print("Hello, ") 10 | println("world!") 11 | // Output: Hello, world! 12 | 13 | println("\n~~~~~~~~~~~~~ SECTION 2 ~~~~~~~~~~~~~\n") 14 | 15 | // There are 5 boxes of popcorn 16 | val popcorn = 5 17 | 18 | // There are 7 hotdogs 19 | val hotdog = 7 20 | 21 | // There are 10 customers in the queue 22 | var customers = 10 23 | 24 | // Some customers leave the queue 25 | customers = 8 26 | 27 | print("Customer Count : ") 28 | println(customers) 29 | // Output: Customer Count : 8 30 | 31 | println("\n~~~~~~~~~~~~~ SECTION 3 ~~~~~~~~~~~~~\n") 32 | 33 | val customerCount = 10 34 | 35 | println("There are $customerCount customers") 36 | // Output: There are 10 customers 37 | 38 | println("There are ${customerCount + 1} customers") 39 | // Output: There are 11 customers 40 | } 41 | 42 | 43 | 44 | //Source : https://kotlinlang.org/docs/kotlin-tour-hello-world.html 45 | 46 | /*========================== 47 | SECTION 1 : Hello, world! 48 | ============================ 49 | - `fun` is used to declare a function 50 | 51 | - The `main()` function is where your program starts from 52 | 53 | - The body of a function is written within curly braces `{}` 54 | _____________________________ 55 | 56 | - `println()` and `print()` functions print their arguments to standard output 57 | 58 | `println()` adds a newline at the end of the output. 59 | `print()` does not add a newline; it keeps the cursor on the same line. 60 | 61 | */ 62 | 63 | 64 | /*========================== 65 | SECTION 2 : Variables 66 | ============================ 67 | - Mutable variables with `var` 68 | 69 | - Read-only variables with `val` 70 | You can't change a read-only variable once you have given it a value. 71 | 72 | - To assign a value, use the assignment operator `=` 73 | 74 | - Variables can be declared outside the `main()` function at the beginning of your program. 75 | Variables declared in this way are said to be declared at `top level`. 76 | 77 | -* Kotlin recommend that you declare all variables as read-only (val) by default. 78 | Declare mutable variables (var) only if necessary. 79 | 80 | */ 81 | 82 | /*========================== 83 | SECTION 3 : String templates 84 | ============================ 85 | - What is a String Template? 86 | A way to include variables or expressions inside a string. 87 | 88 | - How to Use It: 89 | Start with a dollar sign `$` to include a variable. 90 | Use curly braces `{}` after `$` to evaluate a piece of code. 91 | 92 | - String Basics: 93 | Strings are sequences of characters wrapped in double quotes `""` 94 | 95 | */ 96 | 97 | 98 | -------------------------------------------------------------------------------- /001-basics-hello-world/practice-test-1.kt: -------------------------------------------------------------------------------- 1 | /*========================== 2 | Test 1 3 | ============================ 4 | Complete the code to make the program print 5 | "Mary is 20 years old" 6 | to standard output: 7 | */ 8 | 9 | fun main() { 10 | val name = "Mary" 11 | val age = 20 12 | // Write your code here 13 | } 14 | -------------------------------------------------------------------------------- /001-basics-hello-world/practice-test-answer-1.kt: -------------------------------------------------------------------------------- 1 | fun main() { 2 | val name = "Mary" 3 | val age = 20 4 | 5 | println("$name is $age years old") 6 | } -------------------------------------------------------------------------------- /001-basics-hello-world/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NovaLogics/skills-training-cli-kotlin/d9f245d66017d29d72ba25843ad26dd9ac457b07/001-basics-hello-world/screenshot.png -------------------------------------------------------------------------------- /002-basics-data-types/data-types.kt: -------------------------------------------------------------------------------- 1 | 2 | fun main() { 3 | println("\n~~~~~~~~~~~~~ SECTION 1 : Arithmetic operations ~~~~~~~~~~~~~\n") 4 | arithmeticOperations() 5 | 6 | println("\n~~~~~~~~~~~~~ SECTION 2 : Basic types ~~~~~~~~~~~~~\n") 7 | basicTypes() 8 | } 9 | 10 | 11 | /*========================== 12 | SECTION 1 : Arithmetic operations 13 | ============================ 14 | - What Are Types? 15 | - Every variable and data structure in Kotlin has a type. 16 | - Types define what operations (functions and properties) you can use with that variable. 17 | 18 | - Type Inference: 19 | - Kotlin can automatically figure out the type of a variable based on its value. 20 | - This is called type inference. 21 | 22 | Example: 23 | val customers = 10 // Kotlin infers the type as Int 24 | println(customers + 5) // Arithmetic operations are allowed since it's an Int 25 | 26 | - Why Are Types Important? 27 | - They prevent errors by ensuring variables are used in valid ways. 28 | - Example: You can’t use string operations on an Int. 29 | 30 | 31 | +=, -=, *=, /=, and %= are augmented assignment operators. 32 | 33 | */ 34 | fun arithmeticOperations() { 35 | 36 | var customers = 10 37 | 38 | // Some customers leave the queue 39 | customers = 8 40 | 41 | customers = customers + 3 // Example of addition: 11 42 | 43 | customers += 7 // Example of addition: 18 44 | customers -= 3 // Example of subtraction: 15 45 | customers *= 2 // Example of multiplication: 30 46 | customers /= 3 // Example of division: 10 47 | 48 | println("Customers : $customers") 49 | // Output: Customers : 10 50 | } 51 | 52 | 53 | 54 | /*========================== 55 | SECTION 2 : Basic types 56 | ============================ 57 | - Integers 58 | Whole numbers (e.g., 1, 2020). 59 | Types: Byte, Short, Int, Long 60 | Example: 61 | val year: Int = 2020 62 | 63 | - Unsigned Integers 64 | Whole numbers that cannot be negative. 65 | Types: UByte, UShort, UInt, ULong 66 | Example: 67 | val score: UInt = 100u 68 | 69 | - Floating-Point Numbers 70 | Numbers with decimals (e.g., 24.5, 19.99). 71 | Types: Float, Double 72 | Example: 73 | val currentTemp: Float = 24.5f 74 | val price: Double = 19.99 75 | 76 | - Booleans 77 | True or false values. 78 | Type: Boolean 79 | Example: 80 | val isEnabled: Boolean = true 81 | 82 | - Characters 83 | A single letter, digit, or symbol (e.g., 'A', ','). 84 | Type: Char 85 | Example: 86 | val separator: Char = ',' 87 | 88 | - Strings 89 | Text made up of characters (e.g., "Hello"). 90 | Type: String 91 | Example: 92 | val message: String = "Hello, world!" 93 | */ 94 | fun basicTypes() { 95 | 96 | // Integer Types 97 | val year: Int = 2023 // Explicitly typed and initialized 98 | val age = 25 // Type inferred as Int 99 | println("Year: $year, Age: $age") 100 | // Output: Year: 2023, Age: 25 101 | 102 | // Unsigned Integer Types 103 | val score: UInt = 100u // Unsigned Int 104 | println("Score: $score") 105 | // Output: Score: 100 106 | 107 | // Floating-Point Numbers 108 | val temperature: Float = 36.6f // Float requires 'f' suffix 109 | val price: Double = 19.99 // Double type 110 | println("Temperature: $temperature, Price: $price") 111 | // Output: Temperature: 36.6, Price: 19.99 112 | 113 | // Boolean Type 114 | val isAvailable: Boolean = true 115 | println("Is Available: $isAvailable") 116 | // Output: Is Available: true 117 | 118 | // Character Type 119 | val grade: Char = 'A' 120 | val separator: Char = ',' 121 | println("Grade: $grade, Separator: $separator") 122 | // Output: Grade: A, Separator: , 123 | 124 | // String Type 125 | val message: String = "Hello, Kotlin!" 126 | println("Message: $message") 127 | // Output: Message: Hello, Kotlin! 128 | 129 | // Declaring variables without initialization 130 | val d: Int // Declared but not initialized 131 | d = 42 // Initialized later 132 | println("Value of d: $d") // Output: Value of d: 42 133 | 134 | //If you don't initialize a variable before it is read, you see an error: 135 | // Error Example (Uncomment to see the error) 136 | // val d1: Int 137 | // println(d1) // Error: Variable 'd1' must be initialized 138 | 139 | } 140 | -------------------------------------------------------------------------------- /002-basics-data-types/practice-test-1.kt: -------------------------------------------------------------------------------- 1 | /*========================== 2 | Test 1 3 | ============================ 4 | Explicitly declare the correct type for each variable: 5 | */ 6 | 7 | fun main() { 8 | val a: Int = 1000 9 | val b = "log message" 10 | val c = 3.14 11 | val d = 100_000_000_000_000 12 | val e = false 13 | val f = '\n' 14 | } -------------------------------------------------------------------------------- /002-basics-data-types/practice-test-answer-1.kt: -------------------------------------------------------------------------------- 1 | fun main() { 2 | val a: Int = 1000 3 | val b: String = "log message" 4 | val c: Double = 3.14 5 | val d: Long = 100_000_000_000_000 6 | val e: Boolean = false 7 | val f: Char = '\n' 8 | } -------------------------------------------------------------------------------- /002-basics-data-types/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NovaLogics/skills-training-cli-kotlin/d9f245d66017d29d72ba25843ad26dd9ac457b07/002-basics-data-types/screenshot.png -------------------------------------------------------------------------------- /003-basics-collections/collections-list.kt: -------------------------------------------------------------------------------- 1 | /* Collections in Kotlin 2 | Collections are used to group and organize data for processing. 3 | Kotlin offers three main types of collections: 4 | 5 | - Lists: 6 | A sequence of items arranged in a specific order. 7 | Items can be repeated. 8 | 9 | - Sets: 10 | A collection of unique items, with no specific order. 11 | Duplicates are not allowed. 12 | 13 | - Maps: 14 | A collection of key-value pairs where each key is unique. 15 | Keys are used to map to specific values. 16 | 17 | Collections can be either: 18 | Read-only: Cannot be changed after creation. 19 | Mutable: Can be modified (add, remove, or update items). 20 | */ 21 | 22 | fun main() { 23 | 24 | println("\n~~~~~~~~~~~~~ SECTION : List ~~~~~~~~~~~~~\n") 25 | listType() 26 | } 27 | 28 | /*========================== 29 | SECTION 1 : List 30 | ============================ 31 | - What are Lists? 32 | Lists store items in the order they are added. 33 | They allow duplicate items. 34 | 35 | - Types of Lists: 36 | Read-only List: Use the `listOf()` function to create. 37 | Mutable List: Use the `mutableListOf()` function to create. 38 | 39 | - Type Declaration: 40 | Kotlin can automatically infer the type of items in a list. 41 | To declare the type explicitly, use angled brackets `<>` after the list declaration. 42 | 43 | Example: 44 | val numbers: List = listOf(1, 2, 3) // Read-only List 45 | val mutableNumbers: MutableList = mutableListOf("A", "B") // Mutable List 46 | 47 | */ 48 | 49 | fun listType() { 50 | 51 | // Read-only list 52 | 53 | // val readOnlyColors= listOf("Red", "Green", "Blue") OR 54 | val readOnlyColors: List = listOf("Red", "Green", "Blue") 55 | println("Read-only List: $readOnlyColors") 56 | // Output: Read-only List: [Red, Green, Blue] 57 | 58 | // Mutable list 59 | val mutableFruits: MutableList = mutableListOf("Apple", "Banana", "Orange") 60 | println("\n||---> @readOnlyShapes = $mutableFruits") 61 | // Adding a new item 62 | mutableFruits.add("Grapes") 63 | 64 | println("Mutable List after adding a fruit: $mutableFruits") 65 | // Output: Mutable List after adding a fruit: [Apple, Banana, Orange, Grapes] 66 | 67 | // To prevent unwanted modifications, you can create a read-only view of a mutable list by 68 | // assigning it to a List: 69 | val shapesList: MutableList = mutableListOf("triangle", "square", "circle") 70 | val shapesListLocked: List = shapesList 71 | // This is also called `casting` 72 | 73 | println("\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~") 74 | 75 | val readOnlyShapes = listOf("triangle", "square", "circle") 76 | println("\n||---> @readOnlyShapes = $readOnlyShapes \n") 77 | 78 | /* 79 | Lists are ordered so to access an item in a list, use the indexed access operator [] 80 | */ 81 | println("|-> readOnlyShapes[0] |") 82 | println("The first item in the list is: ${readOnlyShapes[0]}") 83 | // The first item in the list is: triangle 84 | 85 | /* 86 | To get the first or last item in a list, use .first() and .last() functions respectively 87 | */ 88 | println("\n|-> readOnlyShapes.first() |") 89 | println("The first item in the list is: ${readOnlyShapes.first()}") 90 | // The first item in the list is: triangle 91 | 92 | 93 | /* 94 | .first() and .last() functions are examples of extension functions. 95 | To call an extension function on an object, write the function name after the object appended with a period `.` 96 | */ 97 | println("\n|-> readOnlyShapes.count() |") 98 | println("This list has ${readOnlyShapes.count()} items ") 99 | 100 | 101 | /* 102 | To check that an item is in a list, use the in operator: 103 | */ 104 | println("\n|-> \"circle\" in readOnlyShapes |") 105 | print("circle" in readOnlyShapes) 106 | 107 | 108 | println("\n\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~") 109 | /* 110 | To add or remove items from a mutable list, use .add() and .remove() functions respectively: 111 | */ 112 | val shapes: MutableList = mutableListOf("triangle", "square", "circle") 113 | println("\n||---> @shapes = $shapes \n") 114 | // Add "pentagon" to the list 115 | shapes.add("pentagon") 116 | println("|-> shapes.add(\"pentagon\") |") 117 | println("$shapes items") 118 | // [triangle, square, circle, pentagon] 119 | 120 | // Remove the first "pentagon" from the list 121 | shapes.remove("pentagon") 122 | println("\n|-> shapes.remove(\"pentagon\") |") 123 | println("$shapes items") 124 | // [triangle, square, circle] 125 | 126 | } 127 | -------------------------------------------------------------------------------- /003-basics-collections/collections-map.kt: -------------------------------------------------------------------------------- 1 | /* Collections in Kotlin 2 | Collections are used to group and organize data for processing. 3 | Kotlin offers three main types of collections: 4 | 5 | - Lists: 6 | A sequence of items arranged in a specific order. 7 | Items can be repeated. 8 | 9 | - Sets: 10 | A collection of unique items, with no specific order. 11 | Duplicates are not allowed. 12 | 13 | - Maps: 14 | A collection of key-value pairs where each key is unique. 15 | Keys are used to map to specific values. 16 | 17 | Collections can be either: 18 | Read-only: Cannot be changed after creation. 19 | Mutable: Can be modified (add, remove, or update items). 20 | */ 21 | 22 | fun main() { 23 | 24 | println("\n~~~~~~~~~~~~~ SECTION : Map ~~~~~~~~~~~~~\n") 25 | typeMap() 26 | } 27 | 28 | /*========================== 29 | SECTION : Map 30 | ============================ 31 | - What are Maps? 32 | Maps store items as key-value pairs. 33 | You access the value using its key. 34 | Every key must be unique, but values can be duplicated. 35 | Useful for looking up values without using an index, like in a list. 36 | 37 | - Types of Maps: 38 | Read-only Map: Use the `mapOf()` function to create. 39 | Mutable Map: Use the `mutableMapOf()` function to create. 40 | 41 | - Type Declaration: 42 | Kotlin can infer the type of keys and values in a map. 43 | To declare the types explicitly, use angled brackets `<>` for keys and values, e.g., MutableMap. 44 | 45 | - Creating a Map: 46 | Use to `to` link each key to its value. 47 | val map = mapOf(1 to "x", 2 to "y", -1 to "zz") 48 | 49 | Explanation: 50 | Read-only map: We use mapOf() to create a map where keys are linked to values. 51 | Mutable map: We use mutableMapOf() to create a map that allows modification (adding or updating key-value pairs). 52 | 53 | */ 54 | 55 | fun typeMap() { 56 | 57 | // Read-only map 58 | val readOnlyJuiceMenu = mapOf("apple" to 100, "kiwi" to 190, "orange" to 100) 59 | println("\n||---> @readOnlyJuiceMenu = $readOnlyJuiceMenu \n") 60 | // Output: Read-only Map: {apple=100, kiwi=190, orange=100} 61 | 62 | // Mutable map with explicit type declaration 63 | val juiceMenu: MutableMap = 64 | mutableMapOf("apple" to 100, "kiwi" to 190, "orange" to 100) 65 | juiceMenu["mango"] = 120 // Adding a new key-value pair 66 | println("\n||---> @juiceMenu = $juiceMenu ") 67 | println("|-> juiceMenu[\"mango\"] = 120 |") 68 | println("Mutable Map after adding a juice: $juiceMenu") 69 | // Output: Mutable Map after adding a juice: {apple=100, kiwi=190, orange=100, mango=120} 70 | 71 | /* 72 | To prevent unwanted modifications, you can create a read-only view of a mutable map by assigning it to a Map: 73 | */ 74 | val juiceMenuMap: MutableMap = 75 | mutableMapOf("apple" to 100, "kiwi" to 190, "orange" to 100) 76 | val juiceMenuMapLocked: Map = juiceMenuMap 77 | 78 | println("\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~") 79 | 80 | println("\n||---> @fruitSet = $juiceMenuMap \n") 81 | 82 | /* 83 | To access a value in a map, use the indexed access operator [] with its key: 84 | */ 85 | println("|-> juiceMenuMap[\"apple\"] |") 86 | println("The value of apple juice is: ${juiceMenuMap["apple"]}") 87 | // The value of apple juice is: 100 88 | 89 | /* 90 | If you try to access a key-value pair with a key that doesn't exist in a map, you see a null value: 91 | */ 92 | println("\n|-> juiceMenuMap[\"pineapple\"] |") 93 | println("The value of pineapple juice is: ${juiceMenuMap["pineapple"]}") 94 | // The value of pineapple juice is: null 95 | 96 | /* 97 | You can also use the indexed access operator [] to add items to a mutable map: 98 | */ 99 | juiceMenuMap["coconut"] = 150 // Add key "coconut" with value 150 to the map 100 | println("\n|-> juiceMenu[\"coconut\"] = 150 |") 101 | println("||---> @fruitSet = $juiceMenuMap ") 102 | // {apple=100, kiwi=190, orange=100, coconut=150} 103 | 104 | /* 105 | To remove items from a mutable map, use the .remove() function: 106 | */ 107 | juiceMenuMap.remove("orange") // Remove key "orange" from the map 108 | println("\n|-> juiceMenu.remove(\"orange\") |") 109 | println("||---> @fruitSet = $juiceMenuMap ") 110 | // {apple=100, kiwi=190, orange=100, coconut=150} 111 | 112 | /* 113 | To get the number of items in a map, use the .count() function: 114 | */ 115 | println("\n|-> juiceMenuMap.count() |") 116 | println("This map has ${juiceMenuMap.count()} key-value pairs") 117 | // This map has 3 key-value pairs 118 | 119 | /* 120 | To get the number of items in a map, use the .count() function: 121 | */ 122 | println("\n|-> juiceMenuMap.containsKey(\"kiwi\") |") 123 | println(juiceMenuMap.containsKey("kiwi")) 124 | // true 125 | 126 | println("\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~") 127 | 128 | println("\n||---> @fruitSet = $juiceMenuMap \n") 129 | 130 | /* 131 | To obtain a collection of the keys or values of a map, use the keys and values properties respectively: 132 | */ 133 | println("\n|-> juiceMenuMap.keys |") 134 | println(juiceMenuMap.keys) 135 | // [apple, kiwi, orange] 136 | 137 | println("\n|-> juiceMenuMap.values |") 138 | println(juiceMenuMap.values) 139 | // [100, 190, 100] 140 | 141 | 142 | /* 143 | To check that a key or value is in a map, use the in operator: 144 | */ 145 | println("\n|-> \"kiwi\" in juiceMenuMap |") 146 | println("kiwi" in juiceMenuMap.keys) 147 | // true 148 | 149 | println("\n|-> 200 in juiceMenuMap.values |") 150 | println(200 in juiceMenuMap.values) 151 | // false 152 | 153 | } 154 | -------------------------------------------------------------------------------- /003-basics-collections/collections-set.kt: -------------------------------------------------------------------------------- 1 | /* Collections in Kotlin 2 | Collections are used to group and organize data for processing. 3 | Kotlin offers three main types of collections: 4 | 5 | - Lists: 6 | A sequence of items arranged in a specific order. 7 | Items can be repeated. 8 | 9 | - Sets: 10 | A collection of unique items, with no specific order. 11 | Duplicates are not allowed. 12 | 13 | - Maps: 14 | A collection of key-value pairs where each key is unique. 15 | Keys are used to map to specific values. 16 | 17 | Collections can be either: 18 | Read-only: Cannot be changed after creation. 19 | Mutable: Can be modified (add, remove, or update items). 20 | */ 21 | 22 | fun main() { 23 | 24 | println("\n~~~~~~~~~~~~~ SECTION : Set ~~~~~~~~~~~~~\n") 25 | typeSet() 26 | } 27 | 28 | /*========================== 29 | SECTION : Set 30 | ============================ 31 | - What are Sets? 32 | Sets store unique items and do not allow duplicates. 33 | They are unordered, meaning the order of items is not guaranteed. 34 | 35 | - Types of Sets: 36 | Read-only Set: Use the `setOf()` function to create. 37 | Mutable Set: Use the `mutableSetOf()` function to create. 38 | 39 | - Type Declaration: 40 | Kotlin can infer the type of items in a set. 41 | To declare the type explicitly, use angled brackets `<>` after the set declaration. 42 | 43 | Explanation: 44 | Read-only set: We use `setOf()` to create a set that automatically removes duplicates. 45 | Mutable set: We use `mutableSetOf()` and can modify the set by adding or removing items. 46 | 47 | */ 48 | 49 | fun typeSet() { 50 | 51 | // Read-only set 52 | val readOnlyFruit = setOf("apple", "banana", "cherry", "cherry") 53 | println("\n||---> @readOnlyFruit = $readOnlyFruit \n") 54 | // Output: Read-only Set: [apple, banana, cherry] (duplicates removed) 55 | 56 | // Mutable set with explicit type declaration 57 | val fruit: MutableSet = mutableSetOf("apple", "banana", "cherry", "cherry") 58 | println("\n||---> @fruit = [apple, banana, cherry, cherry] \n") 59 | 60 | fruit.add("orange") // Adding a new item 61 | println("Mutable Set after adding a fruit: $fruit") 62 | // Output: Mutable Set after adding a fruit: [apple, banana, cherry, orange] 63 | 64 | /* ** 65 | You can see in the previous example that because sets only contain unique elements, the duplicate "cherry" item is dropped. 66 | */ 67 | 68 | /* 69 | To prevent unwanted modifications, you can create a read-only view of a mutable set by assigning it to a Set: 70 | */ 71 | val fruitSet: MutableSet = mutableSetOf("apple", "banana", "cherry", "cherry") 72 | val fruitSetLocked: Set = fruitSet 73 | 74 | println("\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~") 75 | 76 | println("\n||---> @fruitSet = $fruitSet \n") 77 | 78 | /* 79 | As sets are `unordered`, you can't access an item at a particular index. 80 | */ 81 | println("|-> fruitSet.elementAt(0) |") 82 | println("The first item in the Set is: ${fruitSet.elementAt(0)}") 83 | 84 | println("\n|-> fruitSet.indexOf(\"apple\") |") 85 | println("The index of element is: " + fruitSet.indexOf("apple")) 86 | 87 | /* 88 | To get the number of items in a set, use the .count() function: 89 | */ 90 | println("\n|-> fruitSet.count() |") 91 | println("This set has ${fruitSet.count()} items") 92 | 93 | /* 94 | To check that an item is in a set, use the in operator: 95 | */ 96 | println("\n|-> \"banana\" in fruitSet |") 97 | println("banana" in fruitSet) 98 | 99 | /* 100 | To add or remove items from a mutable set, use .add() and .remove() functions 101 | */ 102 | // Add "dragonfruit" to the set 103 | fruitSet.add("dragonfruit") 104 | println("\n|-> fruitSet.add(\"dragonfruit\") |") 105 | println("||---> @fruitSet = $fruitSet") 106 | // [apple, banana, cherry, dragonfruit] 107 | 108 | // Remove "dragonfruit" from the set 109 | fruitSet.remove("dragonfruit") 110 | println("\n|-> fruitSet.remove(\"dragonfruit\") |") 111 | println("||---> @fruitSet = $fruitSet \n") 112 | // [apple, banana, cherry] 113 | 114 | } 115 | -------------------------------------------------------------------------------- /003-basics-collections/practice-test-1.kt: -------------------------------------------------------------------------------- 1 | /*========================== 2 | Test 1 3 | ============================ 4 | You have a list of “green” numbers and a list of “red” numbers. 5 | Complete the code to print how many numbers there are in total. 6 | */ 7 | 8 | fun main() { 9 | val greenNumbers = listOf(1, 4, 23) 10 | val redNumbers = listOf(17, 2) 11 | // Write your code here 12 | } -------------------------------------------------------------------------------- /003-basics-collections/practice-test-2.kt: -------------------------------------------------------------------------------- 1 | /*========================== 2 | Test 2 3 | ============================ 4 | You have a set of protocols supported by your server. 5 | A user requests to use a particular protocol. 6 | Complete the program to check whether the requested protocol is supported or not 7 | (`isSupported` must be a Boolean value). 8 | 9 | Hint 10 | Make sure that you check the requested protocol in upper case. 11 | You can use the .uppercase() function to help you with this. 12 | */ 13 | 14 | fun main() { 15 | val SUPPORTED = setOf("HTTP", "HTTPS", "FTP") 16 | val requested = "smtp" 17 | val isSupported = // Write your code here 18 | println("Support for $requested: $isSupported") 19 | } -------------------------------------------------------------------------------- /003-basics-collections/practice-test-3.kt: -------------------------------------------------------------------------------- 1 | /*========================== 2 | Test 3 3 | ============================ 4 | Define a map that relates integer numbers from 1 to 3 to their corresponding spelling. 5 | Use this map to spell the given number. 6 | */ 7 | 8 | fun main() { 9 | val number2word = // Write your code here 10 | val n = 2 11 | println("$n is spelt as '${}'") 12 | } -------------------------------------------------------------------------------- /003-basics-collections/practice-test-answer-1.kt: -------------------------------------------------------------------------------- 1 | fun main() { 2 | val greenNumbers = listOf(1, 4, 23) 3 | val redNumbers = listOf(17, 2) 4 | 5 | val totalCount = greenNumbers.count() + redNumbers.count() 6 | println("Total : $totalCount") 7 | } 8 | -------------------------------------------------------------------------------- /003-basics-collections/practice-test-answer-2.kt: -------------------------------------------------------------------------------- 1 | fun main() { 2 | val SUPPORTED = setOf("HTTP", "HTTPS", "FTP") 3 | val requested = "smtp" 4 | val isSupported = requested.uppercase() in SUPPORTED 5 | println("Support for $requested: $isSupported") 6 | } -------------------------------------------------------------------------------- /003-basics-collections/practice-test-answer-3.kt: -------------------------------------------------------------------------------- 1 | fun main() { 2 | val number2word = mapOf(1 to "one", 2 to "two", 3 to "three") 3 | val n = 2 4 | println("$n is spelt as '${number2word[n]}'") 5 | } -------------------------------------------------------------------------------- /003-basics-collections/screenshot-list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NovaLogics/skills-training-cli-kotlin/d9f245d66017d29d72ba25843ad26dd9ac457b07/003-basics-collections/screenshot-list.png -------------------------------------------------------------------------------- /003-basics-collections/screenshot-map-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NovaLogics/skills-training-cli-kotlin/d9f245d66017d29d72ba25843ad26dd9ac457b07/003-basics-collections/screenshot-map-1.png -------------------------------------------------------------------------------- /003-basics-collections/screenshot-map-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NovaLogics/skills-training-cli-kotlin/d9f245d66017d29d72ba25843ad26dd9ac457b07/003-basics-collections/screenshot-map-2.png -------------------------------------------------------------------------------- /003-basics-collections/screenshot-set.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NovaLogics/skills-training-cli-kotlin/d9f245d66017d29d72ba25843ad26dd9ac457b07/003-basics-collections/screenshot-set.png -------------------------------------------------------------------------------- /004-control-flow/flow-conditional-expressions.kt: -------------------------------------------------------------------------------- 1 | fun main() { 2 | println("\n~~~~~~~~~~~~~ SECTION 1 : If operations ~~~~~~~~~~~~~\n") 3 | ifExpression() 4 | 5 | println("\n~~~~~~~~~~~~~ SECTION 2 : When operations ~~~~~~~~~~~~~\n") 6 | whenExpression() 7 | } 8 | 9 | /* Conditional Expressions in Kotlin 10 | Kotlin provides two main ways to check conditions: `if` and `when` 11 | 12 | - `if` Expression 13 | * Used for simple conditions, similar to other languages. 14 | * Can be used as an expression, meaning it can return a value. 15 | 16 | - when Expression 17 | * Used for multiple conditions. 18 | * Works like a switch statement in other languages but is more flexible. 19 | * Preferred in Kotlin because it: 20 | * Improves readability by clearly showing the branches. 21 | * Easier to extend by adding more branches. 22 | * Reduces mistakes in logic by handling multiple conditions in one place. 23 | 24 | Key Points: 25 | * `if` : Best for simple conditions. 26 | * `when` : Best for multiple conditions and is easier to maintain. 27 | */ 28 | 29 | /*========================== 30 | SECTION 1 : Using `if` in Kotlin 31 | ============================ 32 | In Kotlin, the if statement is used to check conditions, 33 | and it can be used as an expression, meaning it can return a value. 34 | Here's how it works: 35 | 36 | - Basic if-else: 37 | * Use if to evaluate a condition and execute one block of code if the condition is true, and another block if it is false. 38 | 39 | - No ternary operator: 40 | * Kotlin does not have a ternary operator (condition ? then : else) like some other languages. 41 | Instead, you use if in a more straightforward way. 42 | 43 | - Optional curly braces: 44 | * If there's only one statement per block, you can omit the curly braces {} 45 | 46 | Key Notes: 47 | * if as a statement: Executes different blocks of code based on a condition. 48 | * if as an expression: Returns a value based on the condition, no need for a ternary operator. 49 | * Optional curly braces: If you have a single statement in each block, curly braces are not required. 50 | 51 | */ 52 | fun ifExpression() { 53 | // Regular if-else statement 54 | val isConditionMet = true 55 | val result: Int 56 | 57 | if (isConditionMet) { 58 | result = 1 // Assign 1 if isConditionMet is true 59 | } else { 60 | result = 2 // Assign 2 if isConditionMet is false 61 | } 62 | println("Result: $result") 63 | // Output: 1 64 | 65 | // Using if as an expression 66 | val firstNumber = 1 67 | val secondNumber = 2 68 | print("Larger value: ") 69 | println(if (firstNumber > secondNumber) firstNumber else secondNumber) 70 | // Output: 2 71 | } 72 | 73 | /*========================== 74 | SECTION 2 : Using `when` in Kotlin 75 | ============================ 76 | The when keyword is used for conditional expressions with multiple branches. 77 | It is a flexible and readable alternative to if-else chains. 78 | 79 | Key Points: 80 | 81 | - Purpose: 82 | * Use `when` to evaluate a value and perform actions or return results based on conditions. 83 | 84 | - Structure: 85 | * Place the value to evaluate in parentheses `()` 86 | * Define branches in curly braces `{}` 87 | * Use `->` to separate each condition from its action 88 | 89 | - Types of when Usage: 90 | * As a Statement: Executes actions but does not return a value. 91 | * As an Expression: Returns a value that can be assigned to a variable. 92 | 93 | - Benefits of when: 94 | * Readability: Makes code easier to follow compared to long `if-else` chains. 95 | * Maintainability: Adding new branches is simple. 96 | * Subject Usage: Improves readability and ensures all possible cases are covered (especially for enums). 97 | 98 | - Note: 99 | * When no subject is used, an else branch is mandatory to handle any unmatched cases. 100 | * When a subject is used, Kotlin can validate whether all possible cases are covered. 101 | 102 | */ 103 | fun whenExpression() { 104 | // Example 105 | 106 | println("obj ='Hello' \n") 107 | 108 | val obj = "Hello" 109 | 110 | when (obj) { 111 | // Checks whether obj equals to "1" 112 | "1" -> println("One") 113 | // Checks whether obj equals to "Hello" 114 | "Hello" -> println("Greeting") 115 | // Default statement 116 | else -> println("Unknown") 117 | } 118 | // Greeting 119 | 120 | /* 121 | Note that all branch conditions are checked sequentially until one of them is satisfied. 122 | So only the first suitable branch is executed. 123 | */ 124 | 125 | /* 126 | An expression returns a value that can be used later in your code. 127 | 128 | Here is an example of using when as an expression. 129 | The when expression is assigned immediately to a variable which is later used with the println() function: 130 | */ 131 | 132 | val result = when (obj) { 133 | // If obj equals "1", sets result to "one" 134 | "1" -> "One" 135 | // If obj equals "Hello", sets result to "Greeting" 136 | "Hello" -> "Greeting" 137 | // Sets result to "Unknown" if no previous condition is satisfied 138 | else -> "Unknown" 139 | } 140 | println("result: $result") 141 | // Greeting 142 | 143 | println("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~") 144 | 145 | /* 146 | The examples of when that you've seen so far both had a subject: obj. 147 | But when can also be used without a subject. 148 | */ 149 | 150 | val trafficLightState = "Red" // This can be "Green", "Yellow", or "Red" 151 | println("trafficLightState> $trafficLightState") 152 | 153 | val trafficAction = when { 154 | trafficLightState == "Green" -> "Go" 155 | trafficLightState == "Yellow" -> "Slow down" 156 | trafficLightState == "Red" -> "Stop" 157 | else -> "Malfunction" 158 | } 159 | 160 | println("trafficAction: $trafficAction") 161 | // Stop 162 | 163 | 164 | val trafficActionResult = when (trafficLightState) { 165 | "Green" -> "Go" 166 | "Yellow" -> "Slow down" 167 | "Red" -> "Stop" 168 | else -> "Malfunction" 169 | } 170 | 171 | println("trafficActionResult: $trafficActionResult") 172 | 173 | } 174 | -------------------------------------------------------------------------------- /004-control-flow/flow-loops.kt: -------------------------------------------------------------------------------- 1 | fun main() { 2 | println("\n~~~~~~~~~~~~~ SECTION 1 : Ranges ~~~~~~~~~~~~~\n") 3 | rangesSection() 4 | 5 | println("\n~~~~~~~~~~~~~ SECTION 2 : Loops ~~~~~~~~~~~~~\n") 6 | 7 | println(":::::::::::::: For Loop ::::::::::::::\n") 8 | forLoops() 9 | 10 | println("\n:::::::::::::: While Loop ::::::::::::::\n") 11 | whileLoops() 12 | } 13 | 14 | /*========================== 15 | SECTION 1 : Ranges in Kotlin 16 | ============================ 17 | - Creating a Range: 18 | * Use the .. operator to create an inclusive range. 19 | Example: 20 | 1..4 → 1, 2, 3, 4. 21 | 22 | - Exclusive Range: 23 | * Use ..< for a range that excludes the end value. 24 | Example: 25 | 1..<4 → 1, 2, 3. 26 | 27 | - Reversed Range: 28 | * Use downTo for a range in reverse order. 29 | Example: 30 | 4 downTo 1 → 4, 3, 2, 1. 31 | 32 | - Custom Step: 33 | * Use step to define a custom increment. 34 | Example: 35 | 1..5 step 2 → 1, 3, 5. 36 | 37 | Character Ranges: 38 | 'a'..'d' → 'a', 'b', 'c', 'd' 39 | 40 | 'z' downTo 's' step 2 → 'z', 'x', 'v', 't'. 41 | 42 | 43 | Note: 44 | Ranges are commonly used in loops to iterate over a sequence of values. 45 | 46 | */ 47 | 48 | fun rangesSection() { 49 | 50 | // Inclusive range 51 | val inclusiveRange = 1..4 52 | println("Inclusive range: $inclusiveRange") 53 | println(inclusiveRange.joinToString()) // Output: 1, 2, 3, 4 54 | 55 | println() 56 | 57 | // Exclusive range 58 | val exclusiveRange = 1 ..< 4 59 | println("Exclusive range: $exclusiveRange") 60 | println(exclusiveRange.joinToString()) // Output: 1, 2, 3 61 | 62 | println() 63 | 64 | // Reversed range 65 | val reversedRange = 4 downTo 1 66 | println("Reversed range: $reversedRange") 67 | println(reversedRange.joinToString()) // Output: 4, 3, 2, 1 68 | 69 | println() 70 | 71 | // Custom step 72 | val stepRange = 1..5 step 2 73 | println("Step range: $stepRange") 74 | println(stepRange.joinToString()) // Output: 1, 3, 5 75 | 76 | println() 77 | 78 | // Character range 79 | val charRange = 'a'..'d' 80 | println("Character range: $charRange") 81 | println(charRange.joinToString()) // Output: a, b, c, d 82 | 83 | println() 84 | 85 | // Reversed character range with step 86 | val reversedCharRange = 'z' downTo 's' step 2 87 | println("Reversed character range with step: $reversedCharRange") 88 | println(reversedCharRange.joinToString()) // Output: z, x, v, t 89 | } 90 | 91 | /*========================== 92 | SECTION 2.1 : Loops in Kotlin 93 | ============================ 94 | - `For` Loop: 95 | * Used to iterate over a range of values. 96 | Example: 97 | for (i in 1..5) { println(i) } 98 | → Prints 1, 2, 3, 4, 5. 99 | 100 | - `While` Loop: 101 | * Continues to perform an action as long as a condition is true. 102 | var count = 1 103 | while (count <= 5) { 104 | println(count) 105 | count++ 106 | } 107 | → Prints 1, 2, 3, 4, 5. 108 | 109 | Notes: 110 | Use `for` when iterating over collections or ranges. 111 | Use `while` when a condition dynamically controls the loop. 112 | */ 113 | 114 | fun forLoops() { 115 | for (number in 1..5) { 116 | // number is the iterator and 1..5 is the range 117 | print(number) 118 | } 119 | // 12345 120 | 121 | println("\n~~~~~~~~~~~~~~~~~~~~~~~~~~~") 122 | 123 | val cakes = listOf("carrot", "cheese", "chocolate") 124 | 125 | for (cake in cakes) { 126 | println("Yummy, it's a $cake cake!") 127 | } 128 | // Yummy, it's a carrot cake! 129 | // Yummy, it's a cheese cake! 130 | // Yummy, it's a chocolate cake! 131 | } 132 | 133 | /*========================== 134 | SECTION 2.2 : While in Kotlin 135 | ============================ 136 | - `while` can be used in two ways: 137 | * To execute a code block while a conditional expression is true. (while) 138 | * To execute the code block first and then check the conditional expression. (do-while) 139 | 140 | - In the first use case (while): 141 | * Declare the conditional expression for your while loop to continue within parentheses `()` 142 | * Add the action you want to complete within curly braces `{}` 143 | 144 | - In the second use case (do-while): 145 | * Declare the conditional expression for your while loop to continue within parentheses `()` 146 | * Define the action you want to complete within curly braces `{}` with the keyword `do` 147 | */ 148 | 149 | fun whileLoops() { 150 | 151 | // The following examples use the increment operator ++ to increment the value of the cakesEaten 152 | // variable. 153 | 154 | var cakesEatenByMe = 0 155 | 156 | while (cakesEatenByMe < 3) { 157 | println("Eat a cake") 158 | cakesEatenByMe++ 159 | } 160 | // Eat a cake 161 | // Eat a cake 162 | // Eat a cake 163 | 164 | println("\n~~~~~~~~~~~~~~~~~~~~~~~~~~~") 165 | 166 | var cakesEaten = 0 167 | var cakesBaked = 0 168 | 169 | while (cakesEaten < 3) { 170 | println("Eat a cake") 171 | cakesEaten++ 172 | } 173 | 174 | do { 175 | println("Bake a cake") 176 | cakesBaked++ 177 | } while (cakesBaked < cakesEaten) 178 | // Eat a cake 179 | // Eat a cake 180 | // Eat a cake 181 | // Bake a cake 182 | // Bake a cake 183 | // Bake a cake 184 | } 185 | -------------------------------------------------------------------------------- /004-control-flow/practice-ce-test-1.kt: -------------------------------------------------------------------------------- 1 | /*========================== 2 | Test 1 : Conditional expressions practice 3 | ============================ 4 | 5 | Create a simple game where you win if throwing two dice results in the same number. 6 | Use if to print 7 | You win :) if the dice match 8 | or 9 | You lose :( otherwise. 10 | 11 | In this exercise, you import a package so that you can use the 12 | Random.nextInt() function to give you a random Int. 13 | 14 | Hint 15 | Use the equality operator (==) to compare the dice results. 16 | 17 | ---------------------- 18 | */ 19 | 20 | import kotlin.random.Random 21 | 22 | fun main() { 23 | val firstResult = Random.nextInt(6) 24 | val secondResult = Random.nextInt(6) 25 | // Write your code here 26 | } -------------------------------------------------------------------------------- /004-control-flow/practice-ce-test-2.kt: -------------------------------------------------------------------------------- 1 | /*========================== 2 | Test 2 3 | ============================ 4 | Using a when expression, update the following program so that it prints 5 | the corresponding actions when you input the names of game console buttons. 6 | 7 | Button Action 8 | 9 | A Yes 10 | B No 11 | X Menu 12 | Y Nothing 13 | Other There is no such button 14 | 15 | */ 16 | 17 | 18 | fun main() { 19 | val button = "A" 20 | 21 | println( 22 | // Write your code here 23 | ) 24 | } 25 | 26 | -------------------------------------------------------------------------------- /004-control-flow/practice-ce-test-answer-1.kt: -------------------------------------------------------------------------------- 1 | /*========================== 2 | Test 1 : Conditional expressions practice 3 | ============================ 4 | 5 | Create a simple game where you win if throwing two dice results in the same number. 6 | Use if to print 7 | You win :) if the dice match 8 | or 9 | You lose :( otherwise. 10 | 11 | In this exercise, you import a package so that you can use the 12 | Random.nextInt() function to give you a random Int. 13 | 14 | Hint 15 | Use the equality operator (==) to compare the dice results. 16 | 17 | ---------------------- 18 | 19 | import kotlin.random.Random 20 | 21 | fun main() { 22 | val firstResult = Random.nextInt(6) 23 | val secondResult = Random.nextInt(6) 24 | // Write your code here 25 | } 26 | 27 | */ 28 | 29 | import kotlin.random.Random 30 | 31 | fun main() { 32 | val firstResult = Random.nextInt(6) 33 | val secondResult = Random.nextInt(6) 34 | 35 | if (firstResult == secondResult) { 36 | println("You win :)") 37 | } else { 38 | println("You lose :(") 39 | } 40 | 41 | // val result = if (firstResult == secondResult) "You win :)" else "You lose :(" 42 | // println(result) 43 | } -------------------------------------------------------------------------------- /004-control-flow/practice-ce-test-answer-2.kt: -------------------------------------------------------------------------------- 1 | /*========================== 2 | Test 2 3 | ============================ 4 | Using a when expression, update the following program so that it prints 5 | the corresponding actions when you input the names of game console buttons. 6 | 7 | Button Action 8 | 9 | A Yes 10 | B No 11 | X Menu 12 | Y Nothing 13 | Other There is no such button 14 | 15 | fun main() { 16 | val button = "A" 17 | 18 | println( 19 | // Write your code here 20 | ) 21 | } 22 | 23 | */ 24 | 25 | 26 | fun main() { 27 | val button = "A" 28 | 29 | println( 30 | when(button){ 31 | "A" -> "Yes" 32 | "B" -> "No" 33 | "X" -> "Menu" 34 | "Y" -> "Nothing" 35 | else -> "There is no such button" 36 | } 37 | ) 38 | } -------------------------------------------------------------------------------- /004-control-flow/practice-loops-test-1.kt: -------------------------------------------------------------------------------- 1 | /*========================== 2 | Test 1 : 3 | ============================ 4 | 5 | You have a program that counts pizza slices until there’s a whole pizza with 8 slices. 6 | Refactor this program in two ways: 7 | 8 | Use a while loop. 9 | 10 | Use a do-while loop. 11 | 12 | ---------------------- 13 | */ 14 | 15 | fun main() { 16 | var pizzaSlices = 0 17 | // Start refactoring here 18 | pizzaSlices++ 19 | println("There's only $pizzaSlices slice/s of pizza :(") 20 | pizzaSlices++ 21 | println("There's only $pizzaSlices slice/s of pizza :(") 22 | pizzaSlices++ 23 | println("There's only $pizzaSlices slice/s of pizza :(") 24 | pizzaSlices++ 25 | println("There's only $pizzaSlices slice/s of pizza :(") 26 | pizzaSlices++ 27 | println("There's only $pizzaSlices slice/s of pizza :(") 28 | pizzaSlices++ 29 | println("There's only $pizzaSlices slice/s of pizza :(") 30 | pizzaSlices++ 31 | println("There's only $pizzaSlices slice/s of pizza :(") 32 | pizzaSlices++ 33 | // End refactoring here 34 | println("There are $pizzaSlices slices of pizza. Hooray! We have a whole pizza! :D") 35 | } 36 | -------------------------------------------------------------------------------- /004-control-flow/practice-loops-test-2.kt: -------------------------------------------------------------------------------- 1 | /*========================== 2 | Test 2 : 3 | ============================ 4 | 5 | Write a program that simulates the Fizz buzz game. 6 | Your task is to print numbers from 1 to 100 incrementally, 7 | replacing any number divisible by three with the word "fizz", 8 | and any number divisible by five with the word "buzz". 9 | Any number divisible by both 3 and 5 must be replaced with the word "fizzbuzz". 10 | 11 | Hint 1 12 | Use a for loop to count numbers and a when expression to decide what to print at each step. 13 | 14 | Hint 2 15 | Use the modulo operator (%) to return the remainder of a number being divided. 16 | Use the equality operator (==) to check if the remainder equals zero. 17 | 18 | ---------------------- 19 | */ 20 | 21 | fun main() { 22 | // Write your code here 23 | } 24 | 25 | 26 | -------------------------------------------------------------------------------- /004-control-flow/practice-loops-test-3.kt: -------------------------------------------------------------------------------- 1 | /*========================== 2 | Test 3 : 3 | ============================ 4 | 5 | You have a list of words. 6 | Use for and if to print only the words that start with the letter l. 7 | 8 | Hint 9 | Use the .startsWith() function for String type. 10 | ---------------------- 11 | */ 12 | 13 | fun main() { 14 | val words = listOf("dinosaur", "limousine", "magazine", "language") 15 | // Write your code here 16 | } 17 | -------------------------------------------------------------------------------- /004-control-flow/practice-loops-test-answer-1.kt: -------------------------------------------------------------------------------- 1 | fun main() { 2 | useWhile() 3 | 4 | println() 5 | 6 | useDoWhile() 7 | } 8 | 9 | fun useWhile(){ 10 | var pizzaSlices = 0 11 | // Start refactoring here 12 | 13 | while(pizzaSlices < 7){ 14 | pizzaSlices++ 15 | println("There's only $pizzaSlices slice/s of pizza :(") 16 | 17 | } 18 | pizzaSlices++ 19 | // End refactoring here 20 | println("There are $pizzaSlices slices of pizza. Hooray! We have a whole pizza! :D") 21 | } 22 | 23 | 24 | 25 | fun useDoWhile(){ 26 | var pizzaSlices = 0 27 | // Start refactoring here 28 | pizzaSlices++ 29 | do{ 30 | println("There's only $pizzaSlices slice/s of pizza :(") 31 | pizzaSlices++ 32 | }while(pizzaSlices < 8) 33 | 34 | // End refactoring here 35 | println("There are $pizzaSlices slices of pizza. Hooray! We have a whole pizza! :D") 36 | } -------------------------------------------------------------------------------- /004-control-flow/practice-loops-test-answer-2.kt: -------------------------------------------------------------------------------- 1 | /*========================== 2 | Test 2 : 3 | ============================ 4 | 5 | Write a program that simulates the Fizz buzz game. 6 | Your task is to print numbers from 1 to 100 incrementally, 7 | replacing any number divisible by three with the word "fizz", 8 | and any number divisible by five with the word "buzz". 9 | Any number divisible by both 3 and 5 must be replaced with the word "fizzbuzz". 10 | 11 | Hint 1 12 | Use a for loop to count numbers and a when expression to decide what to print at each step. 13 | 14 | Hint 2 15 | Use the modulo operator (%) to return the remainder of a number being divided. 16 | Use the equality operator (==) to check if the remainder equals zero. 17 | 18 | ---------------------- 19 | */ 20 | 21 | fun main() { 22 | 23 | for (number in 1..100) { 24 | println( 25 | when { 26 | number % 15 == 0 -> "fizzbuzz" 27 | number % 3 == 0 -> "fizz" 28 | number % 5 == 0 -> "buzz" 29 | else -> "$number" 30 | } 31 | ) 32 | } 33 | 34 | // for(number in 1..100){ 35 | // when{ 36 | // (number % 3 == 0 && number%5 == 0) -> println("fizzbuzz") 37 | // (number % 3 == 0) -> println("fizz") 38 | // (number % 5 == 0) -> println("buzz") 39 | // else -> println(number) 40 | 41 | // } 42 | // } 43 | } 44 | 45 | -------------------------------------------------------------------------------- /004-control-flow/practice-loops-test-answer-3.kt: -------------------------------------------------------------------------------- 1 | /*========================== 2 | Test 3 : 3 | ============================ 4 | 5 | You have a list of words. 6 | Use for and if to print only the words that start with the letter l. 7 | 8 | Hint 9 | Use the .startsWith() function for String type. 10 | ---------------------- 11 | */ 12 | 13 | fun main() { 14 | val words = listOf("dinosaur", "limousine", "magazine", "language") 15 | // Write your code here 16 | 17 | for(word in words){ 18 | if(word.startsWith("l")){ 19 | println(word) 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /004-control-flow/screenshot-conditional-expressions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NovaLogics/skills-training-cli-kotlin/d9f245d66017d29d72ba25843ad26dd9ac457b07/004-control-flow/screenshot-conditional-expressions.png -------------------------------------------------------------------------------- /005-basics-functions/1-functions-basics.kt: -------------------------------------------------------------------------------- 1 | /* Functions in Kotlin 2 | 3 | - Declaring Functions: 4 | * Use the fun keyword to declare a function. 5 | Example: 6 | fun hello() { 7 | println("Hello, world!") 8 | } 9 | 10 | - Function Parameters: 11 | * Parameters are placed within parentheses () 12 | * Each parameter must have a type. 13 | * Multiple parameters are separated by commas , 14 | Example: 15 | fun greet(name: String, age: Int) 16 | 17 | - Return Type: 18 | * The return type is specified after the parameters, separated by a colon :. 19 | * If the function doesn't return a value, the return type can be omitted, 20 | or Unit (equivalent to void in other languages) is used. 21 | Example: 22 | fun sum(x: Int, y: Int): Int 23 | 24 | - Return Keyword: 25 | * Use the return keyword to return a value from the function. 26 | Example: 27 | fun sum(x: Int, y: Int): Int { 28 | return x + y 29 | } 30 | 31 | - Function Naming Convention: 32 | * Functions should start with a lowercase letter and use camel case without underscores. 33 | Example: 34 | fun calculateTotal() 35 | 36 | Notes: 37 | If a function does not return anything useful, 38 | you can omit the return type and the return keyword. 39 | */ 40 | 41 | // Function with parameters and return type 42 | fun sum(x: Int, y: Int): Int { 43 | return x + y 44 | } 45 | 46 | fun main() { 47 | println(sum(1, 2)) // Output: 3 48 | } 49 | -------------------------------------------------------------------------------- /005-basics-functions/2-functions-named-arguments.kt: -------------------------------------------------------------------------------- 1 | /* Named Arguments in Kotlin 2 | 3 | - Named Arguments: 4 | * Allows you to specify parameter names when calling a function. 5 | * Improves code readability and clarity. 6 | 7 | - Order Flexibility: 8 | * When using named arguments, the order of parameters can be swapped. 9 | 10 | Notes: 11 | Named arguments are optional; you can still call functions without specifying parameter names. 12 | They are particularly helpful in functions with multiple parameters or default values. 13 | */ 14 | 15 | fun printMessageWithPrefix(message: String, prefix: String) { 16 | println("[$prefix] $message") 17 | } 18 | 19 | fun main() { 20 | // Named arguments with swapped order 21 | printMessageWithPrefix(prefix = "Log", message = "Hello") 22 | // Output: [Log] Hello 23 | } 24 | -------------------------------------------------------------------------------- /005-basics-functions/3-functions-default-parameter-values.kt: -------------------------------------------------------------------------------- 1 | /* Default Parameter Values in Kotlin 2 | 3 | - Default Values: 4 | * Parameters in a function can have default values. 5 | * Default values are assigned using the = operator after the parameter's type. 6 | 7 | - Optional Parameters: 8 | * If a parameter has a default value, it can be omitted when calling the function. 9 | 10 | - Using Default Values: 11 | * If a value is not provided for a parameter, the default value is used. 12 | 13 | Notes: 14 | * Default parameter values improve flexibility and reduce the need for overloaded functions. 15 | * Combine default values with named arguments for clear and concise function calls. 16 | 17 | * You can skip specific parameters with default values, rather than omitting them all. 18 | However, after the first skipped parameter, you must name all subsequent parameters. 19 | */ 20 | 21 | fun printMessageWithPrefix(message: String, prefix: String = "Info") { 22 | println("[$prefix] $message") 23 | } 24 | 25 | fun main() { 26 | // Function called with both parameters 27 | printMessageWithPrefix("Hello", "Log") 28 | // Output: [Log] Hello 29 | 30 | // Function called only with the message parameter 31 | printMessageWithPrefix("Hello") 32 | // Output: [Info] Hello 33 | 34 | // Function called using named arguments 35 | printMessageWithPrefix(prefix = "Log", message = "Hello") 36 | // Output: [Log] Hello 37 | } 38 | 39 | -------------------------------------------------------------------------------- /005-basics-functions/4-functions-without-return.kt: -------------------------------------------------------------------------------- 1 | /* Functions Without Return 2 | 3 | - Unit Return Type: 4 | * If a function doesn’t return a value, its return type is Unit. 5 | * Unit is a type with only one value, also called Unit. 6 | 7 | - Implicit Declaration: 8 | * You don’t need to explicitly declare the return type as Unit. 9 | * The return keyword is optional in these functions. 10 | 11 | Notes: 12 | * Functions without return types are often used for performing actions like printing, updating UI, or logging. 13 | * Kotlin makes such functions concise by eliminating the need for explicit return Unit statements. 14 | */ 15 | 16 | fun printMessage(message: String) { 17 | println(message) 18 | // `return Unit` or `return` is optional 19 | } 20 | 21 | // fun printMessage(message: String): Unit { 22 | // println(message) 23 | // // `return Unit` or `return` is optional 24 | // } 25 | 26 | fun main() { 27 | printMessage("Hello") 28 | // Output: Hello 29 | } 30 | 31 | 32 | -------------------------------------------------------------------------------- /005-basics-functions/5-functions-single-expression.kt: -------------------------------------------------------------------------------- 1 | /* Single-Expression Functions in Kotlin 2 | 3 | - Concise Functions: 4 | * Single-expression functions allow you to write more concise code. 5 | * The function body can be written in one line using the = operator instead of using curly braces {} and return. 6 | 7 | - Type Inference: 8 | * When using a single-expression function, 9 | Kotlin automatically infers the return type, 10 | but you can still explicitly define it for clarity. 11 | 12 | - No Return Keyword: 13 | * The return keyword is not needed in a single-expression function. 14 | * The return value is automatically inferred. 15 | 16 | Notes: 17 | * Single-expression functions are ideal for simple operations, making your code cleaner. 18 | * It’s a good practice to define the return type explicitly, even in single-expression functions, for better readability. 19 | * If you use {} in the function body, you must specify the return type unless it's Unit. 20 | */ 21 | 22 | 23 | fun sum(x: Int, y: Int) = x + y // Single-expression function 24 | 25 | // fun sum(x: Int, y: Int): Int { 26 | // return x + y 27 | // } 28 | 29 | fun main() { 30 | println(sum(1, 2)) 31 | // Output: 3 32 | } 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /005-basics-functions/6-functions-early-returns.kt: -------------------------------------------------------------------------------- 1 | /* Early Returns in Kotlin 2 | 3 | - Early Return: 4 | * You can exit a function early using the return keyword. 5 | * This is useful to stop further code execution if certain conditions are met. 6 | 7 | - Improves Readability: 8 | * Using early returns simplifies complex functions and prevents deeply nested code. 9 | 10 | - Conditional Early Return: 11 | * Combine if statements with return to exit the function when a condition is satisfied. 12 | 13 | Notes: 14 | * Early returns prevent unnecessary processing and improve performance. 15 | * Using early returns can make your code simpler and more readable by avoiding deeply nested conditions. 16 | */ 17 | 18 | 19 | val registeredUsernames = mutableListOf("john_doe", "jane_smith") 20 | val registeredEmails = mutableListOf("john@example.com", "jane@example.com") 21 | 22 | fun registerUser(username: String, email: String): String { 23 | // Early return if the username is already taken 24 | if (username in registeredUsernames) { 25 | return "Username already taken. Please choose a different username." 26 | } 27 | 28 | // Early return if the email is already registered 29 | if (email in registeredEmails) { 30 | return "Email already registered. Please use a different email." 31 | } 32 | 33 | // Proceed with the registration if the username and email are not taken 34 | registeredUsernames.add(username) 35 | registeredEmails.add(email) 36 | 37 | return "User registered successfully: $username" 38 | } 39 | 40 | fun main() { 41 | println(registerUser("john_doe", "newjohn@example.com")) 42 | // Output: Username already taken. Please choose a different username. 43 | 44 | println(registerUser("new_user", "newuser@example.com")) 45 | // Output: User registered successfully: new_user 46 | } 47 | -------------------------------------------------------------------------------- /005-basics-functions/practice-functions-test-1.kt: -------------------------------------------------------------------------------- 1 | /*========================== 2 | Test 1 : 3 | ============================ 4 | 5 | Write a function called circleArea that takes the radius of a circle 6 | in integer format as a parameter and outputs the area of that circle. 7 | ---------------------- 8 | */ 9 | 10 | import kotlin.math.PI 11 | 12 | // Write your code here 13 | 14 | fun main() { 15 | println(circleArea(2)) 16 | } -------------------------------------------------------------------------------- /005-basics-functions/practice-functions-test-2.kt: -------------------------------------------------------------------------------- 1 | /*========================== 2 | Test 2 : 3 | ============================ 4 | 5 | You have a function that translates a time interval given in hours, minutes, and seconds into seconds. 6 | In most cases, you need to pass only one or two function parameters while the rest are equal to 0. 7 | Improve the function and the code that calls it by using default parameter values and named arguments 8 | so that the code is easier to read. 9 | ---------------------- 10 | */ 11 | 12 | fun intervalInSeconds(hours: Int, minutes: Int, seconds: Int) = 13 | ((hours * 60) + minutes) * 60 + seconds 14 | 15 | fun main() { 16 | println(intervalInSeconds(1, 20, 15)) 17 | println(intervalInSeconds(0, 1, 25)) 18 | println(intervalInSeconds(2, 0, 0)) 19 | println(intervalInSeconds(0, 10, 0)) 20 | println(intervalInSeconds(1, 0, 1)) 21 | } -------------------------------------------------------------------------------- /005-basics-functions/practice-functions-test-answer-1.kt: -------------------------------------------------------------------------------- 1 | /*========================== 2 | Test 1 : 3 | ============================ 4 | 5 | Write a function called circleArea that takes the radius of a circle 6 | in integer format as a parameter and outputs the area of that circle. 7 | ---------------------- 8 | 9 | import kotlin.math.PI 10 | 11 | // Write your code here 12 | 13 | fun main() { 14 | println(circleArea(2)) 15 | } 16 | 17 | */ 18 | 19 | import kotlin.math.PI 20 | 21 | // Write your code here 22 | 23 | fun circleArea(radius: Int): Double = PI * (radius * radius) 24 | 25 | //OR 26 | 27 | // fun circleArea(radius: Int): Double { 28 | // return PI * radius * radius 29 | // } 30 | 31 | fun main() { 32 | println(circleArea(2)) // 12.566370614359172 33 | } -------------------------------------------------------------------------------- /005-basics-functions/practice-functions-test-answer-2.kt: -------------------------------------------------------------------------------- 1 | /*========================== 2 | Test 2 : 3 | ============================ 4 | 5 | You have a function that translates a time interval given in hours, minutes, and seconds into seconds. 6 | In most cases, you need to pass only one or two function parameters while the rest are equal to 0. 7 | Improve the function and the code that calls it by using default parameter values and named arguments 8 | so that the code is easier to read. 9 | ---------------------- 10 | 11 | fun intervalInSeconds(hours: Int, minutes: Int, seconds: Int) = 12 | ((hours * 60) + minutes) * 60 + seconds 13 | 14 | fun main() { 15 | println(intervalInSeconds(1, 20, 15)) 16 | println(intervalInSeconds(0, 1, 25)) 17 | println(intervalInSeconds(2, 0, 0)) 18 | println(intervalInSeconds(0, 10, 0)) 19 | println(intervalInSeconds(1, 0, 1)) 20 | } 21 | 22 | */ 23 | 24 | fun intervalInSeconds(hours: Int = 0, minutes: Int = 0, seconds: Int = 0) = 25 | ((hours * 60) + minutes) * 60 + seconds 26 | 27 | fun main() { 28 | println(intervalInSeconds(hours = 1, minutes = 20, seconds = 15)) 29 | println(intervalInSeconds(minutes = 1, seconds = 25)) 30 | println(intervalInSeconds(hours = 2)) 31 | println(intervalInSeconds(minutes = 10)) 32 | println(intervalInSeconds(hours = 1, seconds = 1)) 33 | } -------------------------------------------------------------------------------- /006-lambda-expressions/1-basics.kt: -------------------------------------------------------------------------------- 1 | /* Lambda Expressions in Kotlin 2 | 3 | - Lambda Expression: 4 | * A lambda expression is a concise way to define functions. 5 | * It allows you to write function logic in a more compact format. 6 | 7 | - Syntax: 8 | * A lambda expression consists of parameters (if any) followed by -> and the body of the function. 9 | * It can be assigned to a variable and invoked like a regular function. 10 | 11 | - Advantages: 12 | * Reduces verbosity. 13 | * Often used with higher-order functions like map, filter, etc. 14 | 15 | Notes: 16 | * The lambda upperCaseString is assigned to a variable and works similarly to the traditional function uppercaseString. 17 | * You can use lambda expressions to make your code more concise, 18 | especially when passing functions as arguments to other functions. 19 | 20 | ---------------------------------------------- 21 | 22 | fun uppercaseString(text: String): String { 23 | return text.uppercase() 24 | } 25 | 26 | fun main() { 27 | println(uppercaseString("hello")) 28 | // Output: HELLO 29 | } 30 | 31 | */ 32 | 33 | 34 | fun main() { 35 | // Define the lambda expression 36 | val upperCaseString = { text: String -> text.uppercase() } 37 | 38 | // Call the lambda 39 | println(upperCaseString("hello")) 40 | // Output: HELLO 41 | } 42 | 43 | 44 | -------------------------------------------------------------------------------- /006-lambda-expressions/2-structure.kt: -------------------------------------------------------------------------------- 1 | /* Lambda Expressions Explained 2 | 3 | Lambda expressions in Kotlin are a concise way to define anonymous functions. 4 | Here's a breakdown of how they work: 5 | 6 | Structure of a Lambda Expression: 7 | 8 | - Curly Braces {}: 9 | * The entire lambda expression is wrapped in curly braces. 10 | 11 | - Parameters: 12 | * You can declare parameters inside the curly braces, followed by ->. 13 | Example: 14 | { text: String -> ... } 15 | 16 | - Function Body: 17 | * After ->, you write the logic that the lambda performs. 18 | Example: 19 | { text: String -> text.uppercase() } 20 | 21 | If there are no parameters, the -> is omitted: 22 | Example: 23 | { println("Log message") } 24 | 25 | ---------------------------------------------- 26 | Summary: 27 | Lambda syntax: { parameters -> body } 28 | Assignment: Lambda expressions can be assigned to variables. 29 | Calling Lambda: The variable holding the lambda is called just like a function. 30 | */ 31 | 32 | 33 | fun main() { 34 | // Define the lambda expression 35 | val upperCaseString = { text: String -> text.uppercase() } 36 | 37 | // Call the lambda 38 | println(upperCaseString("hello")) 39 | // Output: HELLO 40 | } 41 | 42 | /* Lambda Expression Example: 43 | 44 | - Parameters: 45 | text is the parameter of type String. 46 | - Function Body: 47 | text.uppercase() is the body, where the function converts text to uppercase. 48 | - Assignment: 49 | The lambda is assigned to the upperCaseString variable. 50 | - Calling Lambda: 51 | The lambda is invoked by calling upperCaseString("hello"). 52 | */ 53 | 54 | 55 | //---------------------------------------------- 56 | 57 | // fun main() { 58 | // // Lambda without parameters 59 | // val logMessage = { println("Log message") } 60 | 61 | // // Calling the lambda 62 | // logMessage() 63 | // // Output: Log message 64 | // } 65 | 66 | //---------------------------------------------- 67 | 68 | /* Lambda Without Parameters: 69 | 70 | - No Parameters: 71 | Since no parameters are used, -> is not required. 72 | - Function Body: 73 | The body of the lambda simply prints "Log message". 74 | 75 | */ 76 | 77 | 78 | -------------------------------------------------------------------------------- /006-lambda-expressions/3-pass-to-another-function.kt: -------------------------------------------------------------------------------- 1 | /* Lambda Expressions in Kotlin 2 | - Uses of Lambda Expressions: 3 | |*| Pass a lambda as a parameter to another function. 4 | * Return a lambda from a function. 5 | * Invoke a lambda directly. 6 | */ 7 | 8 | fun main(){ 9 | println("\n~~~~~~~~~~~~~ SECTION 1 : Pass to another function : ex. Filter ~~~~~~~~~~~~~\n") 10 | passingToAnotherFunctionExFilter() 11 | 12 | println("\n~~~~~~~~~~~~~ SECTION 2 : Pass to another function : ex. Map ~~~~~~~~~~~~~\n") 13 | passingToAnotherFunctionExMap() 14 | 15 | println("\n~~~~~~~~~~~~~ SECTION 3 : Function Types ~~~~~~~~~~~~~\n") 16 | functionTypes() 17 | } 18 | 19 | 20 | /*=================================== 21 | SECTION 1 : Pass to another function : ex. Filter 22 | ============================ 23 | 24 | - Example with `.filter()` 25 | * Filters items in a collection based on a condition. 26 | 27 | - Key Points: 28 | * Lambda can be passed directly to the .filter() function. 29 | * Lambda can also be assigned to a variable (e.g., isNegative) and then passed to the function. 30 | 31 | - Trailing Lambda: 32 | * If the lambda is the only parameter, parentheses can be omitted: 33 | val positives = numbers.filter { x -> x > 0 } 34 | 35 | */ 36 | 37 | fun passingToAnotherFunctionExFilter() { 38 | 39 | println("FILTER: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n") 40 | 41 | val numbers = listOf(1, -2, 3, -4, 5, -6) 42 | 43 | // Keeps only positive numbers 44 | val positives = numbers.filter { x -> x > 0 } 45 | 46 | val isNegative = { x: Int -> x < 0 } 47 | 48 | // Keeps only negative numbers 49 | val negatives = numbers.filter(isNegative) 50 | 51 | println(positives) // Output: [1, 3, 5] 52 | println(negatives) // Output: [-2, -4, -6] 53 | } 54 | 55 | 56 | 57 | /*=================================== 58 | SECTION 2 : Pass to another function : ex. Map 59 | ============================ 60 | 61 | * The .map() function is used to transform each element in a collection by applying a given lambda expression. 62 | 63 | * It creates a new list where each element is the result of the transformation. 64 | 65 | ----------------------- 66 | - Key Points: 67 | * .map() always returns a new list without modifying the original one. 68 | * The lambda specifies the transformation logic for each element. 69 | * You can use .map() for various transformations, such as: 70 | > Scaling numbers 71 | > Converting data types (e.g., Int to String) 72 | > Applying complex operations to each item in the list. 73 | ----------------------- 74 | */ 75 | 76 | fun passingToAnotherFunctionExMap() { 77 | 78 | println("MAP: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n") 79 | 80 | val numbers = listOf(1, -2, 3, -4, 5, -6) 81 | 82 | // Multiply each number by 2 using a lambda 83 | val doubled = numbers.map { x -> x * 2 } 84 | 85 | // Multiply each number by 3 using a predefined lambda 86 | val isTripled = { x: Int -> x * 3 } 87 | 88 | val tripled = numbers.map(isTripled) 89 | 90 | println(doubled) // Output: [2, -4, 6, -8, 10, -12] 91 | println(tripled) // Output: [3, -6, 9, -12, 15, -18] 92 | 93 | } 94 | 95 | 96 | 97 | /*=================================== 98 | SECTION 3 : Function Types 99 | ============================ 100 | 101 | > Functions in Kotlin also have types, just like variables and objects. 102 | > function type defines: 103 | * Parameter types: The types of inputs the function accepts. 104 | * Return type: The type of value the function returns. 105 | 106 | ----------------------- 107 | > Syntax for Function Types 108 | - Format: 109 | (parameterType1, parameterType2, ...) -> returnType 110 | Examples: 111 | * (String) -> String → A function that takes one `String` and returns a `String`. 112 | * (Int, Int) -> Int → A function that takes two `Int` values and returns an `Int`. 113 | 114 | - For no parameters: 115 | * Use empty parentheses: () -> Unit. 116 | 117 | ----------------------- 118 | 119 | > Why Function Types Matter 120 | - Clarity: They make it clear what inputs and outputs a function expects. 121 | - Reusability: Functions can be passed around as values. 122 | - Type Safety: The compiler ensures only functions of the specified type are used. 123 | 124 | 125 | */ 126 | 127 | fun functionTypes() { 128 | 129 | /* Defining a Function Type */ 130 | val upperCaseString: (String) -> String = { text -> text.uppercase() } 131 | 132 | println(upperCaseString("hello")) // Output: HELLO 133 | 134 | println("\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n") 135 | 136 | /* Function with No Parameters */ 137 | 138 | val printLog: () -> Unit = { println("Log message") } 139 | 140 | printLog() // Output: Log message 141 | } 142 | 143 | 144 | /*=================================== 145 | SECTION 3 : Function Types : Notes 146 | ============================ 147 | 148 | > You must either: 149 | - Specify parameter and return types explicitly in the lambda. 150 | - OR define a function type for the variable holding the lambda. 151 | 152 | ---------------------------------------------- 153 | Example of Invalid Code: 154 | 155 | val upperCaseString = { str -> str.uppercase() } // Won't work, type is missing. 156 | 157 | Fix by specifying the type: 158 | val upperCaseString: (String) -> String = { str -> str.uppercase() } 159 | 160 | ---------------------------------------------- 161 | 162 | Use function types when: 163 | - Assigning lambda expressions to variables. 164 | - Passing functions as parameters to other functions. 165 | */ 166 | -------------------------------------------------------------------------------- /006-lambda-expressions/4-returning-lambda-expressions.kt: -------------------------------------------------------------------------------- 1 | /* Lambda Expressions in Kotlin 2 | - Uses of Lambda Expressions: 3 | * Pass a lambda as a parameter to another function. 4 | |*| Return a lambda from a function. 5 | * Invoke a lambda directly. 6 | */ 7 | 8 | fun main(){ 9 | println("\n~~~~~~~~~~~~~ SECTION 1 : Returning Lambda Expressions ~~~~~~~~~~~~~\n") 10 | returningLambdaExpressions() 11 | } 12 | 13 | 14 | /*=================================== 15 | SECTION 1 : Returning Lambda Expressions 16 | ============================ 17 | 18 | > Lambda expressions can be returned from a function, allowing for highly flexible and reusable code. 19 | 20 | > Key Requirements 21 | * To return a lambda, you must declare the function type of the lambda expression. 22 | * Example: (Int) -> Int means the lambda takes an Int as input and returns an Int. 23 | 24 | > Example: toSeconds Function 25 | 26 | The toSeconds() function: 27 | * Takes a String (e.g., "hour", "minute", "second"). 28 | * Returns a lambda that converts time values to seconds. 29 | */ 30 | 31 | fun toSeconds(time: String): (Int) -> Int = when (time) { 32 | "hour" -> { value -> value * 60 * 60 } // Converts hours to seconds 33 | "minute" -> { value -> value * 60 } // Converts minutes to seconds 34 | "second" -> { value -> value } // Seconds remain unchanged 35 | else -> { value -> value } // Default: Returns the value as-is 36 | } 37 | 38 | 39 | fun returningLambdaExpressions() { 40 | 41 | val timesInMinutes = listOf(2, 10, 15, 1) // List of times in minutes 42 | 43 | val min2sec = toSeconds("minute") // Get lambda for minute-to-second conversion 44 | 45 | val totalTimeInSeconds = timesInMinutes.map(min2sec).sum() // Convert & sum in seconds 46 | 47 | println("Total time is $totalTimeInSeconds secs") 48 | // Output: Total time is 1680 secs 49 | 50 | } 51 | 52 | /* How It Works: 53 | 54 | > Function Type: 55 | - toSeconds returns (Int) -> Int, meaning a lambda that takes an Int and returns an Int. 56 | 57 | > when Expression: 58 | Based on the input ("hour", "minute", "second"), it returns the appropriate lambda: 59 | - "hour": Multiplies the input by 3600. 60 | - "minute": Multiplies the input by 60. 61 | - "second": Returns the input unchanged. 62 | - Default: Returns the input unchanged. 63 | 64 | > map with Lambda: 65 | - The toSeconds("minute") function returns the "minute" lambda. 66 | - timesInMinutes.map(min2sec) converts each minute to seconds in the list. 67 | 68 | > Sum: 69 | - .sum() adds all the converted values. 70 | */ 71 | -------------------------------------------------------------------------------- /006-lambda-expressions/5-invoke-separately.kt: -------------------------------------------------------------------------------- 1 | /* Lambda Expressions in Kotlin 2 | - Uses of Lambda Expressions: 3 | * Pass a lambda as a parameter to another function. 4 | * Return a lambda from a function. 5 | |*| Invoke a lambda directly. 6 | */ 7 | 8 | fun main(){ 9 | println("\n~~~~~~~~~~~~~ SECTION 1 : Invoking Lambda Expressions Directly ~~~~~~~~~~~~~\n") 10 | invokingLambdaExpressionsDirectly() 11 | 12 | println("\n~~~~~~~~~~~~~ SECTION 2 : Trailing Lambdas ~~~~~~~~~~~~~\n") 13 | trailingLambdas() 14 | } 15 | 16 | 17 | /*=================================== 18 | SECTION 1 : Invoking Lambda Expressions Directly 19 | ============================ 20 | 21 | > Invoking Lambda Expressions Directly 22 | * You can invoke a lambda expression directly by adding parentheses `()` after the curly braces `{}` 23 | * Pass any required parameters inside the parentheses. 24 | 25 | */ 26 | 27 | fun invokingLambdaExpressionsDirectly() { 28 | 29 | println({ text: String -> text.uppercase() }("hello")) 30 | // Output: HELLO 31 | 32 | } 33 | 34 | 35 | 36 | /*=================================== 37 | SECTION 2 : Trailing Lambdas 38 | ============================ 39 | 40 | > Trailing lambda: A special syntax for lambda expressions used as the last parameter of a function. 41 | > If a lambda is the only parameter, you can omit the function's parentheses () entirely. 42 | 43 | Example with `.fold()` : 44 | The .fold() function: 45 | * Takes an initial value and a lambda operation. 46 | * The lambda operates cumulatively on each element of the list. 47 | 48 | */ 49 | 50 | fun trailingLambdas() { 51 | 52 | // Standard syntax 53 | println(listOf(1, 2, 3).fold(0, { x, item -> x + item })) // Output: 6 54 | 55 | // Trailing lambda syntax 56 | println(listOf(1, 2, 3).fold(0) { x, item -> x + item }) // Output: 6 57 | } 58 | 59 | /* 60 | How It Works: 61 | > Without Trailing Lambda: 62 | - The lambda is passed as the second argument inside the parentheses. 63 | - Example: .fold(0, { x, item -> x + item }). 64 | 65 | > With Trailing Lambda: 66 | - The lambda is moved outside the parentheses because it's the last parameter. 67 | Example: .fold(0) { x, item -> x + item }. 68 | */ 69 | -------------------------------------------------------------------------------- /006-lambda-expressions/practice-test-1.kt: -------------------------------------------------------------------------------- 1 | /*========================== 2 | Test 1 : 3 | ============================ 4 | 5 | You have a list of actions supported by a web service, a common prefix for all requests, 6 | and an ID of a particular resource. To request an action title over the resource with ID: 5, 7 | you need to create the following 8 | URL: https://example.com/book-info/5/title. 9 | Use a lambda expression to create a list of URLs from the list of actions. 10 | ---------------------- 11 | */ 12 | 13 | fun main() { 14 | val actions = listOf("title", "year", "author") 15 | val prefix = "https://example.com/book-info" 16 | val id = 5 17 | val urls = // Write your code here 18 | println(urls) 19 | } -------------------------------------------------------------------------------- /006-lambda-expressions/practice-test-2.kt: -------------------------------------------------------------------------------- 1 | /*========================== 2 | Test 2 : 3 | ============================ 4 | 5 | Write a function that takes an Int value and an action (a function with type () -> Unit) 6 | which then repeats the action the given number of times. 7 | Then use this function to print “Hello” 5 times. 8 | 9 | ---------------------- 10 | */ 11 | 12 | fun repeatN(n: Int, action: () -> Unit) { 13 | // Write your code here 14 | } 15 | 16 | fun main() { 17 | // Write your code here 18 | } -------------------------------------------------------------------------------- /006-lambda-expressions/practice-test-answer-1.kt: -------------------------------------------------------------------------------- 1 | fun main() { 2 | val actions = listOf("title", "year", "author") 3 | val prefix = "https://example.com/book-info" 4 | val id = 5 5 | val urls = actions.map { action -> "$prefix/$id/$action" } 6 | println(urls) 7 | } -------------------------------------------------------------------------------- /006-lambda-expressions/practice-test-answer-2.kt: -------------------------------------------------------------------------------- 1 | fun repeatN(n: Int, action: () -> Unit) { 2 | for (i in 1..n) { 3 | action() 4 | } 5 | } 6 | 7 | fun main() { 8 | repeatN(5) { 9 | println("Hello") 10 | } 11 | } -------------------------------------------------------------------------------- /007-basics-classes/1-basics.kt: -------------------------------------------------------------------------------- 1 | /* Classes in Kotlin 2 | 3 | - Kotlin supports object oriented programming using classes and objects. 4 | - Classes define a blueprint for creating objects, which can store data and behavior. 5 | - Objects are created based on the structure defined in the class. 6 | 7 | Declaring a Class 8 | - Use the class keyword to define a class. 9 | Example: 10 | class Customer 11 | 12 | 13 | Why Use Classes? 14 | - Efficiency: Avoid repeating code for objects with similar characteristics. 15 | - Modularity: Group related data and functions together. 16 | - Reusability: Define the behavior and properties once, and reuse them across multiple objects. 17 | */ 18 | class Customer() 19 | -------------------------------------------------------------------------------- /007-basics-classes/2-structure.kt: -------------------------------------------------------------------------------- 1 | /*=================================== 2 | SECTION 1 : Properties in Kotlin Classes 3 | ============================ 4 | 5 | Properties represent the characteristics of a class's object. 6 | 7 | */ 8 | 9 | // Declaring Properties 10 | 11 | // [ 1 ] In the Class Header (in parentheses after class name): 12 | class Contact(val id: Int, var email: String) 13 | 14 | // [ 2 ] Inside the Class Body (within curly braces): 15 | class ContactDetails(val id: Int, var email: String) { 16 | val category: String = "work" 17 | } 18 | 19 | // ---------------------------------- 20 | 21 | // Read-only Properties: Use val to declare properties that can’t be modified after object creation. 22 | // Example: 23 | class ContactTest1(val category: String = "work") 24 | 25 | // Mutable Properties: Use var to allow properties to be changed. 26 | // Example: 27 | class ContactTest2(var email: String) 28 | 29 | // Default Values: You can assign default values to properties. 30 | // Example: 31 | class ContactTest3(val id: Int, var email: String = "example@gmail.com") 32 | 33 | // ---------------------------------- 34 | 35 | // Creating an Object 36 | // Objects are created by declaring an instance of a class using the constructor. 37 | val contact = Contact(1, "mary@gmail.com") 38 | 39 | // Accessing Properties 40 | // Use a period . to access or modify a property of an instance. 41 | // Access property 42 | // println(contact.email) 43 | // Modify property 44 | // contact.email = "jane@gmail.com" 45 | 46 | // String Templates 47 | // You can concatenate property values into strings using ${} 48 | // println("Their email address is: ${contact.email}") 49 | 50 | // ======================================================================= 51 | 52 | /*=================================== 53 | SECTION 2 : Member Functions in Kotlin 54 | ============================ 55 | */ 56 | 57 | // Member functions define an object's behavior and are declared inside the class body. Example: 58 | class ContactTest4(val id: Int, var email: String) { 59 | fun printId() { 60 | println(id) 61 | } 62 | } 63 | 64 | // To call a member function on an object, use the object instance followed by a period . and the 65 | // function name: 66 | fun main() { 67 | val contact = ContactTest4(1, "a@gmail.com") 68 | contact.printId() // Calls the printId function 69 | 70 | // Access property 71 | println(contact.email) 72 | // Modify property 73 | contact.email = "jane@gmail.com" 74 | 75 | // String Templates 76 | // You can concatenate property values into strings using ${} 77 | println("Their email address is: ${contact.email}") 78 | } 79 | -------------------------------------------------------------------------------- /007-basics-classes/3-data-classes.kt: -------------------------------------------------------------------------------- 1 | /* Data Classes in Kotlin 2 | > Purpose: 3 | Data classes are useful for storing data and come with built-in functions for common tasks like 4 | printing, comparing, and copying instances. 5 | 6 | > Declaring a Data Class 7 | To declare a data class, use the data keyword 8 | 9 | data class User(val name: String, val id: Int) 10 | 11 | > Predefined Functions in Data Classes 12 | * toString(): Prints a readable string of the class instance and its properties. 13 | * equals() or ==: Compares two instances for equality. 14 | * copy(): Creates a copy of an instance, optionally with modified properties. 15 | */ 16 | 17 | data class User(val name: String, val id: Int) 18 | 19 | 20 | fun main(){ 21 | println("\n~~~~~~~~~~~~~ SECTION 1 : Print as String ~~~~~~~~~~~~~\n") 22 | printAsString() 23 | 24 | println("\n~~~~~~~~~~~~~ SECTION 2 : Compare Instances ~~~~~~~~~~~~~\n") 25 | compareInstances() 26 | 27 | println("\n~~~~~~~~~~~~~ SECTION 3 : Copy Instance ~~~~~~~~~~~~~\n") 28 | copyInstance() 29 | 30 | } 31 | 32 | 33 | /*=================================== 34 | SECTION 1 : Print as String 35 | ============================ 36 | 37 | The toString() function is automatically called when you print the object. 38 | */ 39 | 40 | fun printAsString() { 41 | val user = User("Alex", 1) 42 | println(user) // Output: User(name=Alex, id=1) 43 | } 44 | 45 | 46 | 47 | /*=================================== 48 | SECTION 2 : Compare Instances 49 | ============================ 50 | 51 | Use the equality operator == to compare two data class instances 52 | */ 53 | 54 | fun compareInstances() { 55 | val user = User("Alex", 1) 56 | val secondUser = User("Alex", 1) 57 | val thirdUser = User("Max", 2) 58 | 59 | println(user == secondUser) // Output: true 60 | println(user == thirdUser) // Output: false 61 | } 62 | 63 | 64 | /*=================================== 65 | SECTION 3 : Copy Instance 66 | ============================ 67 | 68 | Use the copy() function to create a copy of a data class instance. 69 | You can also modify properties when copying. 70 | */ 71 | fun copyInstance() { 72 | val user = User("Alex", 1) 73 | 74 | // Exact copy of user 75 | println(user.copy()) // Output: User(name=Alex, id=1) 76 | 77 | // Copy with modified name 78 | println(user.copy(name = "Max")) // Output: User(name=Max, id=1) 79 | 80 | // Copy with modified id 81 | println(user.copy(id = 3)) // Output: User(name=Alex, id=3) 82 | } 83 | -------------------------------------------------------------------------------- /007-basics-classes/practice-test-1.kt: -------------------------------------------------------------------------------- 1 | /*========================== 2 | Test 1 : 3 | ============================ 4 | 5 | Define a data class Employee with two properties: 6 | one for a name, and another for a salary. 7 | Make sure that the property for salary is mutable, 8 | otherwise you won’t get a salary boost at the end of the year! 9 | The main function demonstrates how you can use this data class. 10 | ---------------------- 11 | */ 12 | 13 | // Write your code here 14 | 15 | fun main() { 16 | val emp = Employee("Mary", 20) 17 | println(emp) 18 | emp.salary += 10 19 | println(emp) 20 | } -------------------------------------------------------------------------------- /007-basics-classes/practice-test-2.kt: -------------------------------------------------------------------------------- 1 | /*========================== 2 | Test 2 : 3 | ============================ 4 | 5 | Declare the additional data classes that are needed for this code to compile. 6 | 7 | ---------------------- 8 | */ 9 | 10 | data class Person(val name: Name, val address: Address, val ownsAPet: Boolean = true) 11 | // Write your code here 12 | // data class Name(...) 13 | 14 | fun main() { 15 | val person = Person( 16 | Name("John", "Smith"), 17 | Address("123 Fake Street", City("Springfield", "US")), 18 | ownsAPet = false 19 | ) 20 | println(person) 21 | } -------------------------------------------------------------------------------- /007-basics-classes/practice-test-3.kt: -------------------------------------------------------------------------------- 1 | /*========================== 2 | Test 3 : 3 | ============================ 4 | 5 | To test your code, you need a generator that can create random employees. 6 | Define a RandomEmployeeGenerator class with a fixed list of potential names 7 | (inside the class body). Configure the class with a minimum and maximum salary 8 | (inside the class header). In the class body, define the generateEmployee() function. 9 | Once again, the main function demonstrates how you can use this class. 10 | 11 | In this exercise, you import a package so that you can use the Random.nextInt() function. 12 | For more information about importing packages, see Packages and imports. 13 | 14 | ---------------------- 15 | Hint 1 16 | Lists have an extension function called .random() that returns a random item within a list. 17 | 18 | ---------------------- 19 | 20 | Hint 2 21 | Random.nextInt(from = ..., until = ...) gives you a random Int number within specified limits. 22 | */ 23 | 24 | import kotlin.random.Random 25 | 26 | data class Employee(val name: String, var salary: Int) 27 | 28 | // Write your code here 29 | 30 | fun main() { 31 | val empGen = RandomEmployeeGenerator(10, 30) 32 | println(empGen.generateEmployee()) 33 | println(empGen.generateEmployee()) 34 | println(empGen.generateEmployee()) 35 | empGen.minSalary = 50 36 | empGen.maxSalary = 100 37 | println(empGen.generateEmployee()) 38 | } -------------------------------------------------------------------------------- /007-basics-classes/practice-test-answer-1.kt: -------------------------------------------------------------------------------- 1 | data class Employee(val name: String, var salary: Int) 2 | 3 | fun main() { 4 | val emp = Employee("Mary", 20) 5 | println(emp) 6 | emp.salary += 10 7 | println(emp) 8 | } -------------------------------------------------------------------------------- /007-basics-classes/practice-test-answer-2.kt: -------------------------------------------------------------------------------- 1 | data class Person(val name: Name, val address: Address, val ownsAPet: Boolean = true) 2 | data class Name(val first: String, val last: String) 3 | data class Address(val street: String, val city: City) 4 | data class City(val name: String, val countryCode: String) 5 | 6 | fun main() { 7 | val person = Person( 8 | Name("John", "Smith"), 9 | Address("123 Fake Street", City("Springfield", "US")), 10 | ownsAPet = false 11 | ) 12 | println(person) 13 | } -------------------------------------------------------------------------------- /007-basics-classes/practice-test-answer-3.kt: -------------------------------------------------------------------------------- 1 | import kotlin.random.Random 2 | 3 | data class Employee(val name: String, var salary: Int) 4 | 5 | class RandomEmployeeGenerator(var minSalary: Int, var maxSalary: Int) { 6 | val names = listOf("John", "Mary", "Ann", "Paul", "Jack", "Elizabeth") 7 | fun generateEmployee() = 8 | Employee(names.random(), 9 | Random.nextInt(from = minSalary, until = maxSalary)) 10 | } 11 | 12 | fun main() { 13 | val empGen = RandomEmployeeGenerator(10, 30) 14 | println(empGen.generateEmployee()) 15 | println(empGen.generateEmployee()) 16 | println(empGen.generateEmployee()) 17 | empGen.minSalary = 50 18 | empGen.maxSalary = 100 19 | println(empGen.generateEmployee()) 20 | } -------------------------------------------------------------------------------- /008-basics-null-safety/1-basics.kt: -------------------------------------------------------------------------------- 1 | /* Null Safety in Kotlin 2 | 3 | > Purpose: 4 | Kotlin's null safety system helps prevent issues that could arise when working with null values. 5 | It ensures that null values are handled safely, detecting potential problems at compile time instead of runtime. 6 | */ 7 | 8 | /** 9 | * Key Features of Kotlin's Null Safety 10 | * 11 | * 1. Explicit Declaration of Nullability: 12 | * - You can declare when a variable can hold a null value. 13 | * - Use ? to declare a nullable type: 14 | * var name: String? = null 15 | * - Without ?, the variable can't hold null: 16 | * var name: String = "Alex" // Cannot be null 17 | * 18 | * 2. Checking for Null Values: 19 | * - You can check if a variable is null using ?. (safe call operator). 20 | * val length = name?.length // Returns null if name is null, otherwise returns length 21 | * 22 | * 3. Safe Calls: 23 | * - Safe calls allow you to access properties or call functions on a nullable variable without causing a NullPointerException. 24 | * val length = name?.length // Returns null if name is null 25 | * 26 | * 4. Elvis Operator (? :): 27 | * - The Elvis operator allows you to provide a default value when a nullable variable is null. 28 | * val length = name?.length ?: 0 // If name is null, returns 0 29 | * 30 | * 5. Null Assertions (!!): 31 | * - Use !! to assert that a variable is not null. If the variable is null, it will throw a NullPointerException. 32 | * val length = name!!.length // Throws NullPointerException if name is null 33 | */ 34 | 35 | 36 | fun main() { 37 | var name: String? = null 38 | println(name?.length) // Output: null 39 | 40 | name = "Kotlin" 41 | println(name?.length) // Output: 6 42 | 43 | // Using Elvis operator 44 | val length = name?.length ?: 0 45 | println(length) // Output: 6 46 | 47 | // Using !! operator (be careful) 48 | println(name!!.length) // Output: 6 49 | } 50 | 51 | -------------------------------------------------------------------------------- /008-basics-null-safety/2-nullable-types.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * Nullable types in Kotlin 3 | * 4 | * Kotlin allows variables to have nullable types, which means they can hold null values. By 5 | * default, types are non-nullable. To allow nulls, explicitly add ? after the type. 6 | * 7 | * Example: 8 | */ 9 | fun main() { 10 | // neverNull has String type (non-nullable) 11 | var neverNull: String = "This can't be null" 12 | 13 | // Throws a compiler error because 'neverNull' can't be null 14 | // neverNull = null 15 | 16 | // nullable has nullable String type 17 | var nullable: String? = "You can keep a null here" 18 | 19 | // This is OK, nullable can hold null 20 | nullable = null 21 | 22 | // By default, null values aren't accepted in inferredNonNull 23 | var inferredNonNull = "The compiler assumes non-nullable" 24 | 25 | // Throws a compiler error because inferredNonNull can't be null 26 | // inferredNonNull = null 27 | 28 | // notNull doesn't accept null values 29 | fun strLength(notNull: String): Int { 30 | return notNull.length 31 | } 32 | 33 | println(strLength(neverNull)) // 18 34 | // Throws a compiler error because 'nullable' is nullable 35 | // println(strLength(nullable)) // Compiler error 36 | } 37 | -------------------------------------------------------------------------------- /008-basics-null-safety/3-check-for-null-values.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * Checking for null values in Kotlin 3 | * 4 | * You can check for null values using conditional expressions, such as 'if' statements. 5 | * In the example below, we use an 'if' condition to check if the string is not null and has a length greater than zero. 6 | * 7 | * Example: 8 | * 9 | */ 10 | fun describeString(maybeString: String?): String { 11 | if (maybeString != null && maybeString.length > 0) { 12 | return "String of length ${maybeString.length}" 13 | } else { 14 | return "Empty or null string" 15 | } 16 | } 17 | 18 | fun main() { 19 | val nullString: String? = null 20 | println(describeString(nullString)) 21 | } 22 | 23 | -------------------------------------------------------------------------------- /008-basics-null-safety/4-use-safe-calls.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * Safe Calls in Kotlin 3 | * 4 | * To access properties of an object that may be null, use the safe call operator (?.). This allows 5 | * you to safely access properties without triggering a NullPointerException if the object or any of 6 | * its properties are null. 7 | * 8 | */ 9 | 10 | fun lengthString(maybeString: String?): Int? = maybeString?.length 11 | 12 | /** 13 | * Safe calls can also be chained to safely access nested properties: 14 | * 15 | * person.company?.address?.country 16 | * 17 | * Additionally, you can safely call extension or member functions on nullable objects. If the 18 | * object is null, the function call is skipped: 19 | */ 20 | 21 | fun main() { 22 | val nullString: String? = null 23 | println(nullString?.uppercase()) // Output: null 24 | 25 | println(lengthString(nullString)) // Output: null 26 | } 27 | -------------------------------------------------------------------------------- /008-basics-null-safety/5-use-elvis-operator.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * Elvis Operator in Kotlin 3 | * 4 | * The Elvis operator (?:) allows you to provide a default value when a nullable expression results 5 | * in null. This helps avoid null pointer exceptions and allows for fallback values. 6 | * 7 | * Syntax: expression?.property ?: defaultValue 8 | * 9 | * In the example below, the nullString is null, so the safe call (?.) results in a null value. The 10 | * Elvis operator (?:) provides a default value (0) in place of the null value: 11 | */ 12 | fun main() { 13 | val nullString: String? = null 14 | println(nullString?.length ?: 0) // Output: 0 15 | } 16 | -------------------------------------------------------------------------------- /008-basics-null-safety/practice-test-1.kt: -------------------------------------------------------------------------------- 1 | /*========================== 2 | Test 1 : 3 | ============================ 4 | 5 | You have the employeeById function that gives you access to a database of employees of a company. 6 | Unfortunately, this function returns a value of the Employee? type, so the result can be null. 7 | Your goal is to write a function that returns the salary of an employee when their id is provided, 8 | or 0 if the employee is missing from the database. 9 | ---------------------- 10 | */ 11 | 12 | data class Employee (val name: String, var salary: Int) 13 | 14 | fun employeeById(id: Int) = when(id) { 15 | 1 -> Employee("Mary", 20) 16 | 2 -> null 17 | 3 -> Employee("John", 21) 18 | 4 -> Employee("Ann", 23) 19 | else -> null 20 | } 21 | 22 | fun salaryById(id: Int) = // Write your code here 23 | 24 | fun main() { 25 | println((1..5).sumOf { id -> salaryById(id) }) 26 | } -------------------------------------------------------------------------------- /008-basics-null-safety/practice-test-answer-1.kt: -------------------------------------------------------------------------------- 1 | data class Employee (val name: String, var salary: Int) 2 | 3 | fun employeeById(id: Int) = when(id) { 4 | 1 -> Employee("Mary", 20) 5 | 2 -> null 6 | 3 -> Employee("John", 21) 7 | 4 -> Employee("Ann", 23) 8 | else -> null 9 | } 10 | 11 | fun salaryById(id: Int) = employeeById(id)?.salary ?: 0 12 | 13 | fun main() { 14 | println((1..5).sumOf { id -> salaryById(id) }) 15 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Shavinda / Nova 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | ❮ Skills Training ❯ 3 | 4 | Kotlin Language 5 |

6 | 7 |
8 | 9 | ### Kotlin Basics Training 10 | 11 | This repository is for learning Kotlin basics. It has mostly theory, with a few test exercises to help you practice. The theory is based on the official Kotlin documentation, and the exercises help you test what you learn. You can follow along with examples from the Kotlin Tour to understand the main concepts and test your knowledge. 12 | 13 | Source : https://kotlinlang.org/docs/kotlin-tour-hello-world.html --------------------------------------------------------------------------------