6

I’m a Fan of Python’s Default Map. Can Kotlin Do the Same?

 1 year ago
source link: https://betterprogramming.pub/im-a-fan-of-python-s-default-map-can-kotlin-do-the-same-b75187186f32
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

I’m a Fan of Python’s Default Map. Can Kotlin Do the Same?

Meet Kotlin’s version of defaultDict

1*Pk-VDLtzBpCRZFRR7O9mgg.jpeg

A world map

We all know what a (hash)map is and if you know the Python programming language, you probably came across defaultdictat some point. I was wondering if the same construct is available in Kotlin and I will be explaining my finding in this article.

The documentation for Python’s defaultdict including a couple of examples can be found here. Its basic usage is demonstrated in the following snippet:

The default dict got its name from the fact that it includes default values for keys it does not know yet. Like in the example above where a client tries to take a random value out of the map which results in a value 0although there was never anything written to that map.

The defaultdict can be used with other types than integers, too and makes sure that you don't get a KeyError on unknown key accesses. Instead, it provides a default value for unknown keys, which can be really helpful for grouping and counting algorithms like the following one:

This little tool can be extremely helpful and it’s something I used a lot when solving algorithmic problems you encounter in many, many tech interview situations which I wrote about here.

Let’s now consider the Kotlin programming language and see if we can have the same behavior as with Python’s defaultdict.

A straightforward problem

Let’s assume we want to write a generic function that takes a collection and returns a map containing the elements from the original collection associated to the number of their occurrences:

Although there is a natural functional way to do this in Kotlin, we want to consider some iterative approaches first. Let’s start with the following one.

Approach 1: Iterative solution without defaults

We can use a MutableMap for this algorithm. When iterating over all values, we look into the map to see whether it has been seen before. If so, we increment the counter; otherwise, we put an initial count of 1 into the map. Let’s see how we can do better making use of explicit defaults.

Approach 2: Iterative solution with explicit defaults

As an alternative to the previous solution, we can make use of putIfAbsent to ensure that the element is initialized with 0 so that we can safely increment the counter in the following line. What we do in this case is providing an explicit default value. Another, though similar, tool is getOrDefault:

countMap[e] = countMap.getOrDefault(e, 0) + 1

Providing explicit defaults via putIfAbsent or getOrDefault leads to easy and understandable solutions, but we can do better.

Iterative solution with implicit defaults

Similar to Python’s defaultdict, Kotlin comes with a neat extension called MutableMap::withDefault and it looks like this:

This extension allows to provide an initializer for keys that don’t have an associated value in the map. Let’s use it in our solution:

It simplifies the code further since we don’t need to deal with unknown values in the iteration anymore. Since we’re using a default map, we can safely get values from it and increment the counter as we go.

Important note

There’s one important thing you need to be aware of when using the withDefault extension, which is part of its documentation:

[…] This implicit default value is used when the original map doesn’t contain a value for the key specified, and a value is obtained with [Map.getValue] function […]

The default value will only be provided if you use Map::getValue, which is not the case when accessing the map with index operators.

The reason for this is the contract of the Map interface, which says:

Returns the value corresponding to the given [key], or null if such a key is not present in the map.

Since default maps want to fulfill this contract, they cannot return anything but null in the case of non-existent keys, which has been discussed in the Kotlin forum before.

Let’s go idiomatic

All right, we saw that Kotlin indeed comes with ways to provide default values in maps, which is great. But, is it idiomatic to write code as we did in the examples above anyway? Well, it depends. Most cases can be solved in much simpler ways using Kotlin’s functional APIs which come with built-in grouping and counting functionalities.

Nevertheless, it’s good to know the alternatives because, in certain situations, you might choose to go with an iterative approach. This is particularly true if you aim for the most optimal, time- and space-wise, solution. Let me provide you with a simplified version of our code:

It still uses an explicit for loop but got simplified by the use of apply which allows us to initialize the map in a single statement. You can read up on apply and all other scope functions here.

There are dozens of ways to solve the given problem in Kotlin, but probably the shortest (though not most optimal) one is a functional approach:

Conclusion

We can solve simple algorithms in many different ways using Kotlin. Similar to Python, you can initialize a map with a default value provider but need to be aware that the regular index operation access won’t work the way you might expect.

As documented, the getValue function has to be used instead. Since Kotlin comes with a very useful standard library, you want to consider using it as much as possible. This means that implementing common things like grouping or counting should, in most cases, needn’t be implemented from scratch but rather be done with existing functionalities from the standard library.

On the other hand, if you aim for optimal solutions, e.g. while in a coding interview, it’s important to understand alternatives to functional approaches which can make it hard to assess time and space complexities. Knowing how to use a default map is an important bit to writing comprehensible and performant code.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK