Last active
April 27, 2020 23:25
-
-
Save NaCI/7aa048129132d1163462925a22005bf8 to your computer and use it in GitHub Desktop.
Kotlin Notes
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* ANY type holds any type of object. | |
* The root of the Kotlin class hierarchy. Every Kotlin class has Any as a superclass. | |
*/ | |
/** | |
* Almost everything in Kotlin is an expression with a value, but there a few exceptions. | |
* WHILE loops and FOR loops are not expression with a value | |
*/ | |
/** | |
* ———————————————————————————————————————— | |
* NULL Safety - Safe Calls | |
* safe call operator, written ?. | |
* ———————————————————————————————————————— | |
*/ | |
// init nullable item list with values | |
val listWithNulls: List<String?> = listOf("Kotlin", null, "Java", null, null, "C#") | |
// iterate over all items in the list | |
for(item in listWithNulls) { | |
// check if item is null or not | |
// let => provides to implement function for object | |
// print values if item is not null | |
item?.let { println(it+"\n") } | |
} | |
// another safe call example | |
bob?.department?.head?.name | |
// If either `person` or `person.department` is null, the function is not called: | |
person?.department?.head = managersPool.getManager() | |
/** | |
* ———————————————————————————————————————— | |
* Elvis Operator | |
* ———————————————————————————————————————— | |
*/ | |
// When we have a nullable reference b, we can say "if b is not null, use it, otherwise use some non-null value": | |
val l: Int = if (b != null) b.length else -1 | |
// Along with the complete if-expression, this can be expressed with the Elvis operator, written ?:: | |
val l = b?.length ?: -1 | |
// Note that, since throw and return are expressions in Kotlin, they can also be used on the right hand side of the elvis operator. | |
// This can be very handy, for example, for checking function arguments: | |
fun foo(node: Node): String? { | |
val parent = node.getParent() ?: return null | |
val name = node.getName() ?: throw IllegalArgumentException("name expected") | |
// ... | |
} | |
/** | |
* ———————————————————————————————————————— | |
* !! Operator | |
* The third option is for NPE-lovers: | |
* the not-null assertion operator (!!) converts any value to a non-null type and | |
* throws an exception if the value is null. We can write b!!, and this will return a non-null value of b (e.g., a String in our example) or throw an NPE if b is null: | |
* ———————————————————————————————————————— | |
*/ | |
val l = b!!.length | |
/** | |
* ———————————————————————————————————————— | |
* Safe Casts | |
* Regular casts may result into a ClassCastException if the object is not of the target type. | |
* Another option is to use safe casts that return null if the attempt was not successful: | |
* ———————————————————————————————————————— | |
*/ | |
val aInt: Int? = a as? Int | |
/** | |
* ———————————————————————————————————————— | |
* Collections of Nullable Type | |
* If you have a collection of elements of a nullable type and want to filter non-null elements, | |
* you can do so by using filterNotNull | |
* ———————————————————————————————————————— | |
*/ | |
val nullableList: List<Int?> = listOf(1, 2, null, 4) | |
val intList: List<Int> = nullableList.filterNotNull() | |
/** | |
* ———————————————————————————————————————— | |
* String Format | |
* ———————————————————————————————————————— | |
*/ | |
val numberOfItems = 13.4 | |
"I have $numberOfItems item in my backpack" | |
val numberOfThings = 12 | |
"I have ${numberOfItems + numberOfThings} item in my backpack" | |
/** | |
* ———————————————————————————————————————— | |
* Using Range with if-else | |
* ———————————————————————————————————————— | |
*/ | |
val fish = 50 | |
if(fish in 1..100) println("good") | |
val character = "b" | |
if(character in "a".."z") println("good") | |
/** | |
* ———————————————————————————————————————— | |
* Switch - case alternate | |
* ———————————————————————————————————————— | |
*/ | |
val numberOfThings = 12 | |
when(numberOfThings) { | |
0 -> println("No item") | |
20 -> println("20 item") | |
13,14 -> println("It is 13 or 14") | |
in 10..15 -> println("Between 10 and 15") | |
!in 0..10 -> println("Outside of the range 0 to 10") | |
else -> println("Perfect") | |
} | |
/** | |
* Triple Quoted String | |
*/ | |
val herSpeech = "'Oh c'mon'" | |
val myText = """ And -the man says that : "Am i -the only one here!".${"\n"} Then she replied $herSpeech """ | |
println(myText.trim()) | |
// Output : | |
// And -the man says that : "Am i -the only one here!". | |
// Then she replied 'Oh c'mon' | |
/** | |
* String Patterns | |
*/ | |
fun getMonthPattern() = """\d{2}\.\d{2}\.\d{4}""" | |
"13.02.1992".matches(getMonthPattern().toRegex()) // true | |
val month = "(JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)" | |
fun getPattern(): String = """\d{2} $month \d{4}""" | |
"13 JAN 1992".matches(getPattern().toRegex()) // true | |
/** | |
* Nothing type | |
* Nothing type can be used as a return type for a function that always throws an exception. | |
* When you call such a function, the compiler uses the information that it throws an exception. | |
*/ | |
fun failWithWrongAge(age: Int?): Nothing { // If that was not return nothing nullable age param will give an error | |
throw IllegalArgumentException("Wrong age: $age") | |
} | |
fun checkAge(age: Int?) { | |
if (age == null || age !in 0..150) failWithWrongAge(age) | |
println("Congrats! Next year you'll be ${age + 1}.") | |
} | |
checkAge(10) | |
/** | |
* Import Rename | |
* as NewName | |
*/ | |
import kotlin.random.Random as KRandom | |
import java.util.Random as JRandom | |
/** | |
* Kotlin Idioms | |
* https://kotlinlang.org/docs/reference/idioms.html | |
*/ | |
/** | |
* Kotlin Standard Library | |
* https://kotlinlang.org/api/latest/jvm/stdlib/ | |
*/ | |
/** | |
* REFERENCES | |
* https://www.udacity.com/course/kotlin-bootcamp-for-programmers--ud9011 | |
* https://kotlinlang.org/docs/reference/control-flow.html | |
* https://bezkoder.com/kotlin-list-mutable-list/ | |
* https://dzone.com/articles/learning-kotlin-return-when | |
*/ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Consts | |
* Consts are compile time constants. T | |
* Their value has to be assigned during compile time, unlike vals, where it can be done at runtime. | |
* This means, that consts can never be assigned to a function or any class constructor, but only to a String or primitive. | |
*/ | |
const val foo = complexFunctionCall() //Not okay | |
val fooVal = complexFunctionCall() //Okay | |
const val bar = "Hello world" | |
// We can't declare const in class | |
class MyClass { | |
const val CONSTANT2 = "asd" // This will give error | |
} | |
// Instead we need to use compainon object in class or object class | |
class MyClass3 { | |
companion object { | |
const val CONSTANT2 = "123" | |
} | |
} | |
// or | |
object MyClass2 { | |
const val CONSTANT2 = "asd" | |
} | |
// Usage | |
println(MyClass2.CONSTANT2) | |
println(MyClass3.CONSTANT2) | |
/** | |
* EXTENSION FUNCTIONS | |
* It will allow to add functions to an existing class, without having access to its source code | |
* They will not actually modify the class they extend | |
* By defining an extention you do not insert new members into the class | |
*/ | |
fun String.hasSpaces(): Boolean { | |
val found: Char? = this.find { it == ' ' } | |
return found != null | |
} | |
// Alternative way to represent same function | |
fun String.hasSpaces(): Boolean = find { it == ' ' } != null | |
println("My text with space".hasSpaces()) | |
// Note: "extenstions.kt" is good file to keep all extension functions | |
open class AquariumPlant(var color: String, private val size: Int) | |
class GreenLeafyPlant(size: Int): AquariumPlant("Green", size) | |
fun AquariumPlant.isRed() = color.equals("Red", ignoreCase = true) | |
// Extension functions can't access to their extended class's private variables | |
fun AquariumPlant.isBig() = size < 20 // It will give error since size is private variable | |
fun AquariumPlant.print() = println("AquariumPlant") | |
fun GreenLeafyPlant.print() = println("GreenLeafyPlant") | |
val plant = GreenLeafyPlant(20) | |
println(plant.isRed()) // false | |
plant.print() // Output : GreenLeafyPlant | |
val aquariumPlant: AquariumPlant = plant | |
aquariumPlant.print() // Output : AquariumPlant | |
// Normally it should print GreenLeafyPlant but it prints AquariumPlant | |
// because extension functions resolved statically and at compile time (not on runtime) | |
// Extension properties | |
val AquariumPlant.isGreen: Boolean | |
get() = color.equals("Green", ignoreCase = true) | |
val plant = GreenLeafyPlant(20) | |
println(plant.isGreen) | |
// Nullable Example | |
fun AquariumPlant?.pull() { | |
this?.apply { | |
println("removing $this") | |
} | |
} | |
var nullPlant: AquariumPlant? = null | |
nullPlant.pull() // it will not throw exception | |
/** | |
* Extention Function as parameter | |
*/ | |
// By HashMap<K, V>.() defining as that we make our build function as extention function for hashmap | |
fun <K, V> buildMap(build: HashMap<K, V>.() -> Unit): Map<K, V> { | |
val map = HashMap<K, V>() | |
map.build() | |
return map | |
} | |
// Another Example | |
fun <T> T.myApply(f: T.() -> Unit): T { | |
this.f() | |
return this | |
} | |
fun createString(): String { | |
return StringBuilder().myApply { | |
append("Numbers: ") | |
for (i in 1..10) { | |
append(i) | |
} | |
}.toString() | |
} | |
fun createMap(): Map<Int, String> { | |
return hashMapOf<Int, String>().myApply { | |
put(0, "0") | |
for (i in 1..10) { | |
put(i, "$i") | |
} | |
} | |
} | |
/** | |
* Reflections | |
*/ | |
class Plant { | |
fun trim() {} | |
fun fertilize() {} | |
} | |
fun reflections() { | |
val classObj: KClass<Plant> = Plant::class | |
for (method: KFunction<*> in classObj.declaredMemberFunctions){ | |
println("${classObj.simpleName} has ${method.name} method") | |
} | |
} | |
/** | |
* APPLY, RUN, LET | |
*/ | |
// Apply return the object itself | |
// Apply can be very useful for calling functions on a newly created object | |
data class Bird(var name: String) | |
val bird = Bird("marti").apply { name = "sahin" } | |
// Difference between run and apply is | |
// Run returns the result of executing the lambda | |
// While Apply returns the object after the lambda has been applied | |
val bird2 = Bird("leylek") | |
println(bird.run { name.toUpperCase() }) // Leylek | |
println(bird.apply { name = name.toUpperCase() }) // Bird(name=Leylek) | |
// Let returns copy of changed object | |
// Let is particularly useful for chaining manipulations together | |
val result = bird.let { it.name.capitalize() } // Sahin | |
.let { "$it bird" } // Sahin bird | |
.let { it.length } // 10 - converts variable to integer | |
.let { it.plus(30) } // 40 | |
println(result) // Output: 40 | |
/** | |
* Operator overloading | |
* Kotlin allows us to provide implementations for a predefined set of operators on our types. | |
* These operators have fixed symbolic representation (like + or *) and fixed precedence. | |
*/ | |
data class MyDate(val year: Int, val month: Int, val dayOfMonth: Int) | |
// Supported intervals that might be added to dates: | |
enum class TimeInterval { DAY, WEEK, YEAR } | |
fun MyDate.addTimeIntervals(timeInterval: TimeInterval, amount: Int): MyDate { | |
val c = Calendar.getInstance() | |
c.set(year + if (timeInterval == TimeInterval.YEAR) amount else 0, month, dayOfMonth) | |
var timeInMillis = c.timeInMillis | |
val millisecondsInADay = 24 * 60 * 60 * 1000L | |
timeInMillis += amount * when (timeInterval) { | |
TimeInterval.DAY -> millisecondsInADay | |
TimeInterval.WEEK -> 7 * millisecondsInADay | |
TimeInterval.YEAR -> 0L | |
} | |
val result = Calendar.getInstance() | |
result.timeInMillis = timeInMillis | |
return MyDate(result.get(Calendar.YEAR), result.get(Calendar.MONTH), result.get(Calendar.DATE)) | |
} | |
operator fun MyDate.plus(timeInterval: TimeInterval) = | |
addTimeIntervals(timeInterval, 1) | |
class RepeatedTimeInterval(val timeInterval: TimeInterval, val number: Int) | |
operator fun TimeInterval.times(number: Int) = | |
RepeatedTimeInterval(this, number) | |
operator fun MyDate.plus(timeIntervals: RepeatedTimeInterval) = | |
addTimeIntervals(timeIntervals.timeInterval, timeIntervals.number) | |
fun task1(today: MyDate): MyDate { | |
return today + YEAR + WEEK | |
} | |
fun task2(today: MyDate): MyDate { | |
return today + YEAR * 2 + WEEK * 3 + DAY * 5 | |
} | |
/** | |
* Invoke | |
* Objects with an invoke() method can be invoked as a function. | |
* You can add an invoke extension for any class, but it's better not to overuse it | |
*/ | |
class Invokable { | |
var numberOfInvocations: Int = 0 | |
private set // this variable can not be set outside of the class | |
operator fun invoke(): Invokable { | |
numberOfInvocations++ | |
return this | |
} | |
} | |
fun invokeTriple(invokable: Invokable) = invokable()()() | |
println("Method invoked ${invokeTriple(Invokable()).numberOfInvocations} times") // Output : Method invoked 3 times | |
/** | |
* Sequences | |
* LAZY = fetch when needed | |
* EAGER = fetch immediately | |
* The first important thing to know is that all intermediate operations | |
* (the functions that return a new sequence) are not executed until a terminal operation has been called. | |
* Sequences are evaluated lazily to avoid examining all of the input data when it's not necessary. | |
* filter, map, distinct and sorted are intermediate | |
* toList and others are terminal | |
* asSequence() is used to change list to sequence | |
*/ | |
// This will print nothing unless calling a terminal operation | |
sequenceOf("A", "B", "C") | |
.filter { | |
println("filter: $it") | |
true | |
} | |
// But now it will be print the values | |
sequenceOf("A", "B", "C") | |
.filter { | |
println("filter: $it") | |
true | |
} | |
.forEach { | |
println("forEach: $it") | |
} | |
// Output : | |
// filter: A | |
// forEach: A | |
// filter: B | |
// forEach: B | |
// filter: C | |
// forEach: C | |
// In the example below, | |
// c is never processed due to the early exit characteristic of sequences. | |
// The terminal function any stops further processing of elements as soon as the given predicate function yields true. | |
// This can greatly improve the performance of your application when working with large input collections. | |
val result = sequenceOf("a", "b", "c") | |
.map { | |
println("map: $it") | |
it.toUpperCase() | |
} | |
.any { | |
println("any: $it") | |
it.startsWith("B") | |
} | |
println(result) | |
// Output : | |
// map: A | |
// any: A | |
// map: B | |
// any: B | |
// true | |
/** | |
* Collections vs. Sequences | |
* Another huge benefit of sequences over collections is lazy evaluation. | |
* Since collection operations are not lazily evaluated you see huge performance increases when using sequences in certain scenarios. | |
*/ | |
fun measure(block: () -> Unit) { | |
val nanoTime = measureNanoTime(block) | |
val millis = TimeUnit.NANOSECONDS.toMillis(nanoTime) | |
print("$millis ms") | |
} | |
val list = generateSequence(1) { it + 1 } | |
.take(50_000_000) | |
.toList() | |
measure { | |
list | |
.filter { it % 3 == 0 } | |
.average() | |
} | |
// 8644 ms | |
val sequence = generateSequence(1) { it + 1 } | |
.take(50_000_000) | |
measure { | |
sequence | |
.filter { it % 3 == 0 } | |
.average() | |
} | |
// 822 ms | |
/** | |
* Generic Functions | |
*/ | |
// Implementation of standart library's partition() method | |
fun <T, V: MutableCollection<T>> Collection<T>.partitionTo(list1: V,list2: V, predicate: (T) -> Boolean): Pair<V, V> { | |
for(item in this) { | |
if(predicate(item)) { | |
list1.add(item) | |
} else { | |
list2.add(item) | |
} | |
} | |
return Pair(list1, list2) | |
} | |
val (words, lines) = listOf("a", "a b", "c", "d e").partitionTo(ArrayList(), ArrayList()) { s -> !s.contains(" ") } | |
val (letters, other) = setOf('a', '%', 'r', '}').partitionTo(HashSet(), HashSet()) { c -> c in 'a'..'z' || c in 'A'..'Z' } | |
/** | |
* REFERENCES | |
* https://www.udacity.com/course/kotlin-bootcamp-for-programmers--ud9011 | |
* https://winterbe.com/posts/2018/07/23/kotlin-sequence-tutorial/ | |
*/ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Every method and property has PUBLIC access-modifier by default in Kotlin | |
*/ | |
/** | |
* All Access Modifier (public, private, internal, protected) | |
* | |
* INTERNAL means variable is accessible across the same module | |
* this means we can use it anywhere inside of our project but if that was a library it wouldn't be exported as a function | |
* | |
* PRIVATE inside class, subclasses can't see | |
* | |
* PROTECTED inside class, subclasses CAN see | |
*/ | |
// BASIC CLASS DEFINITION | |
class Aquarium { | |
var width: Int = 20 | |
var height: Int = 40 | |
var length: Int = 100 | |
// fun volume(): Int = width * height * length / 1000 | |
// Instead of function we can define property with get method | |
//val volume : Int | |
// get() = width * height * length / 1000 | |
// Also with setter | |
var volume : Int | |
get() = width * height * length / 1000 | |
set(value) { height = (value * 1000) / (width * length)} // setter could be defined as private (private set(value)) | |
} | |
// Getting instance of aquarium | |
val myAquarium = Aquarium() | |
// Another propert sample with getter | |
class SimpleSpice { | |
val name: String = "curry" | |
private val spiceness: String = "mild" | |
val heat: Int | |
get() { | |
return when(spiceness) { | |
"mild" -> 5 | |
else -> 10 | |
} | |
} | |
} | |
// Constructor (like method arguments) | |
// With default parameters constructor overloading is not needed | |
class Aquarium(var width: Int = 20, var height: Int = 40, var length: Int = 100) { | |
var volume : Int | |
get() = width * height * length / 1000 | |
set(value) { height = (value * 1000) / (width * length)} | |
} | |
val smallAquarium = Aquarium(length = 200, width = 10) | |
println("Aquarium is ${smallAquarium.volume} litter") | |
/** | |
* In kotlin we typically don't need secondary constructors | |
* Most classes only provide primary constructor | |
* If we define secondary constructor it must be call primary constructor by using "this" keyword | |
* To write good Kotlin code just use one primary constructor (with default arguments) with init blocks | |
*/ | |
class Fish(val friendly: Boolean = true, volumeNeeded: Int) { | |
// We can call friendly property on Fish object but we can't call volumeNeeded | |
// That is because we didn't define it as val | |
val size: Int | |
init { | |
println("First init block") | |
} | |
// init is the first method to run on initialization | |
init { | |
size = if(friendly) volumeNeeded else volumeNeeded * 2 | |
} | |
// Secondary constructor (Secondary constructor always called after init blocks) | |
constructor(): this(volumeNeeded = 5) { | |
println("Running secondary constructor\nFish : isFriendly{$friendly} size{$size}") | |
} | |
init { | |
println("Constucted fish of size $volumeNeeded final size $size") | |
} | |
} | |
// Calling fish with primary constructor | |
val fish = Fish(false, 10); | |
// OUTPUT : | |
// First init block | |
// Constucted fish of size 10 final size 20 | |
// Calling fish with secondary constructor | |
val fish2 = Fish(); | |
// OUTPUT : | |
// First init block | |
// Constucted fish of size 5 final size 5 | |
// Running secondary constructor | |
// Fish : isFriendly{true} size{5} | |
/** | |
* INHERITANCE | |
* First thing to do to inherit from a class is make the class "open" | |
* Like classes members also are not available for subclassing by default | |
*/ | |
open class Aquarium(open var width: Int = 20, var height: Int = 40, var length: Int = 100) { | |
open var volume : Int | |
get() = width * height * length / 1000 | |
set(value) { height = (value * 1000) / (width * length)} | |
open val water = volume * 0.9 | |
constructor(numberOfFish: Int): this() { | |
val water: Int = numberOfFish * 2000 | |
val tank: Double = water + water * 0.1 | |
height = (tank / (length * width)).toInt() | |
} | |
} | |
// Different representation for constuctor arguments for overrided values | |
class TowerTank(override var width: Int, height: Int): Aquarium(height = height) { | |
override var water = volume * 0.8 | |
override var volume: Int | |
get() = (width * height * length / 1000 * PI).toInt() | |
set(value) { | |
height = (value * 1000) / (width * length) | |
} | |
} | |
val towerTank = TowerTank(width = 10, height = 20) | |
// Another Set Property Example | |
class PropertyExample() { | |
var counter = 0 | |
var propertyWithCounter: Int? = null | |
set(value) { | |
field = value | |
counter++ | |
} | |
} | |
/** | |
* INTERFACES AND ABSTRACT | |
* Abstract classes can have constructors but interfaces can not | |
* Both Interfaces and Abstract classes can contain implementations of methods | |
*/ | |
interface AquariumAction { | |
fun eat() | |
fun jump() | |
fun clean() | |
fun swim() { | |
println("swim") | |
} | |
} | |
abstract class MyAquariumFish: AquariumAction { | |
abstract val color: String | |
override fun eat() = println("yum") | |
} | |
interface FishAction { | |
fun eat() | |
} | |
// Interfaces can be used as a function argument | |
fun feedFish(fish: FishAction) { | |
fish.eat() | |
} | |
class GoldFish: MyAquariumFish(), FishAction { | |
override val color = "orange" | |
override fun jump() { | |
println("GoldFish Jumped") | |
} | |
override fun clean() { | |
println("GoldFish Cleaned") | |
} | |
// We dont need to override eat function, because it has already defined in MyAquariumFish abstract class | |
// With FishAction interface -despite we already have eat method in MyAquariumFish class- we can use feedFish Method | |
} | |
/** | |
* "object" keyword uses for singleton classes | |
*/ | |
/** | |
* INTERFACE DELEGATION | |
*/ | |
interface MyFishAction { | |
fun eat() | |
} | |
interface MyFishColor { | |
val color: String | |
} | |
// SINGLETON Class | |
object GoldColor: MyFishColor { | |
override val color = "gold" | |
} | |
// SINGLETON Class | |
object RedColor: MyFishColor { | |
override val color = "red" | |
} | |
class PrintingFishAction(val food: String): MyFishAction { | |
override fun eat() { | |
println(food) | |
} | |
} | |
class Plecostomus(fishColor: MyFishColor = GoldColor): | |
MyFishAction by PrintingFishAction("a lot of algae"), | |
MyFishColor by fishColor { | |
/* | |
// Since we have "by" class which implements eat method we do not needed to override eat method in our class | |
override fun eat() { | |
println("eat algae") | |
} | |
*/ | |
// override val color = "red" // We dont need to override color that's because "by fishColor" has done it already | |
} | |
// Another representation for Plecostomus class | |
// Since we dont have class body, we dont need curlybracers either | |
class Plecostomus(fishColor: MyFishColor = GoldColor): | |
MyFishAction by PrintingFishAction("a lot of algae"), | |
MyFishColor by fishColor | |
/** | |
* DATA CLASS | |
* Data class : main purpose is to hold data | |
* The primary constructor needs to have at least one parameter; | |
* All primary constructor parameters need to be marked as val or var; | |
* Data classes cannot be abstract, open, sealed or inner; | |
* overrides "toString" and "equals" method according to class properties | |
*/ | |
data class Decorations(val rock: String, var paper: String, val cut: String) | |
// Equals function overrided | |
data class Decorations2(val rock: String, var paper: String, val cut: String) { | |
override fun equals(other: Any?): Boolean { | |
return if (other is Decorations2) { | |
rock == other.rock && paper == other.paper | |
} else { | |
super.equals(other) | |
} | |
} | |
} | |
val otherDecorations = Decorations2("rockiest", "paperiest", "cuttiest") | |
val otherDecorations2 = Decorations2("rockiest", "paperiest", "blabla") | |
println(otherDecorations == otherDecorations2) // Output : true | |
// Altered Copying | |
println(otherDecorations2.copy(rock = "alteredRock")) | |
// Kotlin Destructuring | |
val (rock, paper, cut) = otherDecorations2 | |
println("Rock: $rock - Paper: $paper - Cut: $cut") | |
// Data classes can also uses abstract or interfaces | |
interface ContainerName { | |
val name: String | |
} | |
data class SpiceContainer(val spice: Spice): ContainerName { | |
val label = spice.name | |
override val name = "SpiceContainer" | |
} | |
// Note that the compiler only uses the properties defined inside the primary constructor for the automatically generated functions. | |
// To exclude a property from the generated implementations, declare it inside the class body: | |
data class Person(val name: String) { | |
var age: Int = 0 | |
} | |
val person1 = Person("John") | |
val person2 = Person("John") | |
person1.age = 10 | |
person2.age = 20 | |
println(person1 == person2) // Output: true | |
// SINGLETON (object) Classes | |
object SingletonItem { | |
var name = "My Singleton Item Name" | |
val sayName: () -> String = { | |
"My name is $name" | |
} | |
} | |
val singletonItem1 = SingletonItem | |
println("singletonItem : $singletonItem1") // Output: singletonItem : SingletonItem@7a0ac6e3 | |
println("singletonItem : ${singletonItem1.sayName()}") // Output: singletonItem : My name is My Singleton Item Name | |
singletonItem1.name = "Another Name" | |
val singletonItem2 = SingletonItem | |
println("singletonItem : $singletonItem2") // Output: singletonItem : SingletonItem@7a0ac6e3 | |
println("singletonItem : ${singletonItem2.sayName()}") // Output: singletonItem : My name is Another Name | |
/** | |
* ENUMS | |
*/ | |
enum class Color(val rgb: Int) { | |
RED(0xFF0000), | |
GREEN(0x00FF00), | |
BLUE(0x0000FF); | |
fun printMyColorValue() { | |
println("My color value is $rgb") | |
} | |
} | |
val colorRed = Color.GREEN | |
colorRed.printMyColorValue() // Output : My color value is 65280 | |
println("Enum name : ${colorRed.name} - Enum ordinal : ${colorRed.ordinal}") | |
// Output : Enum name : GREEN - Enum ordinal : 1 | |
// Making a class a sealed class helps keep all the subclasses together in one file. | |
/** | |
* Lateinit and Lazy | |
*/ | |
// -------------------------------------------------------------------------------------------------- | |
// Lateinit | |
// Normally, properties declared as having a non-null type must be initialized in the constructor. | |
// However, fairly often this is not convenient. | |
// For example, properties can be initialized through dependency injection, or in the setup method of a unit test. | |
// In this case, you cannot supply a non-null initializer in the constructor, but you still want to avoid null checks when referencing the property inside the body of a class. | |
// To handle this case, you can mark the property with the lateinit modifier. | |
// -------------------------------------------------------------------------------------------------- | |
class Test { | |
lateinit var value: String | |
fun setup() { | |
value = "Ali" | |
} | |
} | |
// -------------------------------------------------------------------------------------------------- | |
// Lazy | |
// lazy() is a function that takes a lambda and returns an instance of lazy | |
// which can serve as a delegate for implementing a lazy property: | |
// the first call to get() executes the lambda passed to lazy() and remembers the result, | |
// subsequent calls to get() simply return the remembered result. | |
// -------------------------------------------------------------------------------------------------- | |
public class Example{ | |
val name: String by lazy { “Ali Veli” } | |
} | |
/** | |
* REFERENCES | |
* https://www.udacity.com/course/kotlin-bootcamp-for-programmers--ud9011 | |
* https://kotlinlang.org/docs/reference/data-classes.html | |
* https://blog.mindorks.com/learn-kotlin-lateinit-vs-lazy | |
*/ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Load image with Glide | |
fun ImageView.loadFromUrl(url: String, context: Context) { | |
Glide.with(context).load(url).into(this) | |
} | |
imageView.loadUrl(url, context) | |
// ViewGroup Childs | |
val ViewGroup.children: List | |
get() = (0..childCount -1).map { getChildAt(it) } | |
parent.children.forEach { it.visible() } | |
// Button disable / enable | |
fun Button.disableButton() { | |
isEnabled = false | |
alpha = 0.7f | |
} | |
/** | |
* Hides the soft input keyboard from the screen | |
*/ | |
fun View.hideKeyboard(context: Context?) { | |
val inputMethodManager = context?.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager | |
inputMethodManager.hideSoftInputFromWindow(this.windowToken, 0) | |
} | |
/** | |
* Check if the Internet connectivity is available | |
*/ | |
fun Context.isInternetAvailable(): Boolean { | |
val connectivityManager = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager | |
val activeNetworkInfo = connectivityManager.activeNetworkInfo | |
return activeNetworkInfo != null && activeNetworkInfo.isConnected | |
} | |
/** | |
* Shows the Snackbar inside an Activity or Fragment | |
* | |
* @param messageRes Text to be shown inside the Snackbar | |
* @param length Duration of the Snackbar | |
* @param f Action of the Snackbar | |
*/ | |
fun View.showSnackbar(@StringRes messageRes: Int, length: Int = Snackbar.LENGTH_LONG, f: Snackbar.() -> Unit) { | |
val snackBar = Snackbar.make(this, resources.getString(messageRes), length) | |
snackBar.f() | |
snackBar.show() | |
} | |
/** | |
* Adds action to the Snackbar | |
* | |
* @param actionRes Action text to be shown inside the Snackbar | |
* @param color Color of the action text | |
* @param listener Onclick listener for the action | |
*/ | |
fun Snackbar.action(@StringRes actionRes: Int, color: Int? = null, listener: (View) -> Unit) { | |
setAction(actionRes, listener) | |
color?.let { setActionTextColor(color) } | |
} | |
// Shows the Snackbar having text and long duration | |
constraintLayout.showSnackbar(R.string.download_complete) {} | |
// Shows the Snackbar having text and short duration | |
constraintLayout.showSnackbar(R.string.download_complete, Snackbar.LENGTH_SHORT) {} | |
// Shows the Snackbar with text and action | |
constraintLayout.showSnackbar(R.string.download_complete) { | |
action(R.string.open) { Log.e("TAG", "Action item clicked") } | |
} | |
/** | |
* Check if the Internet connectivity is available | |
*/ | |
fun Context.isInternetAvailable(): Boolean { | |
val connectivityManager = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager | |
val activeNetworkInfo = connectivityManager.activeNetworkInfo | |
return activeNetworkInfo != null && activeNetworkInfo.isConnected | |
} | |
/** | |
* REFERENCES | |
* https://theengineerscafe.com/useful-kotlin-extension-functions-android/ | |
*/ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* FUNCTIONS | |
* Functions are declared using the fun keyword. | |
* Functions that are defined inside a class or object are called member functions. | |
* Values are returned using the return keyword. | |
* Unlike other languages in kotlin we dont need to define type for no return value functions | |
* Even no return type is specified, return type will be Unit | |
* Function parameters are provided between the "()". They always have the pattern name:Type | |
*/ | |
/** | |
* IT | |
* A shorthand of a single argument lambda is to use the keyword ‘it'. | |
* This value represents any lone that argument we pass to the lambda function | |
*/ | |
fun hello(name: String, age: Int) { | |
println("Hello $name with age $age") | |
} | |
// Funtions returns String | |
fun getHelloText(name: String): String { | |
return "Hello $name"; | |
} | |
// If a function body contains (returns) only a single expression, you reduce it to a single-line function. | |
fun getHelloText(name: String): String = "hello $name" | |
// Interestingly we dont need to specify return type for single-line function | |
fun getHelloText(name: String) = "hello $name" // This will also work | |
fun getHelloText(name: String) { return "hello $name" } // This will NOT work. it will be error for not specifying return type | |
/** | |
* Main method to play around with | |
*/ | |
fun main(args: Array<String>) { | |
println("${getHelloText("naci").capitalize()} Hi, I am a simple string") | |
} | |
// Single Line Function Example with default arguments and Elvis operator | |
fun dayOfWeek(dayOfWeek: Int? = null): String = when(dayOfWeek ?: Calendar.getInstance().get(Calendar.DAY_OF_WEEK)) { | |
1 -> "Sunday" | |
2 -> "Monday" | |
3 -> "Tuesday" | |
4 -> "Wednesday" | |
5 -> "Thursday" | |
6 -> "Friday" | |
7 -> "Saturday" | |
else -> "Friday" | |
} | |
// Everything in Kotlin expression. We can use such statements like below | |
val isUnit = println("This is an expression") | |
println(isUnit) | |
// This is an expression | |
// kotlin.Unit | |
val temperature = 10 | |
val isHot = temperature > 50 | |
val message = "You are ${ if(isHot) "fried" else "safe"} fish" // Ternary operator for kotlin // we can simply use if-else block | |
println(message) // You are safe fish | |
val timeOfTheDay = args[0].toInt(); | |
println(when { | |
timeOfTheDay < 12 -> "Good morning, Kotlin" | |
else -> "Good night, Kotlin" | |
}) | |
fun getFortuneCookie(): String { | |
val listOfFortunes = listOf( | |
"You will have a great day!", | |
"Things will go well for you today.", | |
"Enjoy a wonderful day of success.", | |
"Be humble and all will turn out well.", | |
"Today is a good day for exercising restraint.", | |
"Take it easy and enjoy life!", | |
"Treasure your friends because they are your greatest fortune." | |
) | |
print("Enter your birthday: ") | |
val birthday = readLine()?.toIntOrNull() ?: 1 | |
return listOfFortunes[birthday%listOfFortunes.size] | |
} | |
// LET method provides to do operations after function call // like do-while but not in loop (do-do) :) | |
getFortuneCookie().let { for (i in 1..3) println(it) } // this calls the method once and prints the result 3 times | |
// or call it in for loop several times | |
var fortune: String | |
for (i in 1..10) { | |
fortune = getFortuneCookie() | |
println("Your fortune is: $fortune") | |
if(fortune.contains("Take it easy")) break | |
} | |
// Default Arguments for Functions | |
fun shouldChangeTheWater( | |
day: String, | |
temperature: Int = 22, | |
isDirty: Boolean = false): Boolean { | |
return false; | |
} | |
// Multiple usages of the default argument methods | |
fun main(args: Array<String>) { | |
shouldChangeTheWater("Monday") | |
shouldChangeTheWater("Monday", isDirty = true) | |
shouldChangeTheWater("Monday", 23) | |
shouldChangeTheWater(isDirty = true, day = "Monday", temperature = 12) | |
} | |
fun canAddFish(tankSize: Double, currentFish: List<Int>, fishSize: Int = 2, hasDecorations: Boolean = true): Boolean { | |
return (tankSize * if (hasDecorations) 0.8 else 1.0) >= (currentFish.sum() + fishSize) | |
} | |
// Functions can be called as default arguments | |
fun calculateTemperatureAsCelsius(temperatureAsFahrenheit: Int) = ((temperatureAsFahrenheit - 32)*5)/9 | |
fun shouldChangeWater(day: String, temperature: Int = calculateTemperatureAsCelsius(20)) { | |
//... | |
} | |
// LAMBDA Function (Anonymous or nameless function) | |
{println("Heelo")} () | |
val swim = {println("swim\n")} // Create and assign function to a variable | |
swim() // Output : swim | |
swim // Output : res2: () -> kotlin.Unit = () -> kotlin.Unit | |
// Use function arguments for lambdas | |
var dirty = 20 | |
val waterFilter = { dirty: Int -> dirty / 2 } | |
waterFilter(dirty) // Output : 10 | |
// Define argument and return type | |
val waterFilter: (Int) -> Int = { dirty -> dirty / 2 } | |
waterFilter(dirty) // Output : 10 | |
// If the code returns no value we use the type Unit: | |
var link: (String) -> Unit = {s:String -> println(s) } // just "s" is ok too (without :String) | |
link("test") | |
// Lambda function with multiple arguments | |
val waterFilter = {dirty: Int, divider: Int -> dirty / divider} | |
waterFilter(10, 4) // Output : 2 | |
// Different presentation for same function | |
val waterFilter: (Int, Int) -> Int = { dirty, divider -> dirty / divider } | |
waterFilter(10, 4) // Output : 2 | |
// HIGHER ORDER FUNCTION | |
// It is a function which takes a function as parameter | |
// List Filter and repeat functions are some example of higher-order functions from standard library | |
// Example | |
fun updateDirty(dirty: Int, operation: (Int) -> Int): Int { | |
return operation(dirty) | |
} | |
// Different calls example for high order functions | |
var dirty = 20 | |
val waterFilter: (Int) -> Int = { dirty -> dirty / 2 } | |
fun feedFish(dirty: Int) = dirty + 10 | |
dirty = updateDirty(dirty, waterFilter) // use lambda function as parameter of another function | |
dirty = updateDirty(dirty, ::feedFish) // use name function as parameter of another function | |
dirty = updateDirty(dirty, { dirty -> dirty + 50 }) // create function as parameter of another function | |
dirty = updateDirty(dirty) { dirty -> dirty + 50 } // create function as parameter - short definition | |
val random1 = Random.nextInt(0, 100); // Output is same for every random1 call | |
// random1 has a value assigned at compile time, and the value never changes when the variable is accessed. | |
val random2 = {Random.nextInt(0, 100)}; // // Output changes for every random2() call | |
// random2 has a lambda assigned at compile time, | |
// and the lambda is executed every time the variable is referenced, returning a different value. | |
val rollDice: (Int) -> Int = { | |
print("$it sided dice is rolling.. ") | |
if(it == 0) 0 | |
else Random.nextInt(1, it) + 1 | |
} | |
fun gamePlay(diceSide: Int, operation: (Int) -> Int) { | |
println("The dice is : ${operation(diceSide)}") | |
} | |
gamePlay(4, rollDice) | |
// EXTENTION FUNCTION EXAMPLE | |
fun List<Int>.divisibleBy(operation: (Int) -> Int): List<Int> { | |
var filteredList = mutableListOf<Int>() | |
for(item in this) { | |
if(operation(item) == 0) { | |
filteredList.add(item) | |
} | |
} | |
return filteredList | |
} | |
val numbers = listOf<Int>(1,2,3,4,5,6,7,8,9,0) | |
val divisibleNumbers = numbers.divisibleBy { | |
it.rem(3) | |
} | |
println(divisibleNumbers) // Output : [3, 6, 9, 0] | |
// Different represantation for defining extention functions | |
val isEven: (Int) -> Boolean = { it.rem(2) == 0 } | |
val isOdd: Int.() -> Boolean = { this.rem(2) == 0 } | |
/** | |
* REFERENCES | |
* https://www.udacity.com/course/kotlin-bootcamp-for-programmers--ud9011 | |
*/ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// AN COMPLETE EXAMPLE | |
val Book.weight: Double | |
get() = pages.times(1.5) | |
fun Book.tornPages(tornPages: Int): Int { | |
pages = if (tornPages < pages) pages.minus(tornPages) else 0 | |
return pages | |
} | |
class Book(val title: String, val author: String, val year: String, var pages: Int) { | |
companion object { | |
const val MAX_NUMBER_OF_BOOKS_PER_USER = 30 | |
} | |
fun printUrl() { | |
println("${Constants.BASE_URL}/$title.html") | |
} | |
fun canBorrow(borrowedBookCount: Int): Boolean { | |
return borrowedBookCount < MAX_NUMBER_OF_BOOKS_PER_USER | |
} | |
fun getTitleAndAuthor(): Pair<String, String> { | |
return title to author | |
} | |
fun getTitleAndAuthorAndYear(): Triple<String, String, String> { | |
return Triple(title, author, year) | |
} | |
override fun toString(): String { | |
return "Book(title='$title', author='$author', year='$year', pages=$pages)" | |
} | |
} | |
class Puppy { | |
fun playWithBook(book:Book) { | |
val tornedPageCount = Random.nextInt(0, book.pages) + 1 | |
book.tornPages(tornedPageCount).apply { | |
println( | |
"Sorry but puppy eat $tornedPageCount pages from the ${book.title}\n" + | |
if (this > 0) "Only $this pages left!!" else "Nothing left.." | |
) | |
} | |
} | |
} | |
val myBook = Book("Satranc", "Stefan Zweig", "1997", 120) | |
//val(title, author) = myBook.getTitleAndAuthor() | |
val(title, author, year) = myBook.getTitleAndAuthorAndYear() | |
println("Here is your book $title written by $author in $year.") | |
val puppy = Puppy() | |
while(myBook.weight > 0) { | |
puppy.playWithBook(myBook) | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* GENERIC Classes | |
*/ | |
class Aquarium<T> (val waterSupply: T) // Nullable | |
class Aquarium<T: Any> (val waterSupply: T) // NonNull | |
class Aquarium<T: WaterSupply> (val waterSupply: T) // Extends from specific class (Define bounds) | |
open class WaterSupply(var needProcessed: Boolean) { | |
// Writes outer class name | |
override fun toString(): String { | |
return "${this::class.simpleName}(needProcessed=$needProcessed)" | |
} | |
} | |
class TapWater: WaterSupply(true) { | |
fun addChemicalCleaners() { | |
needProcessed = false | |
} | |
} | |
class FishStoreWater: WaterSupply(false) | |
class LakeWater: WaterSupply(true) { | |
fun filter() { | |
needProcessed = false | |
} | |
} | |
class Aquarium<T: WaterSupply> (val waterSupply: T) { | |
fun addWater() { | |
check(!waterSupply.needProcessed) { "water supply needs processed" } // throws error (IllegalStateException) if condition doesn't match | |
println("adding water from $waterSupply") | |
} | |
} | |
fun genericExample() { | |
// val aquarium = Aquarium<TapWater>(TapWater()) | |
val aquarium = Aquarium(TapWater()) // Kotlin automatically detects class type | |
aquarium.waterSupply.addChemicalCleaners() | |
val lakeAquarium = Aquarium(LakeWater()) | |
lakeAquarium.waterSupply.filter() | |
lakeAquarium.addWater() // it will be throws exception unless we didn't call filter method | |
} | |
/** | |
* IN and OUT keywords for Generic Classes | |
* For ‘in' generic, we could assign a class of super-type to class of subtype | |
* For 'out' generic, we could assign a class of subtype to class of super-type | |
* | |
* In Kotlin List class uses out keyword | |
* The out keyword says that methods in a List can only return type E and they cannot take any E types as an argument. | |
* This limitation allows us to make List "covariant" | |
* | |
* In Kotlin Compare class uses in keyword | |
* This means that all methods in Compare can have T as an argument but cannot return T type. | |
* This makes Compare "contravariant" | |
*/ | |
interface Production<out T> { | |
fun produce(): T | |
} | |
interface Consumer<in T> { | |
fun consume(item: T) | |
} | |
open class Food() | |
open class FastFood(): Food() | |
class Burger(): FastFood() | |
class FoodStore: Production<Food> { | |
override fun produce(): Food { | |
println("Produce food") | |
return Food() | |
} | |
} | |
class FastFoodStore: Production<FastFood> { | |
override fun produce(): FastFood { | |
println("Produce fastFood") | |
return FastFood() | |
} | |
} | |
class BurgerStore: Production<Burger> { | |
override fun produce(): Burger { | |
println("Produce Burger") | |
return Burger() | |
} | |
} | |
class Everybody: Consumer<Food> { | |
override fun consume(item: Food) { | |
println("Eat food") | |
} | |
} | |
class ModernPeople: Consumer<FastFood> { | |
override fun consume(item: FastFood) { | |
println("Eat FastFood") | |
} | |
} | |
class American: Consumer<Burger> { | |
override fun consume(item: Burger) { | |
println("Eat Burger") | |
} | |
} | |
val production1: Production<Food> = FoodStore() | |
val production2: Production<Food> = FastFoodStore() // Inorder to make this assignment, Production<out T> class must use out | |
val production3: Production<Food> = BurgerStore() // Inorder to make this assignment, Production<out T> class must use out | |
production1.produce() // Output : Produce food | |
production2.produce() // Output : Produce FastFood | |
production3.produce() // Output : Produce Burger | |
val consumer1: Consumer<Burger> = Everybody() | |
val consumer2: Consumer<Burger> = ModernPeople() | |
val consumer3: Consumer<Burger> = American() | |
consumer1.consume(Burger()) // Output : Eat food | |
consumer2.consume(Burger()) // Output : Eat FastFood | |
consumer3.consume(Burger()) // Output : Eat Burger | |
/** | |
* Generic Functions | |
*/ | |
fun <T: WaterSupply> isWaterClean(aquarium: Aquarium<T>) { | |
println("aquarium water is clean: ${!aquarium.waterSupply.needProcessed}") | |
} | |
isWaterClean<TapWater>(aquarium) | |
isWaterClean(lakeAquarium) // we dont need to define class type explicitly, as it's already shown in the argument | |
/** | |
* Inline reified keywords | |
* Non reified types are only available at compile time but can't be used at runtime by your program | |
* If we want to use generic type as real type we need to use reified keyword (we can check if any class is my generic type class) | |
* All generics types are only used at compile time by Kotlin | |
* However at runtime, all the generic types are erased | |
*/ | |
fun <R: WaterSupply> hasWaterSupplyOfType() = waterSupply is R // Gives error | |
inline fun <reified R: WaterSupply> hasWaterSupplyOfType() = waterSupply is R // Correct | |
// We can use reified types for regular functions, even extension functions | |
inline fun <reified T: WaterSupply> WaterSupply.isOfType(): Boolean { | |
println("Type of T is ${T::class.simpleName}") | |
return this is T | |
} | |
aquarium.waterSupply.isOfType<LakeWater>() // false - since aquarium is type of TapWater | |
// Asterisk (*) means any class no matter what generic type is | |
inline fun <reified R: WaterSupply> Aquarium<*>.hasWaterSupplyOfTypeExtension() = waterSupply is R | |
aquarium.hasWaterSupplyOfTypeExtension<FishStoreWater>() // false | |
/** | |
* REFERENCES | |
* https://www.udacity.com/course/kotlin-bootcamp-for-programmers--ud9011 | |
*/ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Example of making custom class iterable | |
*/ | |
data class MyDate(val year: Int, val month: Int, val dayOfMonth: Int) : Comparable<MyDate> { | |
override fun compareTo(other: MyDate): Int { | |
if (year != other.year) return year - other.year | |
if (month != other.month) return month - other.month | |
return dayOfMonth - other.dayOfMonth | |
} | |
} | |
operator fun MyDate.rangeTo(other: MyDate) = DateRange(this, other) | |
import java.util.Calendar | |
/* | |
* Returns the following date after the given one. | |
* For example, for Dec 31, 2019 the date Jan 1, 2020 is returned. | |
*/ | |
fun MyDate.followingDate(): MyDate { | |
val c = Calendar.getInstance() | |
c.set(year, month, dayOfMonth) | |
val millisecondsInADay = 24 * 60 * 60 * 1000L | |
val timeInMillis = c.timeInMillis + millisecondsInADay | |
val result = Calendar.getInstance() | |
result.timeInMillis = timeInMillis | |
return MyDate(result.get(Calendar.YEAR), result.get(Calendar.MONTH), result.get(Calendar.DATE)) | |
} | |
import java.util.* | |
import java.util.function.Consumer | |
class DateRange(val start: MyDate, val end: MyDate): Iterable<MyDate> { | |
override fun forEach(action: Consumer<in MyDate>?) { | |
super.forEach(action) | |
} | |
override fun iterator(): Iterator<MyDate> { | |
return object : Iterator<MyDate> { | |
var current: MyDate = start | |
override fun next(): MyDate { | |
if (!hasNext()) throw NoSuchElementException() | |
val result = current | |
current = current.followingDate() | |
return result | |
} | |
override fun hasNext(): Boolean = current <= end | |
} | |
} | |
override fun spliterator(): Spliterator<MyDate> { | |
return super.spliterator() | |
} | |
} | |
fun iterateOverDateRange(firstDate: MyDate, secondDate: MyDate, handler: (MyDate) -> Unit) { | |
for (date in firstDate..secondDate) { | |
handler(date) | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* ———————————————————————————————————————— | |
* List - MutableList | |
* List is read-only (immutable), you cannot add or update items in the original list. | |
* MutableList inherites List and supports read/write access, you can add, update or remove items. | |
* List & MutableList are ordered collections in which the insertion order of the items is maintained. | |
* List & MutableList allows duplicates and null values | |
* ———————————————————————————————————————— | |
*/ | |
// Initialization | |
val myList: List<String> = listOf("ali", "veli"); | |
val myList = mutableListOf("ali", "ahmet", "mehmet") | |
val myList = (0..1000).toMutableList() | |
val anyList = listOf("test", 2019, null) | |
val anyList = listOfNotNull("test", 2019, null) | |
val intList = List(5) { i -> i } | |
//intList = [0, 1, 2, 3, 4] | |
val intListReversed = MutableList(5) { i -> 4 - i } | |
//intListReversed = [4, 3, 2, 1, 0] | |
// Remove item | |
myList.removeAt(0) | |
//res12: kotlin.String = ahmet | |
// Add Item to List | |
// List doesn’t accept us to add items. So we can only do this with MutableList. | |
// Add item to list using add() method. | |
myList.add("newItem"); | |
// Insert item into the list at the specified index. // starts at 0 | |
myList.add(1, "otherItem") | |
// Add item to list using plus (+) operator. | |
myList += "kotlin" | |
// Add whole list to antoher list with 'addAll' method | |
val anotherList = listOf("item1", "item2") | |
myList.addAll(2, anotherList) // Add new list to specific index | |
// Combine multiple lists | |
val combinedlist1 = list1.plus(list2).plus(list3) | |
val combinedlist2 = list1 + list2 + list3 | |
// Access items from List | |
myList.isNullOrEmpty() | |
myList[2] | |
myList.get(2) | |
myList.getOrElse(6, { -1 }) // -1 | |
myList.getOrNull(6) // null | |
// Get sublist - These methods dont change original list, instead they create another list | |
myList.subList(2, 5) // [2, 3, 4] | |
myList.slice(2..5) // [2, 3, 4, 5] | |
// Take until from list | |
val nums = listOf(0, 1, 5, 9, 4, 2, 6, 7, 8) | |
nums.take(3) // [0, 1, 5] | |
nums.takeWhile { it < 5 } // [0, 1] | |
nums.takeLast(3) // [6, 7, 8] | |
nums.takeLastWhile { it != 4 } // [2, 6, 7, 8] | |
// Drop (Opposite of take) | |
val nums = listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9) | |
// Other methods are also same (dropWhile, dropLast, dropLastWhile) | |
nums.drop(3) // [3, 4, 5, 6, 7, 8] | |
// Update/Replace item in List | |
val list = mutableListOf<Any?>(0,1,2,3) | |
list.set(3, "three") // [0, 1, 2, three] | |
list[0] = "zero" // [zero, 1, 2, three] | |
list.replaceAll { "$it item" } // [0 item, 1 item, 2 item, 3 item] | |
list.replaceAll { e -> e.toString().capitalize() + ".com" } | |
// [0 item.com, 1 item.com, 2 item.com, 3 item.com] | |
list.fill(null) // [null, null, null, null] | |
// Remove Item With Condition | |
myList.removeIf { item -> item % 2 == 0 } | |
myList.removeIf { item in (0..100).toList() } | |
anyList.removeIf { item -> item is Int } | |
// Predicate creation | |
val condition: (String) -> Boolean = { text -> text.startsWith("P") } | |
val condition2: (String) -> Boolean = { it.startsWith("P") } | |
myList.removeIf(condition) | |
// Iterate over List | |
val numbers = listOf(0, 1, 2, 3, 4, 5) | |
// forEach() method. | |
numbers.forEach { i -> print(">$i ") } | |
// Simple for loop | |
for (number in numbers) { | |
print(">$number ") | |
} | |
// ListIterator and a while loop. | |
val listIterator = numbers.listIterator() | |
while (listIterator.hasNext()) { | |
val i = listIterator.next() | |
print(">$i ") | |
} | |
var prevItem = list.getOrElse(0, {0}); | |
list.forEach { item -> | |
println("prevItem : $prevItem - item : $item\n") | |
if(item < prevItem) {list.remove(item)} | |
prevItem = item | |
} | |
// Delete items in loop | |
val list = mutableListOf(0,1,4,87, 33, 45, 48, 51, 23, 20, 10, 15, 13, 17, 100, 1000, 70) | |
var prevItem = list.getOrElse(0, {0}); | |
val listIterator = list.listIterator() | |
while(listIterator.hasNext()) { | |
val index = listIterator.nextIndex() | |
val item = listIterator.next() | |
println("prevItem : $prevItem - item : $item\n") | |
if(item < prevItem) { | |
println("item deleted : $item\n") | |
listIterator.remove() | |
} | |
prevItem = item | |
} | |
// Alternate of remove items for simple cases | |
list.removeIf { it % 10 == 0 } | |
// For loop with item index | |
val startIndex = 1 | |
for (i in startIndex until numbers.size) { | |
print(">${numbers[i]} ") | |
} | |
val list = mutableListOf(0,1,4,87, 33, 45, 48, 51, 23, 20, 10, 15, 13, 17, 100, 1000, 70) | |
for((index, item) in list.withIndex()) { | |
print("Index : $index - Item : $item\n") | |
} | |
// Reverse List | |
// Get a new List with revered order using reversed() method. | |
val numbers = mutableListOf(0, 1, 2, 3) | |
val reversedNumbers = numbers.reversed() | |
// reversedNumbers: [3, 2, 1, 0] | |
numbers[3] = 5 | |
// numbers: [0, 1, 2, 5] | |
// reversedNumbers: [3, 2, 1, 0] - this doesnt change | |
// Get a reversed read-only list of the original List as a reference using asReversed() method. | |
// If we update item in original list, the reversed list will be changed too. | |
val numbers = mutableListOf(0, 1, 2, 3) | |
val refReversedNumbers = numbers.asReversed() | |
// refReversedNumbers: [3, 2, 1, 0] | |
numbers[1] = 8 | |
// numbers: [0, 8, 2, 3] | |
// refReversedNumbers: [3, 2, 8, 0] - this changes too | |
// Filter List | |
data class Animal (val name:String) | |
val animals = listOf(Animal("Lion"), Animal("Elephant"), Animal("Bug")) | |
// animals = [Animal(name=Lion), Animal(name=Elephant), Animal(name=Bug)] | |
val animalsWithN = animals.filter { it.name.contains("n") } | |
// animalsWithN = [Animal(name=Lion), Animal(name=Elephant)] | |
// Check items in list | |
val list = | |
listOf("bezkoder", 2019, "kotlin", "tutorial", "bezkoder.com", 2019) | |
list.contains("bezkoder") // true | |
list.contains("zkoder") // false | |
"bezkoder" in list // true | |
list.containsAll(listOf("bezkoder", "tutorial")) // true | |
list.containsAll(listOf("zkoder", "tutorial")) // false | |
list.indexOf(2019) // 1 | |
list.lastIndexOf(2019) // 5 | |
list.indexOf(2020) // -1 | |
list.lastIndexOf(2020) // -1 | |
list.indexOfFirst { e -> e.toString().startsWith("bez") } // 0 | |
list.indexOfFirst { e -> e.toString().startsWith("zkoder") } // -1 | |
list.indexOfLast { e -> e.toString().endsWith("9") } // 5 | |
list.indexOfLast { e -> e.toString().endsWith(".net") } // -1 | |
list.find { e -> e.toString().startsWith("bez") } // bezkoder | |
list.find { e -> e.toString().startsWith("zkoder") } // null | |
list.findLast { e -> e.toString().endsWith("9") } // 2019 | |
list.findLast { e -> e.toString().endsWith(".net") } // null | |
// Sort List | |
// Sort() method to is used for sort a Mutable List in-place, and sortDescending() for descending order. | |
nums.sort() | |
nums.sortDescending(); | |
// Sorted() and sortedDescending() don’t change the original List. Instead, they return another sorted List. | |
val sortedNums = nums.sorted() | |
val sortedNumsDescending = nums.sortedDescending() | |
// Sort by custom filter | |
data class MyDate (val month:Int, val day:Int) | |
val myDates = mutableListOf( | |
MyDate(4, 3), | |
MyDate(5, 16), | |
MyDate(1, 29) | |
) | |
myDates.sortBy { it.month } | |
myDates.sortByDescending { it.month } // descending version | |
// Sorted by is equal to sorted -with filter. | |
// don’t change the original List. Instead, they return another sorted List. | |
val sortedDates = myDates.sortedBy { it.month } | |
// Sort with complex custom filters | |
val myDates = mutableListOf( | |
MyDate(8, 19), | |
MyDate(5, 16), | |
MyDate(1, 29), | |
MyDate(5, 10), | |
MyDate(8, 3) | |
) | |
myDates.sortWith(compareBy { it.month }) | |
// Another sort example | |
val monthComparator = compareBy<MyDate> { it.month } | |
val dayThenMonthComparator = monthComparator.thenBy { it.day } | |
myDates.sortWith(dayThenMonthComparator) | |
// A complex example | |
class MyDateComparator { | |
companion object : Comparator<MyDate> { | |
override fun compare(o1: MyDate, o2: MyDate): Int = when { | |
o1.month != o2.month -> o1.month - o2.month | |
o1.day != o2.day -> o1.day - o2.day | |
else -> 0 | |
} | |
} | |
} | |
myDates.sortWith(MyDateComparator) | |
// SOME USEFUL LIST METHODS | |
// joinToString | |
println(numbers.joinToString(prefix = "<", postfix = ">", separator = "•")) // <1•2•3•4•5•6> | |
val chars = charArrayOf('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q') | |
println(chars.joinToString(limit = 5, truncated = "...!") { it.toUpperCase().toString() }) // A, B, C, D, E, ...! | |
val max = nums.max() | |
val min = nums.min() | |
val sum = nums.sum() | |
val avg = nums.average() | |
val cars = listOf(Car("Mazda", 6300), Car("Toyota", 12400), | |
Car("Skoda", 5670), Car("Mercedes", 18600)) | |
val car = cars.maxBy { car -> car.price } | |
// Kotlin List map | |
// The mapping operation returns a modified list by applying a transform function on each element of the list. | |
val nums2 = nums.map { e -> e * 2 } // every item multipled by 2 in the nums list as new list | |
// Kotlin List reduction | |
// Reduction is a terminal operation that aggregates list values into a single value. | |
// The reduce() method applies a function against an accumulator and each element | |
// (from left to right) to reduce it to a single value. | |
val nums = listOf(4, 5, 3, 2, 1, 7, 6, 8, 9) | |
val sum = nums.reduce { total, next -> total + next } | |
println(sum) | |
// reduceRight is the same logic but iterate on reversed list | |
// Kotlin List group by | |
// The groupBy() method groups elements of the original list by the key returned by the given selector function, | |
// applied to each element. It returns a map where each group key is associated with a list of corresponding elements. | |
val words = listOf("as", "pen", "cup", "doll", "my", "dog", "spectacles") | |
val res = nums.groupBy { if (it % 2 == 0) "even" else "odd" } | |
println(res) | |
// Result : {odd=[1, 3, 5, 7], even=[2, 4, 6, 8]} | |
val res2 = words.groupBy { it.length } | |
println(res2) | |
// Result : {2=[as, my], 3=[pen, cup, dog], 4=[doll], 10=[spectacles]} | |
// Kotlin List any | |
// The any() method returns true if at least one element matches the given predicate function. | |
val nums = listOf(4, 5, 3, 2, -1, 7, 6, 8, 9) | |
val r = nums.any { e -> e > 10 } // r is false | |
// Kotlin List all | |
// The all() returns true if all elements satisfy the given predicate function. | |
val nums = listOf(4, 5, 3, 2, -1, 7, 6, 8, 9) | |
val r = nums.all { e -> e > 0 } // r is false | |
// Some examples | |
val spices = listOf("curry", "pepper", "cayenne", "ginger", "red curry", "green curry", "red pepper" ) | |
val newSpices = spices.withIndex().filter { (index,item) -> index < 3 && item.contains('c') }.map { it.value } | |
val newSpices2 = spices.take(3).filter { item -> item.contains('c') } | |
println(newSpices) | |
/** | |
* Pairs (key-value) | |
*/ | |
val equipment = "fishnet" to "catching" // Simple Pair definition | |
val equipment = "fishnet" to 12 to "bruce" to "lee" to true // Pair chain | |
equipment.first.first.second // Output: bruce | |
equipment.first // Output: (((fishnet, 12), bruce), lee) | |
val equipment = ("fishnet" to 12) to ("bruce" to "lee") // We can use paranthesis for pairs | |
equipment.second.first // Output: bruce | |
// We can use pairs to return multiple variables on functions. | |
// And we can use them as destructure the pair into variables | |
fun giveMeATool(): Pair<String, String> { | |
return ("fishnet" to "cactching fish") | |
} | |
val(tool, use) = giveMeATool() | |
println("Tool $tool usage is $use") // Output: Tool fishnet usage is cactching fish | |
// Pair could be convert to list with toList() method | |
equipment.toList() // Output : [fishnet, catching] | |
/** | |
* Triple | |
* is just like pair but with three variables | |
*/ | |
val (a, b, c) = Triple(2, "x", listOf(null)) | |
/** | |
* MAPS | |
* Map is list of pairs | |
*/ | |
val cures: MutableMap<String, String> = mutableMapOf("white spots" to "Ich", "red sores" to "hole diseases") | |
cures.putIfAbsent("white spots", "whites"); // It will not be added since the key already exist in map | |
cures.getOrDefault("purple spots", "Nope") // Output: Nope | |
println(cures.getOrPut("purple spots") { | |
println("new value added for purple spots") | |
val myVal = "Purples" | |
myVal | |
}) | |
// Output : | |
// new value added for purple spots | |
// Purples | |
/** | |
* SET | |
* Set<T> stores unique elements; their order is generally undefined. | |
* null elements are unique as well: a Set can contain only one null. | |
* Two sets are equal if they have the same size, and for each element of a set there is an equal element in the other set. | |
* The default implementation of Set – LinkedHashSet – preserves the order of elements insertion | |
*/ | |
val numbers = setOf(1, 2, 3, 4) | |
println("Number of elements: ${numbers.size}") // 4 | |
if (numbers.contains(1)) println("1 is in the set") // true | |
val numbersBackwards = setOf(4, 3, 2, 1) | |
println("The sets are equal: ${numbers == numbersBackwards}") // true (order is not important) | |
// Example of SET and MAP | |
val allBooks = setOf("Macbeth", "Romeo and Juliet", "Hamlet", "A Midsummer Night's Dream") | |
val library = mapOf("Shakespeare" to allBooks) | |
println(library) // Output : {Shakespeare=[Macbeth, Romeo and Juliet, Hamlet, A Midsummer Night's Dream]} | |
println(library["Shakespeare"]?.elementAt(2)) // Output : Hamlet | |
// Return true if all customers are from a given city | |
fun Shop.checkAllCustomersAreFrom(city: City): Boolean = | |
customers.all { it.city == city } | |
// Return true if there is at least one customer from a given city | |
fun Shop.hasCustomerFrom(city: City): Boolean = | |
customers.any { it.city == city } | |
// Return the number of customers from a given city | |
fun Shop.countCustomersFrom(city: City): Int = | |
customers.count { it.city == city } | |
// Return a customer who lives in a given city, or null if there is none | |
fun Shop.findCustomerFrom(city: City): Customer? = | |
customers.find { it.city == city } | |
/** | |
* "FlatMap" example | |
*/ | |
data class Shop(val name: String, val customers: List<Customer>) | |
data class Customer(val name: String, val city: City, val orders: List<Order>) { | |
override fun toString() = "$name from ${city.name}" | |
} | |
data class Order(val products: List<Product>, val isDelivered: Boolean) | |
data class Product(val name: String, val price: Double) { | |
override fun toString() = "'$name' for $price" | |
} | |
data class City(val name: String) { | |
override fun toString() = name | |
} | |
// Return the most expensive product that has been ordered by the given customer | |
fun getMostExpensiveProductBy(customer: Customer): Product? = | |
customer.orders | |
.flatMap{ | |
it.products | |
}.maxBy{ | |
it.price | |
} | |
// Same method, different representation | |
fun getMostExpensiveProductBy(customer: Customer): Product? = | |
customer.orders | |
.flatMap(Order::products) | |
.maxBy(Product::price) | |
// Another example | |
// Return all products the given customer has ordered | |
fun Customer.getOrderedProducts(): List<Product> = | |
orders.flatMap(Order::products) | |
// Return all products that were ordered by at least one customer | |
fun Shop.getOrderedProducts(): Set<Product> = | |
// customers.flatMap(Customer::orders).flatMap(Order::products).toSet() | |
// Another representation | |
customers.flatMap(Customer::getOrderedProducts).toSet() | |
/** | |
* associateBy | |
* Returns a Map containing the elements from the given sequence indexed by the key | |
* returned from keySelector function applied to each element. | |
*/ | |
data class Person(val firstName: String, val lastName: String) { | |
override fun toString(): String = "$firstName $lastName" | |
} | |
val scientists = listOf(Person("Grace", "Hopper"), Person("Jacob", "Bernoulli"), Person("Johann", "Bernoulli")) | |
val byLastName = scientists.associateBy { it.lastName } | |
// Jacob Bernoulli does not occur in the map because only the last pair with the same key gets added | |
println(byLastName) // {Hopper=Grace Hopper, Bernoulli=Johann Bernoulli} | |
val byLastName2 = scientists.associateBy({ it.lastName }, { it.firstName }) | |
// Jacob Bernoulli does not occur in the map because only the last pair with the same key gets added | |
println(byLastName2) // {Hopper=Grace, Bernoulli=Johann} | |
/** | |
* Partition | |
*/ | |
val numbers = listOf("one", "two", "three", "four") | |
val (match, rest) = numbers.partition { it.length > 3 } | |
// Return customers who have more undelivered orders than delivered | |
fun Shop.getCustomersWithMoreUndeliveredOrders(): Set<Customer> = customers.filter { | |
val (delivered, undelivered) = it.orders.partition { order -> order.isDelivered } | |
undelivered.size > delivered.size | |
}.toSet() | |
/** | |
* FOLD | |
* This function helps to accumulate value starting with initial value, | |
* then apply operation from left to right to current accumulator value and each element. | |
* foldRight() -> right to letf | |
*/ | |
println(listOf(1, 2, 3, 4, 5).fold(1) { mul, item -> mul * item }) // (Initial)1 * 1*2*3*4*5 => 120 | |
println(listOf(1, 2, 3, 4, 5).fold(3) { mul, item -> mul * item }) // 3 * 1*2*3*4*5 => 360 | |
println(listOf(71, 79, 67, 68, 64).fold("Tadaaa:") { char, item -> char + item.toChar() }) // Output: Tadaaa:GOCD@ | |
// Another example for fold | |
fun Customer.getOrderedProducts(): List<Product> = | |
orders.flatMap(Order::products) | |
/** | |
* Intersect | |
* Intersect gets same values on two lists | |
*/ | |
// Return the set of products that were ordered by all customers | |
fun Shop.getProductsOrderedByAll(): Set<Product> { | |
val allOrderedProducts = customers.flatMap { it.getOrderedProducts() }.toSet() | |
// Gets allOrderedProducst as initial and crop its value by the customer order values | |
return customers.fold(allOrderedProducts, { orderedByAll, customer -> | |
orderedByAll.intersect(customer.getOrderedProducts()) | |
}) | |
} | |
fun main(args: Array<String>) { | |
val item1 = Product("Köfte", 12.3) | |
val item2 = Product("Pattes", 22.3) | |
val item3 = Product("Dolma", 32.3) | |
val item4 = Product("Antrikot", 2.3) | |
val item5 = Product("Bezelye", 12.7) | |
val customer1 = Customer("Ali",City("Istanbul"), listOf(Order(listOf(item1, item3), true), Order(listOf(item1, item5), false))) | |
val customer2 = Customer("Ayse",City("Ankara"), listOf(Order(listOf(item1, item2, item3), true))) | |
val customer3 = Customer("Veli",City("Bolu"), listOf(Order(listOf(item5), true), Order(listOf(item1, item5), false))) | |
val shop = Shop("lokanta", listOf(customer1, customer2, customer3)) | |
println(shop.getProductsOrderedByAll()) // Output: ['Köfte' for 12.3] | |
} | |
// Count the amount of times a product was ordered. | |
// Note that a customer may order the same product several times. | |
fun Shop.getNumberOfTimesProductWasOrdered(product: Product): Int { | |
return customers.flatMap(Customer::getOrderedProducts).count { it == product } | |
// Long way | |
// return customers.flatMap(Customer::getOrderedProducts).groupBy { it.name }.getValue(product.name).size | |
} | |
/** | |
* distinct() | |
* Returns a sequence containing only distinct elements from the given sequence. | |
*/ | |
val list = listOf('a', 'A', 'b', 'B', 'A', 'a') | |
// just like distinct we can use toSet() method either | |
println(list.distinct()) // [a, A, b, B] | |
println(list.distinctBy { it.toUpperCase() }) // [a, b] | |
/** | |
* REFERENCES | |
* https://www.udacity.com/course/kotlin-bootcamp-for-programmers--ud9011 | |
* https://kotlinlang.org/docs/reference/collections-overview.html | |
* https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-triple/ | |
* https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.sequences/associate-by.html | |
* https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/join-to-string.html | |
* https://bezkoder.com/kotlin-list-mutable-list/ | |
* https://bezkoder.com/kotlin-fold/ | |
* https://dzone.com/articles/learning-kotlin-return-when | |
* http://zetcode.com/kotlin/lists/ | |
*/ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* LOOPS | |
*/ | |
// Simple For Loops | |
for(i in 'b'..'h') print(i) //bcdefgh | |
for(i in 1..5) print(i) //12345 | |
for(i in 5 downTo 1) print(i) //54321 | |
for(i in 1..16 step 3) print(i) //147101316 | |
// Continue and Break | |
outer@ for (n in 2..100) { | |
for (d in 2 until n) { | |
if (n % d == 0) continue@outer | |
} | |
println("$n is prime\n") | |
} | |
// Usage of WHEN statement | |
// We can avoid the next evolution by creating a variable and returning it directly: | |
fun step2(number: Int):String { | |
when (number) { | |
0 -> return "Zero" | |
1 -> return "One" | |
2 -> return "Two" | |
} | |
return "" | |
} | |
// And, now, we get to the cool part — we can just return the when ! | |
fun step3(number: Int):String { | |
return when (number) { | |
0 -> "Zero" | |
1 -> "One" | |
2 -> "Two" | |
else -> "" | |
} | |
} | |
// REPEAT (continue and break keyword are unusable inside repeat function) | |
repeat(3) { // number of repeat count | |
val randomDay = Random(4).nextInt() | |
println(randomDay.toString()) | |
} | |
// DO WHILE | |
do { | |
val fortune = getFortuneCookie(getBirthday()) | |
println(fortune) | |
} while (!fortune.contains("Take it easy")) | |
/** | |
* REFERENCES | |
* https://www.udacity.com/course/kotlin-bootcamp-for-programmers--ud9011 | |
* https://kotlinlang.org/docs/reference/control-flow.html | |
* https://dzone.com/articles/learning-kotlin-return-when | |
*/ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import kotlin.math.absoluteValue | |
enum class Directions(val shortName: String? = null) { | |
NORTH("n"), | |
SOUTH("s"), | |
EAST("e"), | |
WEST("w"), | |
START, | |
END; | |
} | |
class Location (val width: Int = 4, val height: Int = 4) { | |
private val map = Array(width) { arrayOfNulls<String>(height) } | |
var currentLocation = Pair (0,0) | |
fun updateLocation(direction: Directions) { | |
when (direction) { | |
Directions.NORTH -> { | |
currentLocation = Pair(currentLocation.first, (currentLocation.second + 1).rem(height)) | |
} | |
Directions.SOUTH -> { | |
currentLocation = Pair(currentLocation.first, (currentLocation.second - 1).absoluteValue.rem(height)) | |
} | |
Directions.EAST -> { | |
currentLocation = Pair((currentLocation.first + 1).rem(width), currentLocation.second) | |
} | |
Directions.WEST -> { | |
currentLocation = Pair((currentLocation.first - 1).absoluteValue.rem(width), currentLocation.second) | |
} | |
} | |
} | |
fun getDescription ():String? { | |
return map[currentLocation.first.rem(width)][currentLocation.second.rem(height)] | |
} | |
init { | |
map[0][0] = "You are at the start of your journey. [n / e]" | |
map[0][1] = "The road stretches before you. It promises beauty and adventure. [ n / s / e]" | |
map[0][2] = "The road still stretches before you. It rains and the water forms puddles. [ n / s / e]" | |
map[0][3] = "It is getting much colder and you wish you had a wool coat. [ s / e]" | |
map[1][0] = "The narrow path stretches before you. You are glad you are on foot. [ n / e /w]" | |
map[1][1] = "It is getting warmer. You smell moss, and marmot scat. You are stung by a mosquito. [ n / s / e / w]" | |
map[1][2] = "You decide to sit on your backside and slide down a vast snowfield. [ n / s / e /w]" | |
map[1][3] = "You have climbed to the top of a snowy mountain and are enjoying the view. [ w / s / e]" | |
map[2][0] = "You cross an old stone bridge. Your hear the murmuring of water. And another grumbling sound. [ n / e / w]" | |
map[2][1] = "A bridge troll appears. It swings a club and demands gold. You give them all your coins. [ n / s / e /w]" | |
map[2][2] = "It is getting cooler. A dense fog rolls in. You can hear strange voices. [ n / s / e / w]" | |
map[2][3] = "The foothills promise a strenuous journey. You thread your way around gigantic boulders. [ s / e / w ]" | |
map[3][0] = "Your journey continues. A fox runs across the path with a chicken in its mouth.[ n / e ]" | |
map[3][1] = "In the distance, you see a house. You almost bump into a farmer with a large shotgun. They pay you no heed. [ n / s / w ]" | |
map[3][2] = "It is getting hot, and dry, and very, very quiet. You think of water and wish you had brought a canteen.[ n / s / w ]" | |
map[3][3] = "You have reached the infinite desert. There is nothing here but sand dunes. You are bitten by a sand flea.[ s / w ] " | |
} | |
} | |
class Game { | |
private var path: MutableList<Directions> = mutableListOf(Directions.START) | |
val map = Location() | |
private val north = { | |
path.add(Directions.NORTH) | |
true | |
} | |
private val south = { | |
path.add(Directions.SOUTH) | |
true | |
} | |
private val east = { | |
path.add(Directions.EAST) | |
true | |
} | |
private val west = { | |
path.add(Directions.WEST) | |
true | |
} | |
private val end: () -> Boolean = { | |
path.add(Directions.END) | |
println("Game Over\n ${path.joinToString(separator = " - ")}") | |
path.clear() | |
false | |
} | |
private fun move(where: () -> Boolean): Boolean { | |
return where() | |
} | |
fun makeMove(argument: String?): Boolean { | |
/* | |
if(Directions.values().toList().map { it.shortName }.contains(argument)) {} | |
*/ | |
return when(argument) { | |
Directions.WEST.shortName -> { | |
map.updateLocation(Directions.WEST) | |
move(west) | |
} | |
Directions.EAST.shortName -> { | |
map.updateLocation(Directions.EAST) | |
move(east) | |
} | |
Directions.NORTH.shortName -> { | |
map.updateLocation(Directions.NORTH) | |
move(north) | |
} | |
Directions.SOUTH.shortName -> { | |
map.updateLocation(Directions.SOUTH) | |
move(south) | |
} | |
else -> move(end) | |
} | |
} | |
} | |
fun main(args: Array<String>) { | |
val game = Game() | |
while (true) { | |
print("Enter a direction: n/s/e/w:") | |
if(!game.makeMove(readLine())) { | |
break | |
} else { | |
println(game.map.getDescription()) | |
} | |
} | |
} | |
/** | |
* REFERENCES | |
* https://www.udacity.com/course/kotlin-bootcamp-for-programmers--ud9011 | |
*/ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment