How to collect a stream of pairs in kotlin? - kotlin

How to collect a stream of pairs in kotlin?
So in Java, I usually do:
Stream.of("1", "2", "3").map(x -> new AbstractMap.SimpleEntry<>(x, x)).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))
However in Kotlin,
Stream.of("1", "2", "3").map{ x -> x to x }
returns a Stream of Pairs and i can't find a way to collect this.

In Kotlin, it's more natural to use listOf() rather than Java's Stream.of(). Once you have a List<Pair>, you can use the .toMap() extension to turn them into a Map!
val myMap: Map<String, String> = listOf("1", "2", "3").map{ it to it }.toMap()
There is also the .associate() function on List that will just create a map for you, given a lambda:
val myMap2: Map<String, String> = listOf("1", "2", "3").associate { it to it }
That one seems cleaner, IMHO.

Since you use the list element itself as key, using associateWith would make the code even more concise:
val myMap = listOf("1", "2", "3").associateWith{ it }
Result:
{1=1, 2=2, 3=3}

Related

Kotlin Generic problem, UNCHECKED_CAST , required:Nothing

#file:Suppress("UNCHECKED_CAST")
data class Element<T>(
val key: String,
val valueOne: T,
val valueTwo: T,
val comparator: Comparator<T>,
val comparatorValue: CompareResult
)
enum class CompareResult(
val value: Int
) {
LESS(-1),
EQUAL(0),
GREATER_THAN(1)
}
fun <T> matchesComparison(list:Collection<Element<T>>): Pair<Boolean, List<String>> {
val failedComparisons = mutableListOf<String>()
for (element in list) {
val compareValue = element.comparator.compare(element.valueOne, element.valueTwo)
if (element.comparatorValue.value != compareValue) {
failedComparisons.add(element.key)
}
}
return Pair(failedComparisons.isEmpty(), failedComparisons)
}
val stringComparator = Comparator.comparing(String::toString)
val intComparator = Comparator.comparing(Int::toInt)
val elementsToCompare = listOf(
Element("number", 1, 2, intComparator, CompareResult.LESS),
Element("first name", "a", "a", stringComparator, CompareResult.EQUAL),
Element("last name", "a", "b", stringComparator, CompareResult.EQUAL)
)
matchesComparison(elementsToCompare).second.joinToString(", ","Failed elements: \"","\"")
I often get faced with comparing two different object properties with the same values.
As an example object A has props number,firstname,lastname. What i want to do is create a list have and have a function which goes over these Elements and returns which props have failed the comparison. I've managed to use generics for both the object and the matchesComparison function which returns the failed comparisons. The problem begins when i want to pass this list which is of type Collection<Element<out Any>> to this function is i get a type missmatch. instead of using unchecked casts to force the Comparator to be of type Any i would like to do this
val stringComparator = Comparator.comparing(String::toString)
val intComparator = Comparator.comparing(Int::toInt)
The result value that of the script above should be Failed elements: "last name"
I tried changing the signature of the function to out any but then the comparator.compare method has both params as of type Nothing. I really want to avoid unsing unchecked casts.
matchesComparison() doesn't need to be generic in this case. It doesn't really care what is the type of the whole input collection, so we can simply use * here.
Then we have another problem. The compiler isn't smart enough to notice that while we perform operations on a single element, all its properties are of matching types. As a result, it doesn't allow to use element.comparator on element.valueOne and element.valueTwo. To fix this problem, we simply need to create a separate function which works on a single Element, so it understand the type for all properties is the same:
fun matchesComparison(list:Collection<Element<*>>): Pair<Boolean, List<String>> {
fun <T> Element<T>.matches() = comparatorValue.value == comparator.compare(valueOne, valueTwo)
val failedComparisons = mutableListOf<String>()
for (element in list) {
if (!element.matches()) {
failedComparisons.add(element.key)
}
}
return Pair(failedComparisons.isEmpty(), failedComparisons)
}
Also, I believe such matches() function should be actually a member function of Element. It seems strange that while Element is pretty independent and it contains everything that is needed to perform a comparison, it still requires to use external code for this. If it would have a matches() function then we wouldn't need to care about its T. matches() would work with any Element.

