Kotlin Coursera class

Published on February 6, 2021

Reading time: 89 minutes

Kotlin for Java Developers

Class link

> Click here to get the exams code

Kotlin is a general purpose language which supports both functional programming and object-oriented programming paradigms. It's an open-source project developed mainly by JetBrains with the help of the community. Like Java, Kotlin is a statically typed language. However, in Kotlin you can omit the types, and it often looks as concise as some other dynamically-typed languages. Kotlin is safe, it's even safer than Java in terms that, Kotlin compiler can help to prevent even more possible types of errors. One of the main characteristics of Kotlin is its good interoperability with Java. It's hard now to call Java a modern language. However, it has so huge ecosystem, that it would be really difficult to recreate it from scratch if you use a new language

Cool features


> Having a class Pet that can be Dog or cat, we can do pet.meow() or pet.woof(), kotlin is going to do the cast for us

1val regex = """\d{2}\.d{2}\.\d{4}"""
2regex.matches("15.02.2016")
1fun getAnswer() = 42
2getAnswer() eq 42 // true
3getAnswer() eq 43 // false
1var StringBuilder.lastChar:Char
2get() = get(length -1)
3set(value:Char) {
4    this.setCharAt(length-1, value)
5}
6
7val sb = StringBuilder("Kotlin")
8sb.lastChar = '!'
9println(sb) // Kotlin!
 1sealed class Expr
 2class Num(val value: Int): Expr()
 3class Sum(val left:Expr, val Right: Expr): Expr()
 4
 5fun eval(e:Expr):Int = when(e) {
 6    is Num -> e.value
 7    is Sum -> eval(e.left) + eval(e.right)
 8}
 9
10// withouth the sealed modifier, it would need an else for it doesn't know where all the class inheriting from Expr are.

Data initialization, var, val and const


> about const: It's value is known at compile time, the compiler replaces the constant name everywhere in the code with this value, then it's called a compile-time constant.

We can initialize two variables using a Pair

1fun updateWeather(degrees: Int) {
2    val(description, color) = when {
3        degrees < 10 -> Pair("cold", BLUE)
4        degrees < 25 -> "mild" to ORANGE // same as pair
5        else -> "hot" to RED
6    }
7}

Lists


Kotlin has mutable an imutable lists, such as:

1val mutableList = mutableListOf("java")
2mutableList.add("Kotlin")
3
4val readOnlyList = listOf("Java")
5readOnlyList.add("Kotlin") // <- that doesnt work

Data class


Data modifier has equals, hashCode, to-string, and copy methods.

data class person(val name) it creates:

we can copy the instance and give the new attributes

1data class Contact(val name:String, val address: String)
2
3contact.copy(address="mew address")

Functions


return void here is of type Unit

We can define 3 types of fuctions

1fun topLevel() = 1
1class A {
2 fun member() = 2
3}
1fun other() {
2    fun local() = 3
3}
1fun max(a:Int, b:Int):Int {
2    return if (a>b) a else b
3}
4// that becomes
5fun max(a:Int, b:Int) = if (a>b) a else b

Library functions (run, let, takeIf...)

functions that look like built-in language construct

1val foo = run{
2    println("calculating")
3    "foo"
4}
1fun getEmail() : Email?
2val email = getEmail()
3
4if(email != null) sendEmailTo(email)
5// or better
6email?.let {e -> sendEmailTo(e)}

or to functions

1fun analizeUserSession(session:Session) = 
2    (session.user as? FacebookUser)?.let {
3        println(it.accountId)
4    }
 1issue.takeIf { it.status == FIXED }
 2person.patronymicName.takeIf(String::isNotEmpty)
 3
 4val number = 42 
 5number.takeIf { it > 10 } // 42
 6// if the predicate don't fullfill, it returns null
 7
 8issues.filter { it.status == OPEN }.takeIf(List<Issue>::isNotEmpty)?.let { println("some are open")}
 9
10// filtering
11people.mapNotNull { person -> person.takeIf { it.isPublicProfile }?.name }
1repeat(10) { println("Welcome!")}
 1val sb = StringBuilder()
 2with(sb) {
 3    appendln("alphabet: ")
 4    for(c in 'a'..'z'){
 5        append(c)
 6    }
 7    toString()
 8}
 9// or
10with(window) {
11    width = 300
12    height = 200
13    isVisible = true
14}
1val windowOrNull = windowById["main"]
2windowOrNull?.run {
3    width = 300
4    height = 200
5    isVisible = true
6}
1val mainWindow = windowById["main"]?.apply {
2    width = 300
3    height = 200
4    isVisible = true
5} ?: return
1windowById["main"]?.apply {
2    width = 300
3    height = 200
4    isVisible = true
5}?.also { window -> showWindow(window) }

he difference between all these functions

Default values and positional paramters

we can specify default values for parameters, such as:

 1fun display(character: Char = "*", size: Int = 10) {
 2    repeat(size) {
 3        print(character)
 4     }
 5}
 6
 7display('#', 5)
 8display('#')
 9display()
10// or direct the parameter
11display(size=5)

using the name position we can even change the order.

to call a kotlin function with default values, we need to suply an annotation to the function, like:

1@JvmOverloads
2fun sun(a: Int=0, b: Int=0, c:Int=0)=a+b+c
3
4// java
5sum(1);

Conditionals: If & when

if in kotlin is an expression, there is no ternary operations!

when as a switch

You no longer needs to use break;

 1// java
 2switch(color) {
 3   case BLUE:
 4     System.out.printLn("cold");
 5    break;
 6   default:
 7     System.out.printLn("hot");
 8}
 9
10//kotlin
11when(color) {
12    BLUE -> println("cold")
13    else -> println("hot")
14}

Multiples conditions

when can or not have parameters, so if it doesn't have, i''l check its cases for a boolean expression and evaluate them, so when can be a group of ifs or a switch statement.

to check several conditions, you can specify like:

1fun respondToInput(input:String) = when(input) {
2    "y", "yes" -> "I'm glad you agree"
3    "n", "no" -> "Sorry to hear that"
4    else -> "I don't understand you"
5}

or even comparing sets, the argument is checked for equality with the banch conditions

1when(setOf(c1, c2)){
2    setOf(RED,YELLOW) -> ORANGE
3}

Checking a type

we can use is to check a class type, like if pet is a super class of Cat and Dog we can check like:

1when(pet) {
2    is Cat -> pet.meow()
3    is Dog -> pet.woof()
4}

doing the same as the java instanceof

Loops


The are basicly the same, but:

For loop looks a bit different. First, a different keyword is used to express iteration over something in. Second, where meets the element type.

1var list = listOf("a"."b","c")
2for(s in list) {
3    print(s)
4}

Iterate over a map

1val map = mapOf(1 to "one", 2 to "two", 3 to "three")
2for((key, value) in map) {
3    println("$key = $value")
4}

Iterating over an index

1val list = listOf("a", "b", "c")
2for((index, element) in list.withIndex()) {
3    println("$index: $element")
4}

Iterating over Ranges

instead a forin loop we can use a range

 1for(i in 1..9){
 2    print(i)
 3}
 4// -> 123456789
 5
 6// or until, excluding 9
 7for(i in 1 until 9){
 8    print(i)
 9}
10// -> 12345678

or more complex cases like with a step

1for(i in 0 downTo 1 step 2){
2    print(i)
3}
4// -> 97531

Using in over Ranges


there are 2 ways to use it

EG, to check if a string is a letter:

1fun isLetter(c:Char) = c in 'a'..'z' || c in 'A'..'Z'
2isLetter('q')// true
3isLetter(*) // false

and it can be used in a when case as well.

> They can even be stored in a variable such as: val intRange:IntRange = 1..9

Using in into collections

in can be used to check if an element is part of a list, in Java that would be like:

1if(list.contains(element)) {}

but in Kotlin we can do as

1if(element in list){...}

Comparables


We can compare anithing that has a compareTo, bein a code like the following

1class MyDate:Comparable<MyDate>
2
3if(myDate.compareTo(startDate) >= 0 && myDate.compareTo(endDate) <= 0) {...}

can be rewrite in kotlin as

1class MyDate:Comparable<MyDate>
2if(myDate >= startDate && myDate <= endDate) {...}
3
4// or even
5
6if(myDate in startDate..endDate){...}

Exceptions


They are very similar to Java with one important difference. Kotlin doesn't differentiate checked and unchecked exceptions. In Kotlin, you may or may not handle any exception, and your function does not need to specify which exception it can throw.

1val percentage = 
2    if (number in 0..100)
3        number
4    else
5        throw IllegalArgumentException("wron value: $value")

being then try & catch an expression, being able to be assigned to varialbes, like followingval number = try {Integer.parseInt(string)} catch(e:NumberFormatException) {null}

Extension Functions


Extension function extends the class. It is defined outside of the class but can be called as a regular member to this class.

The general usecase for them is when you have a library and wants to extends its api, or something YOU DON'T HAVE POWER OVER.

1fun String.lastChar() = this.get(this.length -1) //this can be ommited
2
3fun String.lastChar() = get(length -1)
4
5// so we can use as
6"me".lastChar() // -> e

> They are not freely accessible, you need to import them.

Infix

there is a second way to extend a funtion with infix

1infix fun <T> T.eq(other: T) {
2    if (this == other) println("OK")
3    else println("Error: $this != $other")
4}
5
6evaluateGuess("BCDF", "ACEB") eq result

Nullable


The introduction of null to earlier programming languages, is known to be called "billion dollar mistake". Because nulls are ubiquitous, NullPointerExceptions, attempts to dereference null pointers, are very common as an issue

to define:

1val s: String? // nullable
2val s: String // non nullable

Elvis for null values: If we want to assing the lenght of a nullable variable to a non nullable, we can use an elvis to return a default one.

1val lenght: Int = s?.length ?: 0 

to throw an esception from accessing an value of a null object, just use: person.company!!.address!!.country but prefer explicit checks

Safe cast

a safe way to cast an expression to a type. In Java, you use a common pattern to do something with the variable only if it is of specific type. First, you use instanceof to check whether the variable is of the required type, then you cast it to this type of restoring the result in a new variable

Instead of using instanceOf or typeCast we can simply use is and as, but in the example, the as is not necessary

1if(any is String) {
2    val s = any as String
3    s.toUpperCase()
4}

Lambdas


Lambda is an anonymous function that can be used as an expression

1button.addActionListner{ println("H1") }

> we can store a lambda into a function val sum = { x:Int, y:Int -> x+y } But we can't store a function in a variable

Sequences

filters and maps returns new values of a list, to avoid it we can use sequences, that are the same as stream

> Stream or sequences: If we use operations on collections, they eagerly return the result, while the operations on sequences postponed the actual computation, and therefore avoid creating intermediate collections.

1val list = listOf(1,2,-3)
2val maxOddSquare = list
3.asSequence() // same as .stream()
4.map{ it * it}
5.filter {it % 2 == 1}
6.max()

sequences output

Generating a sequence

If you need to build a sequence from scratch, for instance, you define a way to receive each new element from the network, you can use the generateSequence function. Here, it generates a sequence of random numbers

1val seq = generateSequence {
2    Random.nextInt(5).takeIf { it > 0}
3}
4println(seq.toList())// [4,4,3,2,3,2]

> The generateSequence function can be useful when you need to read input and stop when a specific string is typed.

 1val input = generateSequence {
 2    readLine().takeIf { it != "exit" }
 3}
 4println(input.toList()) 
 5/*
 6>> a
 7>> b
 8>> exit
 9[a,b]
10*/

Generating an infinite sequence

In this case, we generate an infinite sequence of integer numbers. Note that because this sequence is computed lazily, it might be infinite. Nothing happens until you explicitly ask for it.

1val numbers = generateSequence(0) {it + 1}
2number.take(5).toList() // [1,2,3,4,5]

Yield

Yield allows you to yield elements in a custom way. The generic sequence functions that we saw before, are white constraint

1val numbers = sequence {
2    var x = 0
3    while(true) {
4        yield(x++)
5    }
6}
7numbers.take(5).toList() //[0,1,2,3,4,5]

lambda with receiver (DSL)

It might be considered as a union of two ideas of two other features: extension functions and lambdas.

1// lambda function
2val isEven: (Int) -> Boolean = { it%2 == 0 }
3// lambda with receiver
4val isOdd: Int.() -> Boolean = { this % 2 == 1 }
5
6// calling
7isEven(0)
81.isOdd()

lambda-with-receiver

Return from lambda

Why in Kotlin return returns from the outer function? Consider the following example when you have a regular for loop and inside this for loop you use return. Return simply returns from the function. If you convert the for loop to foreach, then you can't expect that return continue to behave in the same way.

1fun containsZero(list: List<Int>): Boolean {
2    list.forEach {
3        if (it == 0) return true //this return the FUNCTION not the lambda!
4    }
5    return false
6}

to return from the lambda we have to use the labels return syntax:

1list.flatMap {
2    if(it === 0) return@flatMap listOf<Int>()
3     listOf(it, it)
4}

By default, you can use the name of the function that calls this lambda as a label. but we customize it

1list.flatMap l@ {
2    if(it === 0) return@l listOf<Int>()
3     listOf(it, it)
4}

if this is a problem, we can use a function inside of a function like:

1fun duplicateNonZeriLocalFunctcion(list: List<Int>):List<Int> {
2    fun duplicateNonZeroElement(e: Int) :List<Int> {
3        if(e==0) return listOf()
4        return listOf(e,e)
5    }
6    return list.flatMap(::duplicateNonZeroElement)
7}
8println(duplicateNonZeriLocalFunctcion(listOf(3,0,5)))
9// [3,3,5,5]

Or using an anonymous funcion

1fun duplicateNonZeriLocalFunctcion(list: List<Int>):List<Int> {
2    return list.flatMap(fun (e) :List<Int> {
3        if(e==0) return listOf()
4        return listOf(e,e)
5    })
6}
7println(duplicateNonZeriLocalFunctcion(listOf(3,0,5)))
8// [3,3,5,5]

> The return from the lambda doesnt stop the loop, its like a continue

Destructing declarations

The same syntax was used to iterate over a map in a for loop, by assigning a key and a value to separate variables

EG:

1map.mapValues{ entry-> "${entry.key} = ${entry.value}!" }

can be declared as:

1map.mapValues{ (key, value) -> "${key} = ${value}!" }
2// or
3map.mapValues{ (_, value) -> "${value}!" }

Iterating over a list with index

Note that iterating over list with this index also works using destructuring declarations. With index, extension function returns a list of index to value elements.

1for((index, element) in list.withIndex()) {
2    println("$index $element")
3}

Destructing declarations & data classes

You can use any data class as the right-hand side for the destructuring declarations, since the necessary component functions are automatically generated for it

1data class Contact (
2    val name: String,
3    val email: String,
4    val phoneNumber: String
5)
6
7val (name, _, phoneNumber) = contact

Some different lambdas

1val heroes = listOf(Hero("The Captain", 60, MALE),Hero("Lady Lauren", 29, FEMALE))
2val (youngest, oldest) = heroes.partition { it.age < 30 }
3oldest.size
1val heroes = listOf(Hero("The Captain", 60, MALE),Hero("Frenchy", 42, MALE),Hero("Sir Stephen", 37, MALE))
2val mapByName: Map<String, Hero> = heroes.associateBy { it.name }
3mapByName["Frenchy"]?.age
1val heroes = listOf(Hero("The Captain", 60, MALE),Hero("Sir Stephen", 37, MALE))
2val mapByName = heroes.associate { it.name to it.age }
3mapByName.getOrElse("unknown") { 0 } //0
1val a = listOf(1,2,3);
2val b = listOf('a', 'b', 'c')
3a.zip(b)
4
5// 1->a, 2->b and 3->c
1val heroes = listOf(Hero("The Captain", 60, MALE), Hero("Frenchy", 42, MALE)) 
2heroes.maxBy { it.age }?.name

> Another interesting detail about maxBy, it can return more than one list

1val heroes = listOf(Hero("The Kid", 9, null),Hero("Lady Lauren", 29, FEMALE),Hero("First Mate", 29, MALE),Hero("Sir Stephen", 37, MALE))
2val mapByAge: Map<Int, List<Hero>> = heroes.groupBy { it.age }
3val (age, group) = mapByAge.maxBy { (_, group) ->  group.size }!!
4println(age) // 29

group-and-associate-by

Function types


parameter types are written inside the parentheses and then an arrow, then the return type. In this case, is the type that takes two integer parameters and returns an integer as a result.

as a lambda: val sum = { x:Int, y:Int -> x+y } we can give it a type by:

1val sum: (Int, Int) -> Int = {x,y -> x+y}

Calling a stored function

You can call a variable a function type as a regular function, providing all the unnecessary arguments.

1val isEven: (Int) -> Boolean = {x:Int -> i%2 == 0}
2val result:Boolean = isEven(42) // true 
3
4or as an lambda: 
5listOf(1,2,3).any(isEven) // true
6list.filter(isEven) // [2,4]

We can run labdas by: {println("hey")}() or better: run {println("hey")}

SAM interfaces (Single abstract method invocations)

In Java, you can pass a Lambda instead of a SAM interface, an interface with only one single abstract method. In Kotlin, you can use the function types directly, but when you mix Kotlin and Java, you'd want to do the same as in Java. Whenever you call a method that takes SAM interface as a parameter, you can pass a Lambda as an argument to this method instead.

1void postponeComputation(int delay, Runnable computation)
2
3// SAM interface
4public interface Runnable {
5    public abstract void run();
6}

