Kotlin Coursera class

Published on February 6, 2021

Reading time: 38 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

val regex = """\d{2}\.d{2}\.\d{4}"""
regex.matches("15.02.2016")
fun getAnswer() = 42
getAnswer() eq 42 // true
getAnswer() eq 43 // false
var StringBuilder.lastChar:Char
get() = get(length -1)
set(value:Char) {
    this.setCharAt(length-1, value)
}

val sb = StringBuilder("Kotlin")
sb.lastChar = '!'
println(sb) // Kotlin!
sealed class Expr
class Num(val value: Int): Expr()
class Sum(val left:Expr, val Right: Expr): Expr()

fun eval(e:Expr):Int = when(e) {
    is Num -> e.value
    is Sum -> eval(e.left) + eval(e.right)
}

// 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

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

Lists


Kotlin has mutable an imutable lists, such as:

val mutableList = mutableListOf("java")
mutableList.add("Kotlin")

val readOnlyList = listOf("Java")
readOnlyList.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

data class Contact(val name:String, val address: String)

contact.copy(address="mew address")

Functions


return void here is of type Unit

We can define 3 types of fuctions

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

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

functions that look like built-in language construct

val foo = run{
    println("calculating")
    "foo"
}
fun getEmail() : Email?
val email = getEmail()

if(email != null) sendEmailTo(email)
// or better
email?.let {e -> sendEmailTo(e)}

or to functions

fun analizeUserSession(session:Session) = 
    (session.user as? FacebookUser)?.let {
        println(it.accountId)
    }
issue.takeIf { it.status == FIXED }
person.patronymicName.takeIf(String::isNotEmpty)

val number = 42 
number.takeIf { it > 10 } // 42
// if the predicate don't fullfill, it returns null

issues.filter { it.status == OPEN }.takeIf(List<Issue>::isNotEmpty)?.let { println("some are open")}

// filtering
people.mapNotNull { person -> person.takeIf { it.isPublicProfile }?.name }
repeat(10) { println("Welcome!")}
val sb = StringBuilder()
with(sb) {
    appendln("alphabet: ")
    for(c in 'a'..'z'){
        append(c)
    }
    toString()
}
// or
with(window) {
    width = 300
    height = 200
    isVisible = true
}
val windowOrNull = windowById["main"]
windowOrNull?.run {
    width = 300
    height = 200
    isVisible = true
}
val mainWindow = windowById["main"]?.apply {
    width = 300
    height = 200
    isVisible = true
} ?: return
windowById["main"]?.apply {
    width = 300
    height = 200
    isVisible = true
}?.also { window -> showWindow(window) }

he difference between all these functions

Default values and positional paramters

we can specify default values for parameters, such as:

fun display(character: Char = "*", size: Int = 10) {
    repeat(size) {
        print(character)
     }
}