removeAt not functioning in kotlin

I've a list of objects, I want to remove the element with its index, here's my code but it doesn't work, returns an error saying:unresolved reference: removeAt println(sachmelebi.removeAt(asd))
fun main() {
data class SachmelebiClass(
val name: String,
val link: String
)
val sachmelebi = listOf(
SachmelebiClass(
"ხინკალი",
"https://www.google.com/search?q=%E1%83%AE%E1%83%98%E1%83%9C%E1%83%99%E1%83%90%E1%83%9A%E1%83%98&oq=%E1%83%AE%E1%83%98%E1%83%9C%E1%83%99%E1%83%90%E1%83%9A%E1%83%98&aqs=chrome..69i57j46j0l6.1143j0j9&sourceid=chrome&ie=UTF-8"
),
SachmelebiClass(
"aa",
"aaa"
),
SachmelebiClass(
"bb",
"bbb"
)
)
val randomColor = sachmelebi.random()
println()
val asd = sachmelebi.indexOf(randomColor)
sachmelebi.removeAt(asd)
listOf() returns a read-only view of the underlying List so you need a mutable List as follows:
val sachmelebi = mutableListOf(
SachmelebiClass(
"ხინკალი",
"https://www.google.com/search?q=%E1%83%AE%E1%83%98%E1%83%9C%E1%83%99%E1%83%90%E1%83%9A%E1%83%98&oq=%E1%83%AE%E1%83%98%E1%83%9C%E1%83%99%E1%83%90%E1%83%9A%E1%83%98&aqs=chrome..69i57j46j0l6.1143j0j9&sourceid=chrome&ie=UTF-8"
),
SachmelebiClass(
"aa",
"aaa"
),
SachmelebiClass(
"bb",
"bbb"
)
)
You can also do the following so that you can access `removeAt()` method (as #Tenfour04 mentioned, there is an overloaded version of [listOf()][1] that actually returns an immutable List and thus you should be careful while performing this cast) `(sachmelebi as MutableList).removeAt(asd)`
(As discussed in the comments with #Tenfour04, the striked-through suggestion has so many disadvantages that it is better to not even mention it. Thanks #Tenfour04 for the hints.)
Another possibility, most probably a better one, is to stick with read-only Lists and do the following instead:
val finalSachmelebi = sachmelebi.minus(randomColor)
This will create a new List with the final result. Mutable lists is usually a source of bugs, and that is why using read-only lists is usually preferable.
val newList = sachmelebi.toMutableList().removeAt(asd)

How to add data to Map copying existing values based on List of identifiers

Sorry for the poor title but it is rather hard to describe my use case in a short sentence.
Context
I have the following model:
typealias Identifier = String
data class Data(val identifier: Identifier,
val data1: String,
val data2: String)
And I have three main data structures in my use case:
A Set of Identifiers that exist and are valid in a given context. Example:
val existentIdentifiers = setOf("A-1", "A-2", "B-1", "B-2", "C-1")
A Map that contains a List of Data objects per Identifier. Example:
val dataPerIdentifier: Map<Identifier, List<Data>> = mapOf(
"A-1" to listOf(Data("A-1", "Data-1-A", "Data-2-A"), Data("A-1", "Data-1-A", "Data-2-A")),
"B-1" to listOf(Data("B-1", "Data-1-B", "Data-2-B")),
"C-1" to listOf(Data("C-1", "Data-1-C", "Data-2-C"))
)
A List of Lists that group together the Identifiers that should share the same List<Data> (each List includes always 2 Identifiers). Example
val identifiersWithSameData = listOf(listOf("A-1", "A-2"), listOf("B-1", "B-2"))
Problem / Use Case
The problem that I am trying to tackle stems from the fact that dataPerIdentifier might not contain all identifiersWithSameData given that existentIdentifiers contains such missing Identifiers. I need to add those missing Identifier to dataPerIdentifier, copying the List<Data> already in there.
Example
Given the data in the Context section:
A-1=[Data(identifier=A-1, data1=Data-1-A, data2=Data-2-A),
Data(identifier=A-1, data1=Data-1-A, data2=Data-2-A)],
B-1=[Data(identifier=B-1, data1=Data-1-B, data2=Data-2-B)],
C-1=[Data(identifier=C-1, data1=Data-1-C, data2=Data-2-C)]
The desired outcome is to update dataPerIdentifier so that it includes:
A-1=[Data(identifier=A-1, data1=Data-1-A, data2=Data-2-A),
Data(identifier=A-1, data1=Data-1-A, data2=Data-2-A)],
B-1=[Data(identifier=B-1, data1=Data-1-B, data2=Data-2-B)],
C-1=[Data(identifier=C-1, data1=Data-1-C, data2=Data-2-C)],
A-2=[Data(identifier=A-2, data1=Data-1-A, data2=Data-2-A),
Data(identifier=A-2, data1=Data-1-A, data2=Data-2-A)]
The reason is that existentIdentifiers contains A-2 that is missing in the initial dataPerIdentifier Map. B-2 is also missing in the initial dataPerIdentifier Map but existentIdentifiers does not contain it, so it is ignored.
Possible solution
I have already a working code (handleDataForMultipleIdentifiers() method is the one doing the heavy lifting), but it does not feel to be the cleanest or easiest to read:
fun main(args: Array<String>) {
val existentIdentifiers = setOf("A-1", "A-2", "B-1", "C-1")
val dataPerIdentifier: Map<Identifier, List<Data>> = mapOf(
"A-1" to listOf(Data("A-1", "Data-1-A", "Data-2-A"), Data("A-1", "Data-1-A", "Data-2-A")),
"B-1" to listOf(Data("B-1", "Data-1-B", "Data-2-B")),
"C-1" to listOf(Data("C-1", "Data-1-C", "Data-2-C"))
)
val identifiersWithSameData = listOf(listOf("A-1", "A-2"), listOf("B-1", "B-2"))
print("Original Data")
println(dataPerIdentifier)
print("Target Data")
println(dataPerIdentifier.handleDataForMultipleIdentifiers(identifiersWithSameData, existentIdentifiers))
}
fun Map<Identifier, List<Data>>.handleDataForMultipleIdentifiers(identifiersWithSameData: List<List<Identifier>>, existentIdentifiers: Set<Identifier>)
: Map<Identifier, List<Data>> {
val additionalDataPerIdentifier = identifiersWithSameData
.mapNotNull { identifiersList ->
val identifiersWithData = identifiersList.find { it in this.keys }
identifiersWithData?.let { it to identifiersList.minus(it).filter { it in existentIdentifiers } }
}.flatMap { (existentIdentifier, additionalIdentifiers) ->
val existentIdentifierData = this[existentIdentifier].orEmpty()
additionalIdentifiers.associateWith { identifier -> existentIdentifierData.map { it.copy(identifier = identifier) } }.entries
}.associate { it.key to it.value }
return this + additionalDataPerIdentifier
}
typealias Identifier = String
data class Data(val identifier: Identifier,
val data1: String,
val data2: String)
So my question is: how can I do this in a simpler way?
If identifiersWithSameData always contains 2 identifiers per item then it should not really be a list of lists, but rather a list of pairs or dedicated data classes. And if you convert this data structure into a map like this:
val identifiersWithSameData = mapOf("A-1" to "A-2", "A-2" to "A-1", "B-1" to "B-2", "B-2" to "B-1")
The the whole solution is pretty simple:
existentIdentifiers.associateWith {
dataPerIdentifier[it] ?: dataPerIdentifier[identifiersWithSameData[it]!!]!!
}
I'm not sure about both !!, for example I don't know if it is guaranteed that identifier existing in existentIdentifiers exists in identifiersWithSameData as well. You may need to tune this solution a little.

What is the smartest way to copy a Map in Kotlin?

I'd like to get a new instance of some Map with the same content but Map doesn't have a built-in copy method. I can do something like this:
val newInst = someMap.map { it.toPair() }.toMap()
But it looks rather ugly. Is there any more smarter way to do this?
Just use the HashMap constructor:
val original = hashMapOf(1 to "x")
val copy = HashMap(original)
Update for Kotlin 1.1:
Since Kotlin 1.1, the extension functions Map.toMap and Map.toMutableMap create copies.
Use putAll method:
val map = mapOf("1" to 1, "2" to 2)
val copy = hashMapOf<String, Int>()
copy.putAll(map)
Or:
val map = mapOf("1" to 1, "2" to 2)
val copy = map + mapOf<String, Int>() // preset
Your way also looks idiomatic to me.
The proposed way of doing this is:
map.toList().toMap()
However, the java's method is 2 to 3 times faster:
(map as LinkedHashMap).clone()
Anyway, if it bothers you that there is no unified way of cloning Kotlin's collections (and there is in Java!), vote here: https://youtrack.jetbrains.com/issue/KT-11221
Add this extension (to convert entries to pairs)
val <K, V> Map<K, V>.pairs: Array<Pair<K, V>>
get() = entries.map { it.toPair() }.toTypedArray()
And then you can easy combine immutable maps using default Kotlin syntax.
val map1 = mapOf("first" to 1)
val map2 = mapOf("second" to 2)
val map3 = mapOf(
*map1.pairs,
"third" to 3,
*map2.pairs,
)

Why Can't I Say: val list = properties.map((key,value) -> "$key = $value")

In Kotlin I can say
//sweet
for ((key,value) in System.getProperties())
println("$key = $value")
but I cannot say
//sour
val properties = System.getProperties()
val list = properties.map((key,value) -> "$key = $value")
What is the Kotlin equivalent to properties.map{case (key, value) => s"$key = $value"} in Scala?
In Kotlin 1.0 you can say:
val properties = System.getProperties()
val list = properties.map { "${it.key} = ${it.value}" }
And if you prefer to unpack the map entries to separate values you can say:
val properties = System.getProperties()
val list = properties.map { val (key, value) = it; "$key = $value" }
In Kotlin 1.1 "you can now use the destructuring declaration syntax to unpack the arguments passed to a lambda" (What's New in Kotlin 1.1 - Kotlin Programming Language):
val properties = System.getProperties()
val list = properties.map { (key,value) -> "$key = $value" }
Your question has absolutely nothing with inference.
In scala you doc:
import collection.JavaConversions._
val properties = System.getProperties()
val list = properties.map{case (key,value) => s"$key = $value"}
As for your comments.
Having gone from years of Scala, to now exploring Kotlin, I tend to like Kotlin better in its power and simplicity, however, it would be nice to be able to infer things better, given that I am still in learning mode.
The issues in your code have nothing to do with type inference. Not even the syntax was right.
Please correct your syntax for lambda expressions and unpacking:
val properties = mapOf(1 to "First", 2 to "Second")
val list = properties.map {val (value, key) = it; "$key = $value"}
There is a Kotlin feature that is planned but will not be available in 1.0 which will allow writing {(key,value) -> "$key = $value"} to unpack a value type like a Pair – cypressious
Thanks #cypressious that was exactly what I was hoping for. Basically the answer is that Kotlin is still evolving and people are still getting around to polishing off cool stuff.
For now I have found that
//savory
val properties = System.getProperties()
val list = properties.map {property -> "${property.key} = ${property.value}"}
comes pretty close.