Whenever you call a method that takes SAM interface as a parameter, you can pass a Lambda as an argument to this method instead.postponeComputation(1000) {println(42)}

or if you want to use the constructor:

1val runnable = Runnable {printl(42)}
2postponeComputation(1000, runnable) 

Nullable function type

The first one means that, return type is nullable. That's a possible mistake when you simply add a question mark after the function type. It means that you make the return type of this function type nullable, not the whole type itself. If you want to make the whole type nullable, you need to use the parentheses. In the example, you saw null in curly braces. It's a Lambda without arguments that always returns null.

1() ->Int? // return type is null
2(() -> Int)? // the variable is null

calling a null function

1val f: (()-> Int)? = null
2
3if(f != null) {
4    f()
5}   
6// or safe call
7f?.invoke()

Member references


Like Java, Kotlin has member references, which can replace simple Lambdas that only call a member function or return a member property.

1class Person(val name: String, val age: Int)
2
3people.maxBy {it.age}
4// or
5people.maxBy(Person::age)
6// Person is the class
7// age is the member

if the reproached function takes several arguments, you have to repeat all the parameter names as Lambda parameters, and then explicitly pass them through, that makes this syntax robust. Member references allow you to hide all the parameters, because the compiler infers the types for you.

1val action = {person: Person, message: String -> sendEmail(person, message)}
2// same as
3val action = ::sendMail 

Function references

If you try to assign a function to a variable, you'll get a compiler error. To fix this issue, use the function reference syntax. Function references allow you to store a reference to any defined function in a variable to be able to store it and qualitative it. Keep in mind that this syntax is just another way to call a function inside the Lambda, underlying implementation are the same.

1fun isEven(i:Int):Boolean = i%2==0
2val predicate = ::isEven
3// the same as
4val predicate = { i:Int -> isEven(i)}

Passing function references as an argument

whenever your Lambda tends to grow too large and to become too complicated, it makes sense to extract Lambda code into a separate function, then you use a reference to this function instead of a huge Lambda

1fun isEven(i:Int): Boolean = i%2==0
2val list = listOf(1,2,3,4)
3list.any(::isEven) // true
4list.filter(::isEven) [2,4]

Static and non static access:

 1class Person(val name: String, val age: Int) {
 2    fun isOlder(ageLimit:Int) = age > ageLimit
 3}
 4
 5val agePredicate = Person::isOlder // non-bound or static
 6val alice = Person("alice", 29)
 7agePredicate(alice, 29) // true
 8
 9// bounded or non-static
10val alice = Person("Alice", 29)
11val agePredicate = alice::isOlder
12agePredicate(21) // true
13
14// and bound to this references
15class Person(val name: String, val age: Int) {
16    fun isOlder(ageLimit:Int) = age > ageLimit
17
18    fun getAgePredicate() = this::isOlder //we can ommit the this (::isOlder)
19}

Properties


Unlike Java where a property is not a language construct. Kotlin supports it as a separate language feature. In a most common scenario for a trivial case, property syntaxes are really concise. But you can customize it if needed

This is an customization of these field

 1class Retangle(val height: Int, val width: Int) {
 2
 3    val isSquare: Boolean
 4        get() {
 5            return height == width
 6        }
 7}
 8
 9val rectangle = Rectangle(2,3)
10println(rectangle.isSquare) // false

Setting the field

In Kotlin, you don't work with fields directly, you work with properties. However, if you need, you can access a field inside its property accessors. It's not visible for other methods of the class.

1var state = false
2    set(value) {
3        print("setting state to $field")
4        field = value
5    }

Setter visibility

You can make a setter private. Then the getter is accessible everywhere. And therefore the property is accessible everywhere. But it's allowed to modify it only inside the same class. Note that we change only the visibility of the setter, but use the default implementation.

1var sample:Boolean = false
2private set

Property in interfaces

You can define a property in an interface. Why not? Under the hood, it's just a getter. Then you can redefine this getter in subclasses in the way you want.

1interface User {
2    val nickname: String
3}
4
5class FacebookUser(val accountId: Int): User {
6    override val nickname = getFacebookName(accountId)
7}

Extension properties

In Kotlin, you can define extension properties. The syntax is very similar to the one of defining extension functions.

1val String.lasIndex:Int 
2    get() = this.length
3
4val String.indices: IntRange
5    get() = 0..lasIndex
6
7// "abc".lasIndex

Lazy property

Lazy Property is a property which values are computed only on the first success.

1val lazyValue: String by lazy { 
2    println("computed")
3    "Hello"
4}

Lateinit: Late initialization

Sometimes, we want to initialize the property not in the constructor, but in a specially designated for that purpose method. Here, we initialized the myData property in onCreate method, but not in the constructor

1class KotlinActivity: Activity {
2    lateinit var MyData: MyData
3    
4    fun create() {
5        myData = intent.getParcelableExtra("MY_DATA")
6    }
7}

OOP in Kotlin


🤨 Overall, Kotlin doesn't introduce anything conceptually new here, the experience is very similar.

key value
final used by default, cannot be overridden
open can be overridden
abstract must be overridden, can't have an implementation
override mandatory, overrides a member in a superclass or interface

When you put val or var before the parameter, that automatically creates a property. Without val or var, it's on the constructor parameter.

1class Person(name: String) {
2    val name: String
3
4    init {
5        this.name = name
6    }
7}
8// or
9class Person(val name:String)

😀 You can hame multiple constructors, but each secondary constructor must call another secondary or primary constructor

1class Rectangle(val height: Int, val width: Int) {
2
3    constructor(side: Int) : this(side, side) {   
4    }
5}

Enums class

The difference with Java is that now enum is not a separate instance, but a modifier before the class keyword.

 1import Color.*
 2
 3enum class Color {
 4    BLUE, ORANGE, RED
 5}
 6
 7fun getDescription(color: Color) {
 8    when(color) {
 9        RED->"cold"
10        ORANGE -> "mild"
11        RED -> "hot"
12    }
13}

inner modifier

To access this reference of the outer class, you use labeled this. As the label name, you specify the name of the outer class.

1class A {
2    class B
3    inner class C {
4        ...this@A...
5    }
6}

Class delegation

In essence, class delegation allows you to delegate the task of generating trivial matters to the compiler. Note that the controller class implements both interfaces, so we can call interface members on it

1class Controller(
2    repository: Repository,
3    logger: Logger 
4): Repository by repository, Logger by logger

Objects

The keyword object is a singleton in Kotlin: object K {fun foo() {}}; K.foo()

Objects Expressions replace java's anonymous classes:

1window.addMouseListner (
2    object: MouseAdapter() {
3        override fun mouseClicked(e: MouseEvent) {}
4        override fun mouseEntered(e: MouseEvent) {}
5    }
6)

Companion object

It's a nested object inside a class but a special one. The one which members can be accessed by the class name. In Kotlin, there are no static methods like in Java, and companion objects might be a replacement for that.

1class A {
2    companion object {
3        fun foo() = 1
4    }
5}
6fun main(args: Array<String>){
7    A.foo()
8}

Companion object can implment an interface

It would be nice if a static method could override a member of an interface. But for static, that's not possible in java. Now, it's possible for companion object.

 1interface Factory<T> {
 2    fun create():T
 3}
 4class A{
 5    private constructor()
 6    companion object : Factory<A> {
 7        override fun create(): A {
 8            return A()
 9        } 
10    }
11}

companion object can be a receiver of an extension function

Another thing which you can do with companion object, is you can define extension straight. To distinguish an extension to a class from an extension to a companion object, you use the companion suffix.

1class Person(val firstName: String, val lastName: String) {
2    companion object{...}
3}
4
5fun Person.Companion.fromJson(json: String): Person {
6...
7}
8
9val p = Person.fromJSON(json)

Generics

You define a type parameter for a function and try to use this type parameter in the function declaration, into the function body. You can call a generic function on different types.

1fun <T> List<T>.filter(predicate: (T)-> Boolean): List<T>

Non-nullable upper bound

If you want to restrict the generic argument so that it was not nullable, you can specify a non-null upper bound. You add an upper bound right after the type parameter declaration using the same column which replaces the extents cube root in Kotlin

1fun <T: Any> foo(list: List<T>) {
2    for(element in list) {}
3}
4
5foo(listOf(1, null)) // exception

Multiple constrains for a type parameter

Here, you can pass any type that extends to different interfaces, terrace sequence and aboundable. Stringbuilder implements both terrace sequence and dependable, so it's a valid argument for these function.

 1fun <T> ensureTrailingPeriod(seq: T)
 2     where T: CharSequence, T: Appendable {
 3        if(!seq.endsWith('.')) {
 4            seq.append('.')
 5        }
 6}
 7
 8val helloWorld = StringBuilder("Hello, World")
 9ensureTrailingPeriod(helloWorld)
10println(helloWorld) // "Hello, World.

Conventions


accessing map elements using square brackets, actually work via conventions, and the same syntax might be supported for your custom process.

Arithmetic operations

In Kotlin, you can use this syntax of arithmetic operations not only primitives or strings, but for custom types as well. You define a function, a member or an extension with a specific name and mark it as operator.

1// a + b -> a.plus(b)
2
3operator fun Poin.plus(other: Point): Point {
4    return Point(x + other.x, y + other.y)
5}

It's not that you can use any name. You can see the correspondence between the syntax and the operator name that allows you to use this syntax.

expression function name
a + b plus
a - b minus
a * b times
a / b div
a % b mod

And the unary operators:

expression function name
+a unaryPlus
-a unaryMinus
!a not
++a, a++ inc
--a, a-- dec

> Unary operator is a function without arguments which we can call as an operator on this specified receiver.

Prefer val to var

assigning the value like the following will create a new list, like: list = list + 4

1var list = listOf(1,2,3)
2list += 4

Comparisons

Under the hood, the comparison operators are all compiled using the comparative method comparisons.

symbol translate to
a > b a.compareTo(b) > 0
a < b a.compareTo(b) < 0
a >= b a.compareTo(b) >= 0
a <= b a.compareTo(b) <= 0

Accessing elements by index: []

Under the hood, the get and set methods are called

1map[key]
2mutableMap[key] = newValue
3
4// is translate to
5x[a,b] -> x.get(a, b)
6x[a,b] = c -> x.set(a,b,c)

You can define get and set operator functions as members or extensions for your own custom classes.

1class Board {}
2board[1,2] = 'x'
3board[1,2] // x
4
5operator fun Board.get(x: Int, y: Int): Char {...}
6operator fun Board.set(x: Int, y: Int) {...}

The iterator convention

For loop iteration also goes through a convention. In Kotlin, you can iterate over a string.

1operator fun CharSequence.iterator() : CharIterator
2for(c in "abc"){}

Exams and tests

Every week we have an exame, these are the projects solving them:

Week 2 exame: The Secret game

 1data class Evaluation(val rightPosition: Int, val wrongPosition: Int)
 2
 3fun evaluateGuess(secret: String, guess: String): Evaluation {
 4    val totalSecret: MutableList<Pair<Int, Char>> = secret.subtract(guess)
 5    val totalGuess: MutableList<Pair<Int, Char>> = guess.subtract(secret)
 6
 7    return Evaluation(
 8            rightPosition = calculateRightGuess(secret, totalSecret),
 9            wrongPosition = calculateWrongPositions(totalSecret, totalGuess)
10    )
11}
12
13private fun calculateRightGuess(secret: String, totalSecret: MutableList<Pair<Int, Char>>) = secret.length - totalSecret.size
14private fun calculateWrongPositions(totalSecret: MutableList<Pair<Int, Char>>, totalGuess: MutableList<Pair<Int, Char>>): Int {
15    var wrong = 0
16    totalSecret.forEach {
17        val found = totalGuess.findChar(it.second)
18        if (found != null) {
19            wrong++
20            totalGuess.remove(found)
21        }
22    }
23    return wrong
24}
25
26private fun String.asIndexMap() = this.toCharArray().mapIndexed { index, c -> index to c }.toMutableList()
27private fun String.subtract(from: String) = this.asIndexMap().minus(from.asIndexMap()).toMutableList()
28private fun MutableList<Pair<Int, Char>>.findChar(needle: Char) = this.find { it.second == needle }

