Platform: macOS Mojave, Windows 10
This notebook uses JetBrain's Kotlin kernel for IPython/Jupyter, which requires Java 8 to be installed
Created a new conda environment and installed the following:
conda create -n kotlin-jupyter
conda activate kotlin-jupyter
conda install -c conda-forge python=3.7
conda install -c jetbrains kotlin-jupyter-kernel
conda install openjdk=8
conda install -c conda-forge jupyter
P.S. On macOS Mojave, also installed Apache Maven and Gradle for a different purpose (i.e., not needed to run this notebook)
Apache Maven and Gradle are both dependency management and Build Automation Tools, primarily used for Java applications. Maven build files are written in XML (called pom.xml) whereas Gradle doesn't use XML (Gradle's configuration file is called build.gradle in Groovy, or build.gradle.kts in Kotlin). Comparison between Maven and Gradle is discussed here
conda install -c conda-forge maven
conda install -c conda-forge gradle
var language: String= "Kotlin"
val month: String = "December"
val year: Int = 2020
println("Hello World!")
println("Exploring $language in $month, $year")
Hello World! Exploring Kotlin in December, 2020
val first = "Kevin"
val last = " Durant"
val full = first + last //Concatenation: works only when the types being concatenated are the same
println()
println("String concatenation")
println(full)
String concatenation Kevin Durant
Variables in Kotlin can't be assigned a null value by default. That means, no more NullPointerException
errors as in Java. So, can't do this in Kotlin:
var regular: String = null
//Nullable types (type followed by a ? mark) allow a special null value to the variable
var nullstr: String? = null // These are nullable variables
var nullint: Int? = null
println(nullstr)
println(nullint)
null null
import kotlin.random.Random
val random = Random.nextInt(50) + 1
when (random){
in 1..10 -> println("Range of 1 to 10 ") //the ... -> ... is a lambda function
in 11..20 -> println("Range of 11 to 20")
in 21..30 -> println("Range of 21 to 30")
in 31..40 -> println("Range of 31 to 40")
else -> println("Range of 41 to 50")
}
println("Random value: $random")
Range of 1 to 10 Random value: 10
val section = 2
when (section) {
1-> println("Yes, you are registered for this section. Do as you wish!")
2-> {
println("Sorry, you aren't registered in section 1")
println("However, you can audit the course material")
println("You can watch lecture videos too!")
}
else -> println("Let me check with the course instructor. Be right back!")
}
Sorry, you aren't registered in section 1 However, you can audit the course material You can watch lecture videos too!
val age = 21
if (age < 18) {
println("Wait till you are 18 to vote")
} else if (age <=21) {
println("You are eligible to vote. Can't buy alcohol yet though")
} else {
println("You are eligible to vote and you can legally purchase alcohol too")
}
You are eligible to vote. Can't buy alcohol yet though
//Recall that for loops are useful when you know how many loops you need to run ahead of time
var sum = 0L
for (i in 100L..1000000L) {
sum = sum + i
}
println()
println(sum)
//Note that if you use sum = 0, then it is initialized as an Int (32-bit), the
//max value of which isn't large enough for this sum. So, we need to initialize
//sum as Long (64-bit) explicitly
println()
val array = arrayListOf("Kotlin","Julia","Python")
for (element in array) {
println(element)
}
println()
for ((index, value) in array.withIndex()) {
println ("Element at index $index is $value")
}
500000495050 Kotlin Julia Python Element at index 0 is Kotlin Element at index 1 is Julia Element at index 2 is Python
// While loops are useful when you don't know how many iterations to run ahead of time
// Eg., if you are reading a file line by line until the end of file
// While allows you to use arbitrary conditionals to run the loop
var i = 0
while (i < 5) {
println(i)
i++
}
0 1 2 3 4
// Break operator skips all remaining iterations and takes you to the end
// Break is useful when you are interested in the first occurrence only
// Continue operator allows you to skip over to the next iteration
// Continue is useful to reduce unnecessary computation time by skipping cases that aren't relevant
for (char in "Kotlin") {
print(char)
if (char == 'o') {
break
}
}
println()
val languages = arrayListOf("Python","Julia","Kotlin")
for (str in languages) {
if (!str.contains('o')) {
continue
}
println(str)
}
// So, in this case, it skips over "Julia" since it doesn't contain the letter 'o'
Ko Python Kotlin
// Function with no parameter and no return value
fun Hello() {
println("Hello!")
}
// Function with one parameter and no return value
fun printwithSpaces(text: String) {
for (char in text) {
print(char + " ")
}
}
import java.util.Date //Importing 'Date' Class in Java.Util package
// Function that also returns a value
fun getCurrentDate(): Date {
return Date()
}
// Function that takes two parameters and returns a value
fun getMax(a: Int, b: Int): Int {
if (a >= b) {
return a
} else {
return b
}
}
Hello()
printwithSpaces("HHMI Janelia")
println()
println(getCurrentDate())
println(getMax(12, 17))
Hello! H H M I J a n e l i a Tue Dec 29 08:56:44 EST 2020 17
// Lambdas are nameless functions that can be called wherever needed
// Syntax: val lambdaName : Type = { argumentList -> codeBody }
// codeBody is the only non-optional component
val sumA: (Int, Int) -> Int = { x, y -> x + y}
print("Sum (type declared): ")
println(sumA(2,3))
println()
// In this case, type of 'x + y' is inferred based on the input
val sumB = {x: Int, y: Int -> x + y}
print("Sum (type inferred): ")
println(sumB(2,3))
println()
val square = {x: Int -> x*x}
print("Square: ")
println(square(5))
println()
val calculateGrade = {grade : Double ->
when(grade) {
in 0.00..31.99 -> "Grade: Fail"
in 32.00..79.99 -> "Grade: Pass"
in 80.00..100.00 -> "Grade: Distinction"
else -> "Round grade to two decimal places and try again!"
}
}
println(calculateGrade(82.28))
val array = arrayOf(1, 2, 3)
println()
array.forEach { item -> println(item*item) }
println()
println("Using 'it' short-hand: ")
array.forEach { println(it*it) }
Sum (type declared): 5 Sum (type inferred): 5 Square: 25 Grade: Distinction 1 4 9 Using 'it' short-hand: 1 4 9
In Kotlin, we can use extension function to extend a class with new functionality (no need to create a derived class-- note that in other object-oriented languages, a new functionality is added by deriving a new class from an existing class and then adding in the functionality). So, an extension function is a member function of a class that is defined outside the class.
//Below we are extending 'String' with a new function 'SpecialCharacters'
//that replaces a few special characters (&, <, and >) in the string
fun String.SpecialCharacters() : String {
return this // 'this' corresponds to the string
.replace("&", "and")
.replace("<", "[")
.replace(">", "]")
}
val myString= "<Hello friends & family>"
println(myString)
println(myString.SpecialCharacters())
<Hello friends & family> [Hello friends and family]
fun MutableList<Int>.swap(index1: Int, index2: Int) {
val tmp = this[index1] // 'this' corresponds to the list
this[index1] = this[index2]
this[index2] = tmp
}
val tmp = mutableListOf(1, 2, 3, 4, 5)
println(tmp)
tmp.swap(0, 4) // 'this' inside 'swap()' will hold the value of 'list'
println(tmp)
[1, 2, 3, 4, 5] [5, 2, 3, 4, 1]
//Array
val integers = intArrayOf(1,2,3) //arrayOf<Int>(1,2,3)
println()
println("An array of Integers of size " + integers.size)
for (item in integers) {
println(item)
}
val doubles = doubleArrayOf(1.414,3.141,1.333, 1.0/137) // arrayOf<Double>(1,2,3)
println()
println("An array of Doubles of size " + doubles.size)
for (item in doubles) {
println(item)
}
val mixed = arrayOf("Kotlin", 17, 3.0, true)
println()
println("A mixed Array of size " + mixed.size)
for (item in mixed) {
println(item)
}
An array of Integers of size 3 1 2 3 An array of Doubles of size 4 1.414 3.141 1.333 0.0072992700729927005 A mixed Array of size 4 Kotlin 17 3.0 true
//List
val journey = listOf<String>("Math","Physics","Optics","Bio-imaging","Microscopy","Computing")
println("List of size " + journey.size)
println(journey)
val mixedList = listOf("Python", "Julia", "Kotlin", 'x', 3.1415, true)
println()
println("Mixed List of size " + mixedList.size)
println(mixedList)
List of size 6 [Math, Physics, Optics, Bio-imaging, Microscopy, Computing] Mixed List of size 6 [Python, Julia, Kotlin, x, 3.1415, true]
//ArrayList
val arrayList = arrayListOf<String>("MO","MD","WI","NC")
println()
arrayList.add("VA") //arrayList.remove() to remove an element
arrayList.add(0,"KTM")
println("ArrayList of size " + arrayList.size)
println(arrayList)
print("Empty? ")
println(arrayList.isEmpty())
print("Contains CA? ")
println(arrayList.contains("CA"))
//Sublist:
print("Sublist")
val subList = arrayList.subList(4,arrayList.size) //Note that the end is exclusive
println(subList)
ArrayList of size 6 [KTM, MO, MD, WI, NC, VA] Empty? false Contains CA? false Sublist[NC, VA]
val p = floatArrayOf( 8f , 2f , 2.3f , 0.001f )
println(p)
[F@7a5f5c95
val pval = p.map { x -> x } //List
println(pval)
[8.0, 2.0, 2.3, 0.001]
// Compute mean
val mean = p.average()
// Compute sum of all elements
val sum = p.sum()
// Compute min/max
val max = p.max()
val min = p.min()
println("min: $min, max: $max, sum: $sum, mean: $mean")
min: 0.001, max: 8.0, sum: 12.301001, mean: 3.0752499880909454
// Index of the max element ( useful in classification )
val argmax = p.indexOf( max )
println("Index of max: $argmax")
// Filter elements greater than 2
print("Elements greater than 2: " + p.filter { xi -> xi > 2f })
Index of max: 0 Elements greater than 2: [8.0, 2.3]
// Normalize the values
val normalizedValues = p.map { xi -> ( xi - min ) / max }
println(normalizedValues)
[0.999875, 0.249875, 0.287375, 0.0]
// Use map() successively to normalize multi dim arrays
// An image ( W * H * 3 ) could be an example
val multiDimArray = arrayOf(
arrayOf(
floatArrayOf( 1f , 2f , 3f )
) ,
arrayOf(
floatArrayOf( 4f , 5f , 6f )
),
arrayOf(
floatArrayOf( 9f , 10f , 2f )
)
)
val normalizedMultiDimArray = multiDimArray.map {
column -> column.map {
row -> row.map {
element -> element / 255f
} } }
println(normalizedMultiDimArray)
[[[0.003921569, 0.007843138, 0.011764706]], [[0.015686275, 0.019607844, 0.023529412]], [[0.03529412, 0.039215688, 0.007843138]]]
println(normalizedMultiDimArray[0])
[[0.003921569, 0.007843138, 0.011764706]]
/*
Refresher:
Object: has properties; a member of the Class
Classes: are blueprints to create objects; leaves room for specific variations
Properties: defines attributes of a Class
Methods: defines capabilities and functionalities of a Class
Interface: define a contract that a Class can adhere to
*/
class Person {
//Adding some properties to the Class
var name: String = "Jordan"
var age: Int = 57
//Adding some functionalities to the Class
fun sports() {
println("Basketball")
}
fun greet(name: String) {
print("Welcome " + name + "! ")
println("It's showtime y'all!")
}
/*
fun getBirthYear(): Int {
return 2020 - age
}
*/
fun getBirthYear() = 2020 - age
}
val person = Person()
//person.name = "MJ" // We can change this property since we defined it using 'var' and not 'val'
println("Name: " + person.name)
println("Age: " + person.age)
print("Year of Birth: ")
println(person.getBirthYear())
print("Sports: ")
person.sports()
person.greet("fans")
Name: Jordan Age: 57 Year of Birth: 1963 Sports: Basketball Welcome fans! It's showtime y'all!
class Persons(val name: String, var age: Int) {
//Class with named properties 'name' and 'age'
//Adding some functionalities to the Class
fun sports() {
println("Basketball")
}
fun greet(name: String) {
print("Welcome " + name + "! ")
println("It's showtime y'all!")
}
fun getBirthYear() = 2020 - age
}
val person = Persons("Michael Jordan", 57)
println("Name: " + person.name)
println("Age: " + person.age)
print("Year of Birth: ")
println(person.getBirthYear())
print("Sports: ")
person.sports()
person.greet("everyone")
Name: Michael Jordan Age: 57 Year of Birth: 1963 Sports: Basketball Welcome everyone! It's showtime y'all!
/*
Two of the parameters have default values
and a function called info() to print its info
*/
class People(val name: String,
val university: String = "College",
val degree: String,
val year: Int = 2020) {
fun info(){
println("Name: $name")
println("University: $university")
println("Degree: $degree")
println("Year: $year")
println()
}
}
Overloading the constructor:
/*
Here, we also "overload the constructor"
ie have different constructors for the same class
personA, personB, personC
*/
val personA = People(name = "Rudy", university = "UMBC", degree = "BS", year = 2004)
personA.info()
val personB = People(name = "Jeremy", university = "UNC-CH", degree = "BS", year = 2008)
personB.info()
val personC = People(name="Ava", degree = "MS")
personC.info()
Name: Rudy University: UMBC Degree: BS Year: 2004 Name: Jeremy University: UNC-CH Degree: BS Year: 2008 Name: Ava University: College Degree: MS Year: 2020
/*
Inheritance: inherit properties and functions from a parent class and avoid code duplication
Normal class is defined as "class ... {}"
- Properties and functions from a normal class cannot be inherited
Open class is define as "open class ... {}"
- Child classes can inherit its properties and functions
- Objects for this parent class can also be created
*/
open class Member(open val name: String, open val year: Int = 2020) {
fun info(){
println()
println("Name: $name")
println("Started in: $year")
}
}
class Instructor(override val name: String,
override val year: Int,
val fulltime: Boolean) : Member(name, year) {
fun employeeStatus() {
if (fulltime) {
println("Employee Status: Full-time instructor")
} else {
println("Employee Status: Not a full-time instructor")
}
}
}
class Student(override val name: String,
override val year: Int,
val id: Int) : Member(name, year) {
fun printID() {
println("Student ID: $id")
}
}
val member = Member(name = "Kotlin", year = 2010)
member.info()
val instructor = Instructor(name = "Julia", year = 2001, fulltime = true)
instructor.info()
instructor.employeeStatus()
val student = Student(name = "Jenny", year = 2002, id = 56027)
student.info()
student.printID()
Name: Kotlin Started in: 2010 Name: Julia Started in: 2001 Employee Status: Full-time instructor Name: Jenny Started in: 2002 Student ID: 56027
/*
Abstract class is defined as "abstract class ... {}"
- Properties and functions can be inherited from other child classes
- Unlike open class, no object of this abstract class can be created
- Only objects that belong to the child class can be created
- Abstract classes MUST be inherited from, otherwise they are pointless
- Allow us to encapsulate common behaviors between child classes and the
differences between them can be directly implemented in the child classes
themselves
- Make for a robust and reusable code while avoiding duplication
Open vs Abstract
- Open classes on the other hand can instantiate their own objects. So, it CAN be
inherited from, but it's not a requirement that they be inherited from
- Same with Abstract methods and properties that we define; They MUST be overriden from
the child classes, whereas Open functions CAN be overriden but don't need to be
- Open classes aren't allowed to have abstract methods; Only abstract classes can have
abstract methods
- Abstract methods on the other hand can have open parameters and open functions
*/
abstract class Members(open val names: String, open val years: Int = 2020) {
abstract fun information()
}
class Instructors(override val names: String,
override val years: Int,
val fulltimes: Boolean) : Members(names, years) {
fun employeeStatus() {
if (fulltimes) {
println("Employee Status: Full-time instructor")
} else {
println("Employee Status: Not a full-time instructor")
}
}
override fun information() {
println()
println("Instructor's name: $names")
println("Started teaching in: $years")
}
}
class Students(override val names: String,
override val years: Int,
val ids: Int) : Members(names, years) {
fun printID() {
println("Student ID: $ids")
}
override fun information() {
println()
println("Student's name: $names")
println("Joined school in: $years")
}
}
// Now we can no longer create an object of the Members class
//val member = Members(names = "Kotlin", years = 2010)
//member.info()
val instructor = Instructors(names = "Julia", years = 2001, fulltimes = true)
instructor.information()
instructor.employeeStatus()
val student = Students(names = "Jenny", years = 2002, ids = 12321)
student.information()
student.printID()
Instructor's name: Julia Started teaching in: 2001 Employee Status: Full-time instructor Student's name: Jenny Joined school in: 2002 Student ID: 12321
/*
Interfaces enforce certain capabilities (methods) to Objects in a Class
If a Class implements an interface, all methods defined by the interface
are now available to Objects in that Class. This implementation is enforced
at build time by the compiler
Eg.
Driveable (Interface): drive, brake
Vehicle (Class) can then implement the above interface
Car, Truck, Scooter (Child Classes of Vehicle)
The exact implementation is different for each
*/
interface Driveable {
val mpg: Int
fun drive()
}
interface Entertainment {
fun radio()
}
class Car(val color: String): Driveable, Entertainment {
override val mpg = 36
override fun drive() {
print("Driving a Car...")
println("miles per gallon: $mpg")
}
override fun radio() {
println("Playing your favorite FM station!")
}
}
class Truck(val color: String): Driveable {
override val mpg = 25
override fun drive() {
print("Driving a Truck...")
println("miles per gallon: $mpg")
}
}
val car1: Driveable = Car("Blue")
car1.drive()
val truck: Driveable = Truck("White")
truck.drive()
val car2: Entertainment = Car("Blue")
car2.radio()
Driving a Car...miles per gallon: 36 Driving a Truck...miles per gallon: 25 Playing your favorite FM station!
Load additional dependencies to the notebook from Maven repos as follows:
@file:Repository("https://repo1.maven.org/maven2")
@file:DependsOn("org.nield:kotlin-statistics:1.2.1")
Then, import as:
import org.nield.kotlinstatistics.*
The Kotlin kernel pre-configures certain libraries, and allows the notebook user to load them via special commands, also known as magics. To pre-configure libraries for a notebook, one must comma-separate their names prepened with %use. Here's how it works:
%useLatestDescriptors //use latest versions of library descriptors available. By default, bundled descriptors are used
%use lets-plot
LetsPlot.getInfo()
Lets-Plot Kotlin API v.1.1.0. Frontend: Notebook with dynamically loaded JS. Lets-Plot JS v.1.5.4.
lets-plot: Open-source plotting library for statistical data. It is inspired by Python's ggplot and The Grammar of Graphics; this library is integrated tightly with the Kotlin kernel; the library is multi-platform and can be used not just with JVM but also from JS and Python.
val data = mapOf<String, Any>(
"tray" to listOf("1", "2", "2", "1", "1", "2", "2", "1"),
"fruits" to listOf("banana", "grapes", "banana", "banana", "banana", "grapes", "banana", "grapes"))
val p = lets_plot(data)
val layer = geom_bar {
x = "tray"
fill = "fruits"
}
(p + layer)
val rand = java.util.Random()
val data = mapOf (
"rating" to List(200) { rand.nextGaussian() } + List(200) { rand.nextGaussian() * 1.5 + 1.5 },
"cond" to List(200) { "A" } + List(200) { "B" }
)
var p = lets_plot(data)
p += geom_density(color="dark_green", alpha=.3) {x="rating"; fill="cond"}
p + ggsize(500, 250)
kmath: Could be pronounced as key-math. The Kotlin MATHematics library is intended as a Kotlin-based analog to Python's numpy library. In contrast to numpy and scipy it is modular and has a lightweight core. This library provides functionality for data manipulation using a functional-style API; it allows to filter, transform, aggregate and reshape tabular data.
%use kmath
import scientifik.kmath.structures.Matrix
import kotlin.random.Random
val random = Random(1224)
val dim = 100
//creating invertible matrix
val matA = Matrix.real(dim, dim) { i, j -> if (i <= j) random.nextDouble() else 0.0 }
val matB = Matrix.real(dim, dim) { i, j -> if (i >= j) random.nextDouble() else 0.0 }
val matrix = matB dot matA
print(matrix)
Matrix(rowsNum = 100, colNum = 100, features=[])
matrix.dimension
2