In Python, there are list comprehensions and similar constructs for maps and sets. In Kotlin there is nothing at all in any of the documentation with a similar name.
What are the equivalents of these comprehensions? For example, those found in Python 3 Patterns, Recipes and Idioms. Which includes comprehensions for:
Note: this question is intentionally written and answered by the author (Self-Answered Questions), so that the idiomatic answers to commonly asked Kotlin topics are present in SO.
Set comprehension is a method for creating sets in python using the elements from other iterables like lists, sets, or tuples. Just like we use list comprehension to create lists, we can use set comprehension instead of for loop to create a new set and add elements to it.
The last comprehension we can use in Python is called Set Comprehension. Set comprehensions are similar to list comprehensions but return a set instead of a list. The syntax looks more like Dictionary Comprehension in the sense that we use curly brackets to create a set.
A Python list comprehension consists of brackets containing the expression, which is executed for each element along with the for loop to iterate over each element in the Python list. Python List comprehension provides a much more short syntax for creating a new list based on the values of an existing list.
Practical Data Science using Python We can create new sequences using a given python sequence. This is called comprehension. It basically a way of writing a concise code block to generate a sequence which can be a list, dictionary, set or a generator by using another sequence.
Taking examples from Python 3 Patterns, Recipes and Idioms we can convert each one to Kotlin using a simple pattern. The Python version of a list comprehension has 3 parts:
These directly correlate to Kotlin functional extensions to collection classes. The input sequence, followed by the optional predicate in a filter
lambda, followed by the output expression in a map
lambda. So for this Python example:
# === PYTHON
a_list = [1, 2, 3, 4, 5, 6]
# output | var | input | filter/predicate
even_ints_squared = [ e*e for e in a_list if e % 2 == 0 ]
print(even_ints_squared)
# output: [ 4, 16, 36 ]
Becomes
// === KOTLIN
var aList = listOf(1, 2, 3, 4, 5, 6)
// input | filter | output
val evenIntsSquared = aList.filter { it % 2 == 0 }.map { it * it }
println(evenIntsSquared)
// output: [ 4, 16, 36 ]
Notice that the variable is not needed in the Kotlin version since the implied it
variable is used within each lambda. In Python you can turn these into a lazy generator by using the ()
instead of square brackets:
# === PYTHON
even_ints_squared = ( e**2 for e in a_list if e % 2 == 0 )
And in Kotlin it is more obviously converted to a lazy sequence by changing the input via a function call asSequence()
:
// === KOTLIN
val evenIntsSquared = aList.asSequence().filter { it % 2 == 0 }.map { it * it }
Nested comprehensions in Kotlin are created by just nesting one within the other's map
lambda. For example, take this sample from PythonCourse.eu in Python changed slightly to use both a set and a list comprehension:
# === PYTHON
noprimes = {j for i in range(2, 8) for j in range(i*2, 100, i)}
primes = [x for x in range(2, 100) if x not in noprimes]
print(primes)
# output: [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97]
Becomes:
// === KOTLIN
val nonprimes = (2..7).flatMap { (it*2..99).step(it).toList() }.toSet()
val primes = (2..99).filterNot { it in nonprimes }
print(primes)
// output: [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97]
Notice that the nested comprehension produces a list of lists which is converted to a flat list using flatMap()
and then converted to a set using toSet()
. Also, Kotlin ranges are inclusive, whereas a Python range is exclusive so you will see the numbers are slightly different in the ranges.
You can also use a sequence
generator with co-routines in Kotlin to yield the values without needing the call to flatMap()
or flatten()
:
// === KOTLIN
val nonprimes = sequence {
(2..7).forEach { (it*2..99).step(it).forEach { value -> yield(value) } }
}.toSet()
val primes = (2..99).filterNot { it in nonprimes }
Another example from the referenced Python page is generating a matrix:
# === PYTHON
matrix = [ [ 1 if item_idx == row_idx else 0 for item_idx in range(0, 3) ] for row_idx in range(0, 3) ]
print(matrix)
# [[1, 0, 0],
# [0, 1, 0],
# [0, 0, 1]]
And in Kotlin:
// === KOTLIN
val matrix = (0..2).map { row -> (0..2).map { col -> if (col == row) 1 else 0 }}
println(matrix)
// [[1, 0, 0],
// [0, 1, 0],
// [0, 0, 1]]
Or in Kotlin instead of lists, you could also generate arrays:
// === KOTLIN
val matrix2 = Array(3) { row ->
IntArray(3) { col -> if (col == row) 1 else 0 }
}
Another of the examples for set comprehensions is to generate a unique set of properly cased names:
# === PYTHON
names = [ 'Bob', 'JOHN', 'alice', 'bob', 'ALICE', 'J', 'Bob' ]
fixedNames = { name[0].upper() + name[1:].lower() for name in names if len(name) > 1 }
print(fixedNames)
# output: {'Bob', 'Alice', 'John'}
Is translated to Kotlin:
// === KOTLIN
val names = listOf( "Bob", "JOHN", "alice", "bob", "ALICE", "J", "Bob" )
val fixedNames = names.filter { it.length > 1 }
.map { it.take(1).toUpperCase() + it.drop(1).toLowerCase() }
.toSet()
println(fixedNames)
// output: [Bob, John, Alice]
And the example for map comprehension is a bit odd, but can also be implemented in Kotlin. The original:
# === PYTHON
mcase = {'a':10, 'b': 34, 'A': 7, 'Z':3}
mcase_frequency = { k.lower() : mcase.get(k.lower(), 0) + mcase.get(k.upper(), 0) for k in mcase.keys() }
print(mcase_frequency)
# output: {'a': 17, 'z': 3, 'b': 34}
And the converted, which is written to be a bit more "wordy" here to make it clearer what is happening:
// === KOTLIN
val mcase = mapOf("a" to 10, "b" to 34, "A" to 7, "Z" to 3)
val mcaseFrequency = mcase.map { (key, _) ->
val newKey = key.toLowerCase()
val newValue = mcase.getOrDefault(key.toLowerCase(), 0) +
mcase.getOrDefault(key.toUpperCase(), 0)
newKey to newValue
}.toMap()
print(mcaseFrequency)
// output: {a=17, b=34, z=3}
Further reading:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With