Kotlin Playground: Interchangeable predicates

Functions 'all', 'none' and 'any' are interchangeable, you can use one or the other to implement the same functionality. Implement the functions 'allNonZero' and 'containsZero' using all three predicates in turn. 'allNonZero' checks that all the elements in the list are non-zero; 'containsZero' checks that the list contains zero element. Add the negation before the whole call (right before 'any', 'all' or 'none') when necessary, not only inside the predicate.

 1fun List<Int>.allNonZero() = all { it != 0 }
 2fun List<Int>.allNonZero1() = none { it == 0 }
 3fun List<Int>.allNonZero2() = !any { it == 0 }
 4
 5fun List<Int>.containsZero() = any { it == 0 }
 6fun List<Int>.containsZero1() = !all { it != 0 }
 7fun List<Int>.containsZero2() = !none { it == 0 }
 8
 9fun main(args: Array<String>) {
10    val list1 = listOf(1, 2, 3)
11    list1.allNonZero() eq true
12    list1.allNonZero1() eq true
13    list1.allNonZero2() eq true
14
15    list1.containsZero() eq false
16    list1.containsZero1() eq false
17    list1.containsZero2() eq false
18
19    val list2 = listOf(0, 1, 2)
20    list2.allNonZero() eq false
21    list2.allNonZero1() eq false
22    list2.allNonZero2() eq false
23
24    list2.containsZero() eq true
25    list2.containsZero1() eq true
26    list2.containsZero2() eq true
27}
28
29infix fun <T> T.eq(other: T) {
30    if (this == other) println("OK")
31    else println("Error: $this != $other")
32}

Assignment: Nice Strings

Nice String

A string is nice if at least two of the following conditions are satisfied:

Your task is to check whether a given string is nice. Strings for this task will consist of lowercase letters only. Note that for the purpose of this task, you don't need to consider 'y' as a vowel.

Note that any two conditions might be satisfied to make a string nice. For instance, "aei" satisfies only the conditions #1 and #2, and ```"nn"` satisfies the conditions #1 and #3, which means both strings are nice.

Example 1

Example 2

Example 3

Example 4

Example 5

 1val VOWELS = listOf('a', 'e', 'i', 'o', 'u')
 2val forbiddenWords = listOf("ba", "be", "bu")
 3
 4fun String.isNice(): Boolean {
 5    var total = 0
 6
 7    val hasNotForbiddenWords = { input: String -> if (forbiddenWords.none{ input.contains(it)}) 1 else 0 }
 8    val hasMoreThenThreeVOWELS = { input: String -> if (input.count(VOWELS::contains) >= 3) 1 else 0 }
 9    val hasDoubleLetters = { input: String -> if (input.zipWithNext().count { it.first == it.second } > 0) 1 else 0 }
10
11    total += hasNotForbiddenWords(this)
12    total += hasMoreThenThreeVOWELS(this)
13    total += hasDoubleLetters(this)
14
15    return total > 1
16}

Assignment: Taxi park

 1/*
 2 * Task #1. Find all the drivers who performed no trips.
 3 */
 4fun TaxiPark.findFakeDrivers(): Set<Driver> = allDrivers.minus(trips.map { it.driver }.toSet())
 5
 6/*
 7 * Task #2. Find all the clients who completed at least the given number of trips.
 8 */
 9fun TaxiPark.findFaithfulPassengers(minTrips: Int): Set<Passenger> {
10    val byPassenger = trips.map { it.passengers }
11
12    return allPassengers.filter {
13        byPassenger.count { trip -> trip.contains(it) } >= minTrips
14    }.toSet()
15}
16
17/*
18 * Task #3. Find all the passengers, who were taken by a given driver more than once.
19 */
20fun TaxiPark.findFrequentPassengers(driver: Driver): Set<Passenger> = trips
21        .filter { it.driver == driver }
22        .flatMap (Trip::passengers)
23        .groupBy { passenger -> passenger }
24        .filterValues {group -> group.size > 1 }
25        .keys
26
27/*
28 * Task #4. Find the passengers who had a discount for majority of their trips.
29 */
30private fun Passenger.hadMoreTripsWithDiscount(trips: List<Trip>): Boolean {
31    val totalRides = trips.filter { it.passengers.contains(this) }.map { it.discount }
32    val ridesWithoutDiscount = totalRides.filter(Objects::isNull).size
33    val ridesWithDiscount = totalRides.filter(Objects::nonNull).size
34
35    return ridesWithDiscount > ridesWithoutDiscount
36}
37
38fun TaxiPark.findSmartPassengers(): Set<Passenger> = allPassengers.filter { it.hadMoreTripsWithDiscount(trips) }.toSet()
39
40/*
41 * Task #5. Find the most frequent trip duration among minute periods 0..9, 10..19, 20..29, and so on.
42 * Return any period if many are the most frequent, return `null` if there're no trips.
43 */
44fun TaxiPark.findTheMostFrequentTripDurationPeriod(): IntRange? {
45    return trips
46        .groupBy{
47            val start = it.duration /10 * 10
48            val end = start + 9
49            start..end
50        }
51        .maxBy{(_,group) -> group.size}
52        ?.key
53}
54
55/*
56 * Task #6.
57 * Check whether 20% of the drivers contribute 80% of the income.
58 */
59fun TaxiPark.checkParetoPrinciple(): Boolean {
60    if(this.trips.isEmpty()) {
61        return false
62    }
63    val incomeByDriver = trips
64            .groupBy { it.driver }
65            .map { (_, tripsByDriver) -> tripsByDriver.sumDouble(Trip::cost)}
66            .sortedByDescending { it }
67
68    val topTwentyDrivers = (allDrivers.size * 0.2).roundToInt()
69    var income = incomeByDriver.take(topTwentyDrivers).sum()
70
71
72    val total = trips.sumByDouble(Trip::cost)
73    val percentage = income * 100 / total
74    return percentage >= 80
75}

