Functional Kotlin part 4: collections manipulation

This is a part 4 of the #kotlin-showoff series and it's going to be about the standard functions over the collections(mostly iterables to be precise) allowing developer to express data modification in the clean and functional way.

General convention

Although one might think that kotlin has inherited all the base collection types from the Java it's not quite true. Kotlin transparently maps existing Java collections into the Kotlin by using some tricks such as typealiasing. Collections hierarchy in Kotlin make code even more safer by imposing separation between mutable and immutable data structures. Take a look on the interfaces diagram:

diagram originally posted on the kotlinlang.org

Having dedicated interfaces for immutable collections makes expressions are purely functional - no need to worry if api consumer modifies list on the way or even worse, attempt to insert into the immutable collection(goodbye UnsupportedOperationException!). Indeed, immutability is enforced in compile time by contract.

A note about Iterable vs Sequence

Those are very similar types of the base entities even with the same signatures, let's take a look

public interface Sequence<out T> {
    public operator fun iterator(): Iterator<T>
}

public interface Iterable<out T> {
    public operator fun iterator(): Iterator<T>
}

Those two base classes define the way data will be processed in the chain of the calls:

  • Operations on Iterable produce result immideatelly, so the full intermideate result will be passed between calls in the chain. The result is evaluated eagerly after each step.
  • Operations on the Sequence treat data items comming thorough as it would be an infinite stream, the closest analogy would be java8 Stream or RxObservable. Items passed via the chain of the calls one by one. Result is evaluated lazily.

As for now we focus on the Iterable and it's descendants(Collection, List, Map, etc..) . Luckily, many operations exist for both interfaces with exactly the same signatures

Simple list transformations filter, map, forEach

Those are the probably the most widely used operators and they do exactly after their name. The provided function is applied to the each element of the operation

val adminNames = users
  .filter { it.isAdmin }
  .map { it.name }

pupils.forEach { 
  println("${it.name}: ${it.score}")
}

filter* and map* families

There are way more similar operations provided in the Kotlin stdlib giving extra flexibility when it need:

val userList = users
  .filterNotNul()
  .filterNot { it.isBanned }
  .mapTo(mutableHashSet()) { it.userId }
  .mapIndexed { (idx, userId) -> "#${idx}: {it.userId}" }

In many occasions you'll find the same pattern - verb [not] [indexed] [to]. No need to memorise - the names come out intuitively:

Operations returning single element: first, last, single, elementAt, get

first and last return first and last elements (obviously).

val firstUser = users.first()
val firstAdminUser = users.first { it.isAdmin }
val lastBannedUser = users.last { it.isBanned }

single returns one element and throws exception if more than 1 element in collection matches the predicate

val oneLove = listOf("java", "kotlin",  "javascript").singleOrNull { it == "kotlin" } 

Also those operations can have return alternative value - provided by closure or null:

val oneLove = languages.singleOrNull { it == "kotlin" }
val tenthWinnerName = user.getOrElse(10) { "NO WINNER" }
val secondPerson = user.getOrNull(2)

Aggregation operations count, average, min, max

Again, intuitively those operations perform aggregations:

val avgScore = pupils.average { it.score }
val topStudent = pupils.max { it.score }
val channagingStudent = pupils.min { it.score }

Conditional oprations all, none, count, any

val numberOfTopStudents = pupils.count { it.score > 4.5 }
val allPassed = pupils.all { it.score > 2.0 }
val hasNeedleInHaystack = heap.any { it.object == NEEDLE }
val allGood = results.none { it.error != null }

List to Map transformation associate*, groupBy

Both operations produce a Map and they are different on how keys are collided. While assciate* simply overwrites existing value with associated key, groupBy adds value to the list of values:

val usersById = users.associate { it.id to it } // result type: Map<UserId, User>
val usersById = users.associateBy { it.id } // same output
val pupilsByScore = pupils.groupBy { it.score } // result type Map<Int, List<Pupil>>

Many more

There a way more functional operations over collections are available in Kotlin stdlib such as fold, reduce, minus(-), plus(+), contains(in) etc:

// result - list of the both users
val allUsers = fbUsers + twitterUsers 

// result - elements of allUserIds which are not in bannedUsersIds
val activeUserIds = allUserIds - bannedUsersIds 

// result - the longest length of the name
val longestName = names.reduce { longest, item -> if (longest.length < item.length) item else longest }

// result - same as above, the longest length of the name
val longestLength = names.map(String::length).fold(0, ::max))

// result - if Wally was there
val isWallyLovesKotlin = "Wally" in kotlinLovers

Those extension functions are very intuitive and widely used, essentially can cover most of the everyday tasks.

Conclusion

Kotlin collection functions provide a lot of flexibility to express your ideas and business logic in very concise, clear and functional way

Hopefully you found this article useful for you, please check out other posts by #kotlin-showoff hashtag

Functional kotlin part 3: scoping functions

In the part 3 of the series of the posts about kotlin we going to look into the one of the intensively used kotlin extension functions from the standard library - they allow to write very expressive and safe, functionally-looking code.

For folks who got lost on the word "extension functions" - it's a way to attach a function or property to the instances of the existing classes. For example, val d = 10.twice()It's very much like a classic Java Util classes with method twice(int) but done in a very clean way. Visually it looks like you're calling a member of the class, but in reality, the compiler calls your function passing receiver as an argument.

Read more

Functional kotlin part 2: elvis operator

Continuing series of posts #kotlin-showoff about functional constructions in kotlin I want to demostrate use of elvis operator

Essentially, elvis operator lvalue ?: rexpression is returning left value if it's not null or executes rexpression otherwise. The crazy thing about kotlin is most of the constructions are expressions and that gives another way to express business logic.

Read more

Functional kotlin part 1: safe calls

For the seasoned Java developer it's very easy to switch to kotlin. Even more, thanks to the great effort of JetBrains team for java interop, there is no need to wait for the greenfield project to start to write kotlin code. You can start koding straight away by either implementing new functionality in kotlin or converting existing classes into the new language by employing Intellj Idea automagic converter

This is a first of this series of posts unioned by tag #kotlin-showoff

Read more