Learning Kotlin: Operators
- This is the 17th post in a multipart series.
If you want to read more, see our series index
This next post is an introduction to operators in Kotlin, not the basic the “use a plus sign to add numbers together” stuff (you read this blog, you got to be smart enough to figure that out). No, this post is about just how amazingly extensive the language is when it comes to support for allowing your classes to use them.
Adding things together
So let’s start with a simple addition things together with plus I said we wouldn’t do. In the following example code, we have a way to keep track of scoring events in a rugby game and I would like to add up those events to get the total score:
enum class scoreType {
`try`,
conversion,
kick
}
data class score(val type:scoreType) {
fun points() = when(this.type) {
scoreType.`try` -> 5
scoreType.conversion -> 2
scoreType.kick -> 3
}
}
fun main(args: Array<String>) {
val gameData = listOf(score(scoreType.`try`), score(scoreType.conversion), score(scoreType.`try`), score(scoreType.kick))
var totalPoints = 0
for (event in gameData) {
totalPoints = event + totalPoints
}
println(totalPoints)
}
This obviously won’t compile, you can’t += an Int and my class? Right?!
We could make this change, which is probably the better way but for my silly example, let us say this isn’t ideal.
totalPoints = event.points() + totalPoints
So to get our code to compile we just need a function named plus
which has the operator
keyword and while I could call this myself, the Kotlin compiler is smart enough to make now it just work:
data class score(val type:scoreType) {
fun points() = when(this.type) {
scoreType.`try` -> 5
scoreType.conversion -> 2
scoreType.kick -> 3
}
operator fun plus(other:Int) = this.points() + other
}
How cool is that?!
If I wanted to take it further and support say totalPoints += event
then you would need to add a function to Integer which tells it how to add a score. Thankfully that is easy with extension functions:
operator fun Int.plus(other:score) = this + other.points()
Extensive
While the above is a bit silly, imagine building classes for distances, time, weights etc… being able to have a kilogram and a pound class and add them together! What makes Kotlin shine is how extensive it is, just look at this list!
Class | Operators | Method | Example Expression |
---|---|---|---|
Arithmetic | + , ‘+=’ |
plus |
first + second |
Augmented Assignments | += |
`plusAssign | first += second |
Unary | + |
unaryPlus |
+first |
Increment & Decrement | ++ |
inc |
first++ |
Arithmetic | - , -= |
minus |
first - second |
Augmented Assignments | -= |
minusAssign |
first -= second |
Unary | - |
unaryMinus |
`-first |
Increment & Decrement | -- |
dec |
first-- |
Arithmetic | * , *= |
times |
first * second |
Augmented Assignments | *= |
timesAssign |
first *= second |
Arithmetic | / , /= |
div |
first / second |
Augmented Assignments | /= |
divAssign |
first /= second |
Arithmetic | % , %= |
rem |
first % second |
Augmented Assignments | %= |
remAssign |
first %= second |
Equality | == , != |
equals |
first == second |
Comparison | > , < , <= , >= |
compareTo |
first > second |
Unary | ! |
not |
!first |
Arithmetic | .. |
rangeTo |
first..second |
In | in , !in |
contains |
first in second |
Index Access | [ , ] |
get |
This returns a value |
Index Access | [ , ] |
set |
This sets a value |
Invoke | () |
invoke |
first() |
I am going to go through some of these in more detail in future blog posts, but one I wanted to call out now:
Augmented Assignments vs. Plus or Minus
You might wonder why when we just implemented plus
above we got both support for +
and +=
and the table lists +=
under both plus and augmented. Why both - because you may want to support just +=
without supporting +
.
SFTPK: Hash Table
This post is one in a series of stuff formally trained programmers know – the rest of the series can be found in the series index.
The hash table, like the tree, needs a key. That key is then converted into a number using a hash function which results in the position in the array that is the data structure. So when you need to lookup an item, you pass in the key, get the same hash value and that points to the same place in memory so you get an O(1) lookup performance.
This is better than a pure array, where you cannot do a lookup so you have to search through giving an O(n) performance and better than a tree which also needs a search, so it ends at O(log n).
A hash table would be implemented with an array, which means that inserting is O(1) - unless the array is full then it is O(n), so this is a pretty good level of performance. An important note in comparing, O(1) when adding to the end of the array and O(1) with a hash table is that the hash table has additional computation so that while they have the same complexity it is still slower to use a hash table; this is why big O notation is useful for understanding it has limits on how it can help us choose what to use.
A hash table, in concept, is easy, but in practice, it is another thing altogether. First, you need a hash algorithm that ends up giving a value inside the bounds of the array - this is often done using modulus. For example, if we get a value of 14213 but our array is only 8 in size we can do 14213 % 8 to get its position, 5.
This converting of numbers brings a new problem, collisions; namely, we are taking a large number of possible numbers and converting them to a smaller number, so what happens when we get say 85? It also has a modulus of 5! Two items can’t live in the same location. There is no single solution to collisions, there are many.
One option uses linked lists in the items, so if there is a collision then you can store it at the same index and then you only have to search the, hopefully, very limited set in the collisions list. Others use offsets to shift collided items around the existing array. For this series though, a complete analysis of the options is beyond the scope.
Implementations
.NET has hashtable and DIctionary. Java comes with Hashtable and Kotlin has HashMap
Learning Kotlin: it is a thing
- This is the 15th post in a multipart series.
If you want to read more, see our series index
Our last post covered a lot of the awesome extensions to collections in Kotlin and as someone who comes from .NET and Java I think about writing Lambdas in a specific way.
val numbers = listOf(1,5,6,8,10)
val evens = numbers.filter({ num -> num % 2 == 0 })
println(evens)
In .NET/Java we need a range variable, in the example above it is num
which refers to the item in the collection we are working on. Kotlin knows you will always have a range variable, so why not simplify it and make it have a common name it
:
val numbers = listOf(1,5,6,8,10)
val evens = numbers.filter({ it % 2 == 0 })
println(evens)
Here we got ride of the num ->
start of the lambda and we can just refer to the automatically created range variable it
.
Learning Kotlin: Collections
- The code being referenced.
- This is the 15th post in a multipart series.
If you want to read more, see our series index
The Koans for Kotlin, are broken into sections and our earlier posts have covered the first section; this post is going to cover the entire second section. The reasons we are not breaking this down as we did before is because the common theme here is working the kotlin.collections
and in particular the wealth of extension methods that exist.
Koan | Functions | What it does? | C# Equivalent | Notes |
---|---|---|---|---|
Filter and Map | Map & Filter | Map is a projection - it allows you to take in a collection of items and change each time. It is useful for selecting properties of objects in a collection. Filter lets you filter a collection | Map is the same as select and filter is the same as where | |
All Any And Other Predicates | any, all, count and firstOrNull | Any returns true if it finds one any match to the provided expression. All requires all items in the collection to match the provided expression. Count provides the size of the collection. FirstOrNull returns the first item in a collection which matches the expression or, if nothing does, it returns null | any, all, count and firstOrDefault | Any, all and count work the same as in .NET. Count should be avoided when size exists, since Count could cause it to be on O(n) operation to determine the sized, though it depends on the collection implementation. firstOrNull is similar to .NETs firstOrDefault except, it returns null if there is no match where .NET might return null (only for reference types, like classes) and what the default is for value types (0 for int, false for boolean etc…) |
Flatmap | flatMap | Where map returns a collection which has the same size as the input, however flatMap the resulting collection can be a different size from the input | selectMany | |
Max and min | max and maxBy | MaxBy returns the largest value in the collection by letting you select which property to use for the ordering | Max | Interesting that .NET uses one function name to do with Kotlin uses max and maxBy to do both. |
sort | sortBy | SortBy lets your sort the collection with an O(n2) performance | orderBy | |
Sum | sumBy | sum | ||
GroupBy | groupBy | returns a map (or dictionary) where the key is one provided by the expression and the value is the items from the input collection which match the expression | GroupBy | |
Partition | partition | Partition returns a Pair with a list in both values and the expression defines what goes into each value. Basically you are splitting a collection | No direct option for this. You could use take if you only cared about half the collection. | |
Fold | fold | Allows you to produce a single value from a collection; normally aggregation - for example adding up all values in an array. | aggregate |
Going through these exercises was really interesting and I’m glad there is a lot of similar functionality to .NETs LINQ functions.
Learning Kotlin: The awesome that is the backtick
- This is the 14th post in a multi-part series.
If you want to read more, see our series index
The backtick in Kotlin is meant to allow Kotlin to use keywords or, since Kotlin is meant to interop with Java, allow Kotlin to call Java functions that might conflict with Kotlin; for example, this won’t work
val class = "school"
println(class)
If we wrap class
in backticks (as it is a keyword) then it works just fine:
val \`class\` = "school"
println(\`class\`)
This is nice… however it can be used for function names too, for example with tests we might use underscores to make the name verbose:
fun should_return_true_for_values_of_one() {
however…
fun \`should return true for values of one\`() {
Yes! That is a function name with REAL SPACES in it! AWESOME!
Learning Kotlin: return when
- This is the 13th post in a multipart series.
If you want to read more, see our series index
Continuing our break from the Koans today and going to look at another cool trick I learnt using Kotlin this week and focusing on the when keyword we learnt about previously;
Let’s start with a simple function to return the text for a value using when:
fun step1(number: Int):String {
var result = "";
when (number) {
0 -> result = "Zero"
1 -> result = "One"
2 -> result = "Two"
}
return result;
}
The next evolution is we can avoid creating a variable and returning directly (this is something I would do often in .NET)
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 -> “”
}
}
Yup, the when can return a value which means we can also do one final trick:
fun step4(number: Int):String = when (number) {
0 -> "Zero"
1 -> "One"
2 -> "Two"
else -> ""
}
It is so cool that your logic can just return from a condition, and it works with if
statements too and even with the Elvis operator we learnt yesterday:
fun ifDemo3(name:String?) = name ?: "Mysterious Stranger"
Learning Kotlin: Kotlin's Elvis Operator
- This is the 12th post in a multipart series.
If you want to read more, see our series index
We are going to take a short break from the Koans and look at a cool trick I learnt today. Previously, we learnt about the safe null operator but that only helps when calling functions or properties of objects…
fun ifDemo1(name:String?) {
val displayName = if (name != null) name else "Mysterious Stranger"
println("HI ${displayName}")
}
In the above example, we have the name
String which could be null but safe null operator (this needs a better name) can’t help here… so let us look at what Kotlin calls the Elvis operator ?:
and what it gives us:
fun ifDemo2(name:String?) {
val displayName = name ?: "Mysterious Stranger"
println("YO ${displayName}")
}
This is really cool and reminds me of SQL COALESCE where it lets you test the first value and if it is not null, it returns the first value, else it returns the second value.
SFTPK: Heap
This post is one in a series of stuff formally trained programmers know – the rest of the series can be found in the series index.
Continuing on with more tree-based data structures, we get to the heap. If we look back at the BST and Red/Black those trees ended up with the most average/middle value at the root node so that nodes which were smaller were on the left and larger nodes were on the right, the heap changes that.
There are actually two heaps, a minimum and maximum value heap, but there are very similar. The key aspects to know about either heap are
- It is a binary tree. This is important for insertion and deletion and ensures the depth of the tree doesn’t grow out too far from the root node.
- In a minimum heap, the root node is the SMALLEST value in the tree.
- In a minimum heap, any node should be smaller than all of its’ children
- In a maximum heap, the root node is the LARGEST value in the tree
- In a maximum heap, any node should be larger than all of its children
The advantage of a heap is as an implementation of a Queue where you can control the order items appear in the queue rather than just relying on insertion order.
Let’s have a look at what these will look like when we have the following dataset: 45, 42, 56, 78, 99, 30
Step | Minimum Heap | Maximum Heap | ||
---|---|---|---|---|
1 | We add 45 as the root node | We add 45 as the root node | ||
2 | We add 42 as the first child, but it is smaller, so we will swap it with the root node | {width=100} {width=100} | We add 42 add as the first child node | {width=100} |
3 | We add 56 as the second child node | {width=100} | We add 56 as the second child node; it is larger than its parent so we swap them. | {width=100} {width=100} |
4 | We add 78 as a child of 45 | {width=100} | We add 78 as a child of 42, though it is larger so it must be swapped. 78 is now a child of 56, which still wrong so we need to swap them too. | {width=100} {width=100} {width=100} |
5 | We add 99 as a child of 45 | {width=100} | We add 99 as a child of 56. 99 is larger, so we then swap 99 and 56. 99 is still larger than 78, so we need to swap those nodes too | {width=100} {width=100} {width=100} |
6 | Next we add 30 under 56. It is smaller than 56 so it must be swapped. Once swapped, its parent 42 is also larger so they need to swapped too. | {width=100} {width=100} {width=100} | Last we add 30 to under 45. | {width=100} |
Implementations
Java has a built-in implementation with the PriorityQueue and, unfortunately, .NET and JavaScript lacks an out of the box option.
Learning Kotlin: Object Expressions and SAM Conversions
- Code for the first Koan can be found here.
- Code for the second Koan can be found here.
- This is the 11th post in a multipart series.
If you want to read more, see our series index
For the 11th post, we get something new to an old C# person - Object Expressions!
Object Expressions are very similar to Anonymous Classes in Java where you can declare a class inline rather than entirely separately in its own file.
In the first Koan we need to implement Comparator<Int>
inline:
fun task10(): List {
val arrayList = arrayListOf(1, 5, 2)
Collections.sort(arrayList, object : Comparator {
override fun compare(o1:Int, o2:Int):Int {
return o2 - o1;
}
})
return arrayList
}
You can see on line 21, we define the new object with the object
keyword and then use : Comparator<Int>
to state it implements that interface. The Comparator has a single function which needs to be implemented which we do on line 22.
The second Koan takes this further and states if there is a single method in an abstract class or interface then we can use SAM, or Single Abstract Method, to avoid needing the class at all as we just need to implement the single function. To achieve this with the Koan, we use an anonymous function that handles the compare function of the Comparator:
fun task11(): List {
val arrayList = arrayListOf(1, 5, 2)
Collections.sort(arrayList, { x, y -> y - x})
return arrayList
}
and lastly, if we look back to previous post we can use the extension method to simply it further:
fun task12(): List {
return arrayListOf(1, 5, 2).sortedDescending()
}
---
These Koans have given me more thoughts about the language than probably any previous Koans:
- Why do classes which implement an interface use override?
In C#, when you implement an interface you do not need to state that you are not overriding the functions (see this example). In C# only state that your are overriding when you inherit from a function and you actually override a function. The reason is that an interface in Kotlin is closer to an abstract class than in C#, to the point it can have functionality - yup, interfaces can have functions and logic! - So why does Kotlin have interfaces and abstract classes?
The key difference is an abstract class can have state while the logic in an interface needs to be stateless! - Why bother having SAM?
As I was working on the Koan, I was delighted by the SAM syntax… and then I wondered why I needed this at all? Why isCollections.sort
taking a class as the second parameter? Why not just pass in a function, since that is all that is actually needed? Both C# and Kotlin supports passing functions so this is possible… but something I never knew about Java is that it doesn’t support passing functions! You have to use Callable, a class, to pass functions.
Learning Kotlin: Extension Functions and Extensions On Collections
- Code for the first Koan can be found here.
- Code for the second Koan can be found here.
- This is the 10th post in a multipart series.
If you want to read more, see our series index
This post is the first to cover multiple Koans in a single post.
The 10th in our series is very simple coming from C# because Extension Functions in Kotlin are identical as Extension Methods in C#, though they are much cleaner in their implementation.
In their example for the Koan we add a lastChar
and lastChar1
function to the String class.
fun String.lastChar() = this.get(this.length - 1)
// ‘this’ refers to the receiver (String) and can be omitted
fun String.lastChar1() = get(length - 1)
For the Koan itself, we need to an r
function to Int
and Pair<int, int>
which returns an instance of RationalNumber
, which we do as follows:
fun Int.r(): RationalNumber = RationalNumber(this, 1)
fun Pair.r(): RationalNumber = RationalNumber(first, second)
An additional learning on this, was the Pair<A, B>
class which is similar to the Tuple
class from C#.
When we get into the second Koan, we get exposed to some of the built-in extension’s functions in Kotlin which ship out of the box; in this case, we use sortedDescending
extension method with the Java collection. It is a great example of mixing Java and Kotlin too:
fun task12(): List {
return arrayListOf(1, 5, 2).sortedDescending()
}