Unstable val

Implement the property 'foo' so that it produced a different value on each access. Note that you can modify the code outside the 'foo' getter (e.g. add additional imports or properties).

 1var foo: Int = 0
 2    get():Int  {
 3       field += 1
 4       return field
 5    }
 6
 7fun main(args: Array<String>) {
 8    // The values should be different:
 9    println(foo)
10    println(foo)
11    println(foo)
12}

Rational Numbers

Your task is to implement a class Rational representing rational numbers. A rational number is a number expressed as a ratio n/d , where n (numerator) and d (denominator) are integer numbers, except that d cannot be zero.

  1package rationals
  2
  3import java.math.BigInteger.ONE
  4import java.math.BigInteger.ZERO
  5import java.math.BigInteger
  6
  7class Rational(val numerator: BigInteger, val denominator: BigInteger) {
  8
  9    companion object {
 10        private fun minCommonDivisor(denominatorA: BigInteger, denominatorB: BigInteger): BigInteger {
 11            val maxCommonDivisor = denominatorA.gcd(denominatorB)
 12            return denominatorA * (denominatorB / maxCommonDivisor)
 13        }
 14
 15        fun toSameBase(first: Rational, second: Rational): Pair<Rational, Rational> {
 16            val mmc = minCommonDivisor(first.denominator, second.denominator)
 17
 18            val firstEquivalentRational = Rational(first.toEquivalentNumerator(mmc), mmc)
 19            val secondEquivalentRational = Rational(second.toEquivalentNumerator(mmc), mmc)
 20
 21            return firstEquivalentRational to secondEquivalentRational
 22        }
 23    }
 24
 25    fun toEquivalentNumerator(mmc: BigInteger) = this.numerator * (mmc / this.denominator)
 26
 27    /*
 28    The denominator must be always positive in the normalized form.
 29    If the negative rational is normalized, then only the numerator can be negative,
 30    e.g. the normalized form of 1/-2 should be -1/2.wq
 31    */
 32    fun normalize(): Rational {
 33        if (numerator == ZERO) return this
 34
 35        val maxDivider = numerator.gcd(denominator)
 36        val normalized = Rational(
 37                numerator = numerator / maxDivider,
 38                denominator = (denominator / maxDivider).abs()
 39        )
 40
 41        return when {
 42            denominator < ZERO -> -normalized
 43            else -> normalized
 44        }
 45    }
 46
 47    override fun toString(): String {
 48        return when (denominator) {
 49            ONE -> numerator.toString()
 50            else -> "$numerator/$denominator"
 51        }
 52    }
 53
 54    override fun equals(other: Any?) = when (other) {
 55        is Rational -> this.numerator == other.numerator && this.denominator == other.denominator
 56        else -> super.equals(other)
 57    }
 58
 59    override fun hashCode(): Int {
 60        var result = numerator.hashCode()
 61        result = 31 * result + denominator.hashCode()
 62        return result
 63    }
 64
 65    operator fun compareTo(rational: Rational): Int {
 66        val (sameBaseThis, sameBaseTarget) = toSameBase(this, rational)
 67
 68        return when {
 69            sameBaseThis.numerator > sameBaseTarget.numerator -> 1
 70            sameBaseThis.numerator < sameBaseTarget.numerator -> -1
 71            else -> 0
 72        }
 73    }
 74
 75    operator fun unaryMinus(): Rational {
 76        return Rational(-numerator, denominator)
 77    }
 78
 79    operator fun rangeTo(rational: Rational): Pair<Rational, Rational> {
 80        return Pair(this, rational)
 81    }
 82
 83    operator fun plus(target: Rational): Rational {
 84        val (sameBaseThis, sameBaseTarget) = toSameBase(this, target)
 85        return Rational(
 86                numerator = sameBaseThis.numerator + sameBaseTarget.numerator,
 87                denominator = sameBaseThis.denominator
 88        ).normalize()
 89    }
 90
 91    operator fun minus(target: Rational): Rational {
 92        val (sameBaseThis, sameBaseTarget) = toSameBase(this, target)
 93        return Rational(
 94                numerator = sameBaseThis.numerator - sameBaseTarget.numerator,
 95                denominator = sameBaseThis.denominator
 96        ).normalize()
 97    }
 98
 99    operator fun times(target: Rational) = Rational(
100            numerator = this.numerator * target.numerator,
101            denominator = this.denominator * target.denominator
102    ).normalize()
103
104    operator fun div(target: Rational) = Rational(
105            numerator = this.numerator * target.denominator,
106            denominator = this.denominator * target.numerator).normalize()
107}
108
109operator fun Pair<Rational, Rational>.contains(rational: Rational): Boolean {
110    return rational >= this.first && rational < this.second
111}
112
113fun String.toRational(): Rational {
114    return if (this.contains("/")) {
115        val (num, den) = this.split("/")
116        Rational(num.toBigInteger(), den.toBigInteger()).normalize()
117    } else {
118        Rational(this.toBigInteger(), ONE)
119    }
120}
121
122infix fun Number.divBy(first: Number) = Rational(this.toString().toBigInteger(), first.toString().toBigInteger()).normalize()
123
124fun main() {
125    val half = 1 divBy 2
126    val third = 1 divBy 3
127
128    val sum: Rational = half + third
129    println(5 divBy 6 == sum)
130
131    val difference: Rational = half - third
132    println(1 divBy 6 == difference)
133
134    val product: Rational = half * third
135    println(1 divBy 6 == product)
136
137    val quotient: Rational = half / third
138    println(3 divBy 2 == quotient)
139
140    val negation: Rational = -half
141    println(-1 divBy 2 == negation)
142
143    println((2 divBy 1).toString() == "2")
144    println((-2 divBy 4).toString() == "-1/2")
145    println("117/1098".toRational().toString() == "13/122")
146
147    val twoThirds = 2 divBy 3
148    println(half < twoThirds)
149
150    println(half in third..twoThirds)
151
152    println(2000000000L divBy 4000000000L == 1 divBy 2)
153
154    println("912016490186296920119201192141970416029".toBigInteger() divBy
155            "1824032980372593840238402384283940832058".toBigInteger() == 1 divBy 2)
156}