display('#', 5)
display('#')
display()
// or direct the parameter
display(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:

@JvmOverloads
fun sun(a: Int=0, b: Int=0, c:Int=0)=a+b+c

// java
sum(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;

// java
switch(color) {
   case BLUE:
     System.out.printLn("cold");
    break;
   default:
     System.out.printLn("hot");
}

//kotlin
when(color) {
    BLUE -> println("cold")
    else -> println("hot")
}

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:

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

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

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

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:

when(pet) {
    is Cat -> pet.meow()
    is Dog -> pet.woof()
}

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.

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

Iterate over a map

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

Iterating over an index

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

Iterating over Ranges

instead a forin loop we can use a range

for(i in 1..9){
    print(i)
}
// -> 123456789

// or until, excluding 9
for(i in 1 until 9){
    print(i)
}
// -> 12345678

or more complex cases like with a step

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

Using in over Ranges


there are 2 ways to use it

EG, to check if a string is a letter:

fun isLetter(c:Char) = c in 'a'..'z' || c in 'A'..'Z'
isLetter('q')// true
isLetter(*) // 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:

if(list.contains(element)) {}

but in Kotlin we can do as

if(element in list){...}

Comparables


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

class MyDate:Comparable<MyDate>

if(myDate.compareTo(startDate) >= 0 && myDate.compareTo(endDate) <= 0) {...}

can be rewrite in kotlin as

class MyDate:Comparable<MyDate>
if(myDate >= startDate && myDate <= endDate) {...}

// or even

if(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.

val percentage = 
    if (number in 0..100)
        number
    else
        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.

fun String.lastChar() = this.get(this.length -1) //this can be ommited

fun String.lastChar() = get(length -1)

// so we can use as
"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

infix fun <T> T.eq(other: T) {
    if (this == other) println("OK")
    else println("Error: $this != $other")
}

evaluateGuess("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:

val s: String? // nullable
val 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.

val 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

if(any is String) {
    val s = any as String
    s.toUpperCase()
}

Lambdas


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

button.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.

val list = listOf(1,2,-3)
val maxOddSquare = list
.asSequence() // same as .stream()
.map{ it * it}
.filter {it % 2 == 1}
.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

val seq = generateSequence {
    Random.nextInt(5).takeIf { it > 0}
}
println(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.

val input = generateSequence {
    readLine().takeIf { it != "exit" }
}
println(input.toList()) 
/*
>> a
>> b
>> exit
[a,b]
*/

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.

val numbers = generateSequence(0) {it + 1}
number.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

val numbers = sequence {
    var x = 0
    while(true) {
        yield(x++)
    }
}
numbers.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.

// lambda function
val isEven: (Int) -> Boolean = { it%2 == 0 }
// lambda with receiver
val isOdd: Int.() -> Boolean = { this % 2 == 1 }

// calling
isEven(0)
1.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.

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

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

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

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

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

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

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

Or using an anonymous funcion

fun duplicateNonZeriLocalFunctcion(list: List<Int>):List<Int> {
    return list.flatMap(fun (e) :List<Int> {
        if(e==0) return listOf()
        return listOf(e,e)
    })
}
println(duplicateNonZeriLocalFunctcion(listOf(3,0,5)))
// [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:

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

can be declared as:

map.mapValues{ (key, value) -> "${key} = ${value}!" }
// or
map.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.

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

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

data class Contact (
    val name: String,
    val email: String,
    val phoneNumber: String
)

val (name, _, phoneNumber) = contact

Some different lambdas

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

// 1->a, 2->b and 3->c
val heroes = listOf(Hero("The Captain", 60, MALE), Hero("Frenchy", 42, MALE)) 
heroes.maxBy { it.age }?.name

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

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

val 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.

val isEven: (Int) -> Boolean = {x:Int -> i%2 == 0}
val result:Boolean = isEven(42) // true 

or as an lambda: 
listOf(1,2,3).any(isEven) // true
list.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.

void postponeComputation(int delay, Runnable computation)

// SAM interface
public interface Runnable {
    public abstract void run();
}

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:

val runnable = Runnable {printl(42)}
postponeComputation(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.

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

calling a null function

val f: (()-> Int)? = null

if(f != null) {
    f()
}   
// or safe call
f?.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.

class Person(val name: String, val age: Int)

people.maxBy {it.age}
// or
people.maxBy(Person::age)
// Person is the class
// 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.

val action = {person: Person, message: String -> sendEmail(person, message)}
// same as
val 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.

fun isEven(i:Int):Boolean = i%2==0
val predicate = ::isEven
// the same as
val 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

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

Static and non static access:

class Person(val name: String, val age: Int) {
    fun isOlder(ageLimit:Int) = age > ageLimit
}

val agePredicate = Person::isOlder // non-bound or static
val alice = Person("alice", 29)
agePredicate(alice, 29) // true

// bounded or non-static
val alice = Person("Alice", 29)
val agePredicate = alice::isOlder
agePredicate(21) // true

// and bound to this references
class Person(val name: String, val age: Int) {
    fun isOlder(ageLimit:Int) = age > ageLimit

    fun getAgePredicate() = this::isOlder //we can ommit the this (::isOlder)
}

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

class Retangle(val height: Int, val width: Int) {

    val isSquare: Boolean
        get() {
            return height == width
        }
}

val rectangle = Rectangle(2,3)
println(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.

var state = false
    set(value) {
        print("setting state to $field")
        field = value
    }

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.

var sample:Boolean = false
private 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.

interface User {
    val nickname: String
}

class FacebookUser(val accountId: Int): User {
    override val nickname = getFacebookName(accountId)
}

Extension properties

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

val String.lasIndex:Int 
    get() = this.length

val String.indices: IntRange
    get() = 0..lasIndex

// "abc".lasIndex

Lazy property

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

val lazyValue: String by lazy { 
    println("computed")
    "Hello"
}

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

class KotlinActivity: Activity {
    lateinit var MyData: MyData
    
    fun create() {
        myData = intent.getParcelableExtra("MY_DATA")
    }
}

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.

class Person(name: String) {
    val name: String

    init {
        this.name = name
    }
}
// or
class Person(val name:String)

๐Ÿ˜€ You can hame multiple constructors, but each secondary constructor must call another secondary or primary constructor

class Rectangle(val height: Int, val width: Int) {

    constructor(side: Int) : this(side, side) {   
    }
}

Enums class

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

import Color.*

enum class Color {
    BLUE, ORANGE, RED
}

fun getDescription(color: Color) {
    when(color) {
        RED->"cold"
        ORANGE -> "mild"
        RED -> "hot"
    }
}

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.

class A {
    class B
    inner class C {
        ...this@A...
    }
}

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

class Controller(
    repository: Repository,
    logger: Logger 
): 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:

window.addMouseListner (
    object: MouseAdapter() {
        override fun mouseClicked(e: MouseEvent) {}
        override fun mouseEntered(e: MouseEvent) {}
    }
)

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.

class A {
    companion object {
        fun foo() = 1
    }
}
fun main(args: Array<String>){
    A.foo()
}

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.

interface Factory<T> {
    fun create():T
}
class A{
    private constructor()
    companion object : Factory<A> {
        override fun create(): A {
            return A()
        } 
    }
}

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.

class Person(val firstName: String, val lastName: String) {
    companion object{...}
}

fun Person.Companion.fromJson(json: String): Person {
...
}

val 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.

fun <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

fun <T: Any> foo(list: List<T>) {
    for(element in list) {}
}

foo(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.

fun <T> ensureTrailingPeriod(seq: T)
     where T: CharSequence, T: Appendable {
        if(!seq.endsWith('.')) {
            seq.append('.')
        }
}

val helloWorld = StringBuilder("Hello, World")
ensureTrailingPeriod(helloWorld)
println(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.

// a + b -> a.plus(b)

operator fun Poin.plus(other: Point): Point {
    return Point(x + other.x, y + other.y)
}

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

var list = listOf(1,2,3)
list += 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

map[key]
mutableMap[key] = newValue

// is translate to
x[a,b] -> x.get(a, b)
x[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.

class Board {}
board[1,2] = 'x'
board[1,2] // x

operator fun Board.get(x: Int, y: Int): Char {...}
operator 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.

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

Exams and tests

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

Week 2 exame: The Secret game

data class Evaluation(val rightPosition: Int, val wrongPosition: Int)

fun evaluateGuess(secret: String, guess: String): Evaluation {
    val totalSecret: MutableList<Pair<Int, Char>> = secret.subtract(guess)
    val totalGuess: MutableList<Pair<Int, Char>> = guess.subtract(secret)

    return Evaluation(
            rightPosition = calculateRightGuess(secret, totalSecret),
            wrongPosition = calculateWrongPositions(totalSecret, totalGuess)
    )
}

private fun calculateRightGuess(secret: String, totalSecret: MutableList<Pair<Int, Char>>) = secret.length - totalSecret.size
private fun calculateWrongPositions(totalSecret: MutableList<Pair<Int, Char>>, totalGuess: MutableList<Pair<Int, Char>>): Int {
    var wrong = 0
    totalSecret.forEach {
        val found = totalGuess.findChar(it.second)
        if (found != null) {
            wrong++
            totalGuess.remove(found)
        }
    }
    return wrong
}

private fun String.asIndexMap() = this.toCharArray().mapIndexed { index, c -> index to c }.toMutableList()
private fun String.subtract(from: String) = this.asIndexMap().minus(from.asIndexMap()).toMutableList()
private 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.

fun List<Int>.allNonZero() = all { it != 0 }
fun List<Int>.allNonZero1() = none { it == 0 }
fun List<Int>.allNonZero2() = !any { it == 0 }

fun List<Int>.containsZero() = any { it == 0 }
fun List<Int>.containsZero1() = !all { it != 0 }
fun List<Int>.containsZero2() = !none { it == 0 }

fun main(args: Array<String>) {
    val list1 = listOf(1, 2, 3)
    list1.allNonZero() eq true
    list1.allNonZero1() eq true
    list1.allNonZero2() eq true

    list1.containsZero() eq false
    list1.containsZero1() eq false
    list1.containsZero2() eq false

    val list2 = listOf(0, 1, 2)
    list2.allNonZero() eq false
    list2.allNonZero1() eq false
    list2.allNonZero2() eq false

    list2.containsZero() eq true
    list2.containsZero1() eq true
    list2.containsZero2() eq true
}

infix fun <T> T.eq(other: T) {
    if (this == other) println("OK")
    else println("Error: $this != $other")
}

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

val VOWELS = listOf('a', 'e', 'i', 'o', 'u')
val forbiddenWords = listOf("ba", "be", "bu")

fun String.isNice(): Boolean {
    var total = 0

    val hasNotForbiddenWords = { input: String -> if (forbiddenWords.none{ input.contains(it)}) 1 else 0 }
    val hasMoreThenThreeVOWELS = { input: String -> if (input.count(VOWELS::contains) >= 3) 1 else 0 }
    val hasDoubleLetters = { input: String -> if (input.zipWithNext().count { it.first == it.second } > 0) 1 else 0 }

    total += hasNotForbiddenWords(this)
    total += hasMoreThenThreeVOWELS(this)
    total += hasDoubleLetters(this)

    return total > 1
}

Assignment: Taxi park

/*
 * Task #1. Find all the drivers who performed no trips.
 */
fun TaxiPark.findFakeDrivers(): Set<Driver> = allDrivers.minus(trips.map { it.driver }.toSet())

/*
 * Task #2. Find all the clients who completed at least the given number of trips.
 */
fun TaxiPark.findFaithfulPassengers(minTrips: Int): Set<Passenger> {
    val byPassenger = trips.map { it.passengers }

    return allPassengers.filter {
        byPassenger.count { trip -> trip.contains(it) } >= minTrips
    }.toSet()
}

/*
 * Task #3. Find all the passengers, who were taken by a given driver more than once.
 */
fun TaxiPark.findFrequentPassengers(driver: Driver): Set<Passenger> = trips
        .filter { it.driver == driver }
        .flatMap (Trip::passengers)
        .groupBy { passenger -> passenger }
        .filterValues {group -> group.size > 1 }
        .keys

/*
 * Task #4. Find the passengers who had a discount for majority of their trips.
 */
private fun Passenger.hadMoreTripsWithDiscount(trips: List<Trip>): Boolean {
    val totalRides = trips.filter { it.passengers.contains(this) }.map { it.discount }
    val ridesWithoutDiscount = totalRides.filter(Objects::isNull).size
    val ridesWithDiscount = totalRides.filter(Objects::nonNull).size

    return ridesWithDiscount > ridesWithoutDiscount
}

fun TaxiPark.findSmartPassengers(): Set<Passenger> = allPassengers.filter { it.hadMoreTripsWithDiscount(trips) }.toSet()

/*
 * Task #5. Find the most frequent trip duration among minute periods 0..9, 10..19, 20..29, and so on.
 * Return any period if many are the most frequent, return `null` if there're no trips.
 */
fun TaxiPark.findTheMostFrequentTripDurationPeriod(): IntRange? {
    return trips
        .groupBy{
            val start = it.duration /10 * 10
            val end = start + 9
            start..end
        }
        .maxBy{(_,group) -> group.size}
        ?.key
}

/*
 * Task #6.
 * Check whether 20% of the drivers contribute 80% of the income.
 */
fun TaxiPark.checkParetoPrinciple(): Boolean {
    if(this.trips.isEmpty()) {
        return false
    }
    val incomeByDriver = trips
            .groupBy { it.driver }
            .map { (_, tripsByDriver) -> tripsByDriver.sumDouble(Trip::cost)}
            .sortedByDescending { it }

    val topTwentyDrivers = (allDrivers.size * 0.2).roundToInt()
    var income = incomeByDriver.take(topTwentyDrivers).sum()


    val total = trips.sumByDouble(Trip::cost)
    val percentage = income * 100 / total
    return percentage >= 80
}

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).

var foo: Int = 0
    get():Int  {
       field += 1
       return field
    }

fun main(args: Array<String>) {
    // The values should be different:
    println(foo)
    println(foo)
    println(foo)
}

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.

package rationals

import java.math.BigInteger.ONE
import java.math.BigInteger.ZERO
import java.math.BigInteger

class Rational(val numerator: BigInteger, val denominator: BigInteger) {

    companion object {
        private fun minCommonDivisor(denominatorA: BigInteger, denominatorB: BigInteger): BigInteger {
            val maxCommonDivisor = denominatorA.gcd(denominatorB)
            return denominatorA * (denominatorB / maxCommonDivisor)
        }

        fun toSameBase(first: Rational, second: Rational): Pair<Rational, Rational> {
            val mmc = minCommonDivisor(first.denominator, second.denominator)

            val firstEquivalentRational = Rational(first.toEquivalentNumerator(mmc), mmc)
            val secondEquivalentRational = Rational(second.toEquivalentNumerator(mmc), mmc)

            return firstEquivalentRational to secondEquivalentRational
        }
    }

    fun toEquivalentNumerator(mmc: BigInteger) = this.numerator * (mmc / this.denominator)

    /*
    The denominator must be always positive in the normalized form.
    If the negative rational is normalized, then only the numerator can be negative,
    e.g. the normalized form of 1/-2 should be -1/2.wq
    */
    fun normalize(): Rational {
        if (numerator == ZERO) return this

        val maxDivider = numerator.gcd(denominator)
        val normalized = Rational(
                numerator = numerator / maxDivider,
                denominator = (denominator / maxDivider).abs()
        )

        return when {
            denominator < ZERO -> -normalized
            else -> normalized
        }
    }

    override fun toString(): String {
        return when (denominator) {
            ONE -> numerator.toString()
            else -> "$numerator/$denominator"
        }
    }

    override fun equals(other: Any?) = when (other) {
        is Rational -> this.numerator == other.numerator && this.denominator == other.denominator
        else -> super.equals(other)
    }

    override fun hashCode(): Int {
        var result = numerator.hashCode()
        result = 31 * result + denominator.hashCode()
        return result
    }

    operator fun compareTo(rational: Rational): Int {
        val (sameBaseThis, sameBaseTarget) = toSameBase(this, rational)

        return when {
            sameBaseThis.numerator > sameBaseTarget.numerator -> 1
            sameBaseThis.numerator < sameBaseTarget.numerator -> -1
            else -> 0
        }
    }

    operator fun unaryMinus(): Rational {
        return Rational(-numerator, denominator)
    }

    operator fun rangeTo(rational: Rational): Pair<Rational, Rational> {
        return Pair(this, rational)
    }

    operator fun plus(target: Rational): Rational {
        val (sameBaseThis, sameBaseTarget) = toSameBase(this, target)
        return Rational(
                numerator = sameBaseThis.numerator + sameBaseTarget.numerator,
                denominator = sameBaseThis.denominator
        ).normalize()
    }

    operator fun minus(target: Rational): Rational {
        val (sameBaseThis, sameBaseTarget) = toSameBase(this, target)
        return Rational(
                numerator = sameBaseThis.numerator - sameBaseTarget.numerator,
                denominator = sameBaseThis.denominator
        ).normalize()
    }

    operator fun times(target: Rational) = Rational(
            numerator = this.numerator * target.numerator,
            denominator = this.denominator * target.denominator
    ).normalize()

    operator fun div(target: Rational) = Rational(
            numerator = this.numerator * target.denominator,
            denominator = this.denominator * target.numerator).normalize()
}

operator fun Pair<Rational, Rational>.contains(rational: Rational): Boolean {
    return rational >= this.first && rational < this.second
}

fun String.toRational(): Rational {
    return if (this.contains("/")) {
        val (num, den) = this.split("/")
        Rational(num.toBigInteger(), den.toBigInteger()).normalize()
    } else {
        Rational(this.toBigInteger(), ONE)
    }
}

infix fun Number.divBy(first: Number) = Rational(this.toString().toBigInteger(), first.toString().toBigInteger()).normalize()

fun main() {
    val half = 1 divBy 2
    val third = 1 divBy 3

    val sum: Rational = half + third
    println(5 divBy 6 == sum)

    val difference: Rational = half - third
    println(1 divBy 6 == difference)

    val product: Rational = half * third
    println(1 divBy 6 == product)

    val quotient: Rational = half / third
    println(3 divBy 2 == quotient)

    val negation: Rational = -half
    println(-1 divBy 2 == negation)

    println((2 divBy 1).toString() == "2")
    println((-2 divBy 4).toString() == "-1/2")
    println("117/1098".toRational().toString() == "13/122")

    val twoThirds = 2 divBy 3
    println(half < twoThirds)

    println(half in third..twoThirds)

    println(2000000000L divBy 4000000000L == 1 divBy 2)

    println("912016490186296920119201192141970416029".toBigInteger() divBy
            "1824032980372593840238402384283940832058".toBigInteger() == 1 divBy 2)
}

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.

data class Cell(val i: Int, val j: Int) {
    override fun toString() = "($i, $j)"
}

enum class Direction {
    UP, DOWN, RIGHT, LEFT;

    fun reversed() = when (this) {
        UP -> DOWN
        DOWN -> UP
        RIGHT -> LEFT
        LEFT -> RIGHT
    }
}

interface SquareBoard {
    val width: Int

    fun getCellOrNull(i: Int, j: Int): Cell?
    fun getCell(i: Int, j: Int): Cell

    fun getAllCells(): Collection<Cell>

    fun getRow(i: Int, jRange: IntProgression): List<Cell>
    fun getColumn(iRange: IntProgression, j: Int): List<Cell>

    fun Cell.getNeighbour(direction: Direction): Cell?
}

interface GameBoard<T> : SquareBoard {

    operator fun get(cell: Cell): T?
    operator fun set(cell: Cell, value: T?)

    fun filter(predicate: (T?) -> Boolean): Collection<Cell>
    fun find(predicate: (T?) -> Boolean): Cell?
    fun any(predicate: (T?) -> Boolean): Boolean
    fun all(predicate: (T?) -> Boolean): Boolean
}

open class SquareBoardImp(final override val width: Int) : SquareBoard {

    private val cells: List<Cell> = sequence {
        var i = 0
        repeat(width) {
            var j = 0
            i++
            repeat(width) {
                j++
                yield(Cell(i, j))
            }
        }
    }.toList()

    private fun List<Cell>.find(i: Int, j: Int) = this.find { it.i == i && it.j == j }

    override fun getAllCells(): Collection<Cell> {
        return cells
    }

    override fun getCell(i: Int, j: Int): Cell = cells.find(i, j)!!

    override fun getCellOrNull(i: Int, j: Int): Cell? = cells.find(i, j)

    override fun getColumn(iRange: IntProgression, j: Int): List<Cell> {
        return sequence {
            for (i in iRange) {
                getCellOrNull(i, j)?.let { yield(it) }
            }
        }.toList()
    }

    override fun getRow(i: Int, jRange: IntProgression): List<Cell> {
        return sequence {
            for (j in jRange) {
                getCellOrNull(i, j)?.let { yield(it) }
            }
        }.toList()
    }

    override fun Cell.getNeighbour(direction: Direction): Cell? {
        val (i, j) = when (direction) {
            Direction.UP -> i - 1 to j
            Direction.DOWN -> i + 1 to j
            Direction.RIGHT -> i to j + 1
            Direction.LEFT -> i to j - 1
        }
        return cells.find(i, j)
    }
}

class GameBoardImp<T>(size: Int) : SquareBoardImp(size), GameBoard<T> {

    private var values: Set<Pair<Cell, T?>> = this.getAllCells().map { it to null }.toSet()

    override fun get(cell: Cell): T? = values.find { it.first == cell }!!.second

    override fun set(cell: Cell, value: T?) {
        values = values.map {
            val updatedValue = if (it.first == cell) value else it.second

            it.first to updatedValue
        }.toSet()
    }

    override fun filter(predicate: (T?) -> Boolean): Collection<Cell> {
        return values.filter { predicate(it.second) }.map { it.first }
    }

    override fun find(predicate: (T?) -> Boolean): Cell? {
        return values.find { predicate(it.second) }?.first
    }

    override fun any(predicate: (T?) -> Boolean): Boolean {
        return values.any { predicate(it.second) }
    }

    override fun all(predicate: (T?) -> Boolean): Boolean {
        return values.all { predicate(it.second) }
    }
}

Fibonacci sequence

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

fun fibonacci(): Sequence<Int> = buildSequence {
    val elements = Pair(0,1)
    while(true) {
        yiled(elements.first)
        elements = Pair(elements.second, elements.first + elements.second)
    }
}

fun main(args: Array<String>) {
    fibonacci().take(4).toList().toString() eq
            "[0, 1, 1, 2]"

    fibonacci().take(10).toList().toString() eq
            "[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]"
}

infix fun <T> T.eq(other: T) {
    if (this == other) println("OK")
    else println("Error: $this != $other")
}