Board

Your task is to implement interfaces SquareBoard and GameBoard.

SquareBoard stores the information about the square board and all the cells on it. It allows the retrieval of a cell by its indexes, parts of columns and rows on a board, or a specified neighbor of a cell.

GameBoard allows you to store values in board cells, update them, and enquire about stored values (like any, all etc.) Note that GameBoard extends SquareBoard.

  1data class Cell(val i: Int, val j: Int) {
  2    override fun toString() = "($i, $j)"
  3}
  4
  5enum class Direction {
  6    UP, DOWN, RIGHT, LEFT;
  7
  8    fun reversed() = when (this) {
  9        UP -> DOWN
 10        DOWN -> UP
 11        RIGHT -> LEFT
 12        LEFT -> RIGHT
 13    }
 14}
 15
 16interface SquareBoard {
 17    val width: Int
 18
 19    fun getCellOrNull(i: Int, j: Int): Cell?
 20    fun getCell(i: Int, j: Int): Cell
 21
 22    fun getAllCells(): Collection<Cell>
 23
 24    fun getRow(i: Int, jRange: IntProgression): List<Cell>
 25    fun getColumn(iRange: IntProgression, j: Int): List<Cell>
 26
 27    fun Cell.getNeighbour(direction: Direction): Cell?
 28}
 29
 30interface GameBoard<T> : SquareBoard {
 31
 32    operator fun get(cell: Cell): T?
 33    operator fun set(cell: Cell, value: T?)
 34
 35    fun filter(predicate: (T?) -> Boolean): Collection<Cell>
 36    fun find(predicate: (T?) -> Boolean): Cell?
 37    fun any(predicate: (T?) -> Boolean): Boolean
 38    fun all(predicate: (T?) -> Boolean): Boolean
 39}
 40
 41open class SquareBoardImp(final override val width: Int) : SquareBoard {
 42
 43    private val cells: List<Cell> = sequence {
 44        var i = 0
 45        repeat(width) {
 46            var j = 0
 47            i++
 48            repeat(width) {
 49                j++
 50                yield(Cell(i, j))
 51            }
 52        }
 53    }.toList()
 54
 55    private fun List<Cell>.find(i: Int, j: Int) = this.find { it.i == i && it.j == j }
 56
 57    override fun getAllCells(): Collection<Cell> {
 58        return cells
 59    }
 60
 61    override fun getCell(i: Int, j: Int): Cell = cells.find(i, j)!!
 62
 63    override fun getCellOrNull(i: Int, j: Int): Cell? = cells.find(i, j)
 64
 65    override fun getColumn(iRange: IntProgression, j: Int): List<Cell> {
 66        return sequence {
 67            for (i in iRange) {
 68                getCellOrNull(i, j)?.let { yield(it) }
 69            }
 70        }.toList()
 71    }
 72
 73    override fun getRow(i: Int, jRange: IntProgression): List<Cell> {
 74        return sequence {
 75            for (j in jRange) {
 76                getCellOrNull(i, j)?.let { yield(it) }
 77            }
 78        }.toList()
 79    }
 80
 81    override fun Cell.getNeighbour(direction: Direction): Cell? {
 82        val (i, j) = when (direction) {
 83            Direction.UP -> i - 1 to j
 84            Direction.DOWN -> i + 1 to j
 85            Direction.RIGHT -> i to j + 1
 86            Direction.LEFT -> i to j - 1
 87        }
 88        return cells.find(i, j)
 89    }
 90}
 91
 92class GameBoardImp<T>(size: Int) : SquareBoardImp(size), GameBoard<T> {
 93
 94    private var values: Set<Pair<Cell, T?>> = this.getAllCells().map { it to null }.toSet()
 95
 96    override fun get(cell: Cell): T? = values.find { it.first == cell }!!.second
 97
 98    override fun set(cell: Cell, value: T?) {
 99        values = values.map {
100            val updatedValue = if (it.first == cell) value else it.second
101
102            it.first to updatedValue
103        }.toSet()
104    }
105
106    override fun filter(predicate: (T?) -> Boolean): Collection<Cell> {
107        return values.filter { predicate(it.second) }.map { it.first }
108    }
109
110    override fun find(predicate: (T?) -> Boolean): Cell? {
111        return values.find { predicate(it.second) }?.first
112    }
113
114    override fun any(predicate: (T?) -> Boolean): Boolean {
115        return values.any { predicate(it.second) }
116    }
117
118    override fun all(predicate: (T?) -> Boolean): Boolean {
119        return values.all { predicate(it.second) }
120    }
121}

Fibonacci sequence

Implement the function that builds a sequence of Fibonacci numbers using 'sequence' function. Use 'yield'.

 1fun fibonacci(): Sequence<Int> = buildSequence {
 2    val elements = Pair(0,1)
 3    while(true) {
 4        yiled(elements.first)
 5        elements = Pair(elements.second, elements.first + elements.second)
 6    }
 7}
 8
 9fun main(args: Array<String>) {
10    fibonacci().take(4).toList().toString() eq
11            "[0, 1, 1, 2]"
12
13    fibonacci().take(10).toList().toString() eq
14            "[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]"
15}
16
17infix fun <T> T.eq(other: T) {
18    if (this == other) println("OK")
19    else println("Error: $this != $other")
20}