How to merge two Maps properly? - kotlin

I've been trying to solve a problem, where I should merge two Maps to get the new one with no equal values for equal keys.
fun main() {
mergePhoneBooks(mapOf("Emergency" to "112"),
mapOf("Emergency" to "911", "Police" to "02"))
}
fun mergePhoneBooks(mapA: Map<String, String>, mapB: Map<String, String>): Map<String, String> {
val result = mutableMapOf<String, String>()
for ((keyA, valueA) in mapA) {
for ((keyB, valueB) in mapB) {
result[keyA] = valueA
result[keyB] = valueB
if (keyA == keyB) {
if (valueA != valueB) {
result[keyA] = "$valueA, $valueB"
}
}
}
}
println(result)
return result
}
What I need is:
{Emergency=112, 911, Police=02},
but all I get is:
{Emergency=112, Police=02}

fun main() {
val m1 = mapOf("Emergency" to "112")
val m2 = mapOf("Emergency" to "911", "Police" to "02")
val r = (m1.entries + m2.entries).groupBy({ it.key }, { it.value })
// or use Sequence
// val r = (m1.asSequence() + m2.asSequence()).distinct().groupBy({ it.key }, { it.value })
println(r) // {Emergency=[112, 911], Police=[02]}
println(r.mapValues { it.value.joinToString(", ") }) // {Emergency=112, 911, Police=02}
}

result[keyA] = valueA
result[keyB] = valueB
You've assigned the valueB to the key (if keyA was same as keyB) again so the condition if (valueA != valueB) { is never going to be true.
You should remove the above lines and do it like this:
if (keyA == keyB && valueA != valueB) {
result[keyA] = "$valueA, $valueB"
} else {
result[keyA] = valueA
}

A map can only have one value per key. If you need to preserve multiple values, you need to change the value type to something like a List or Pair.
fun <K, V> mergeMapsValues(mapA: Map<K, V>, mapB: Map<K, V>): Map<K, List<V>> {
val outMap = mapA.mapValues { (_, value) -> mutableListOf(value) }.toMutableMap()
mapB.forEach { (key, value) ->
outMap.getOrPut(key, ::mutableListOf).add(value)
}
return outMap
}

I think below code will solve your issue:
fun main() {
val first = mapOf("A" to "0", "B" to "1", "C" to "2")
val second = mapOf("A" to "4", "C" to "2")
val result = (first.asSequence() + second.asSequence()).distinct()
.groupBy({ it.key }, { it.value })
.mapValues { it.value.joinToString(",") }
print(result) // {A=0,4, B=1, C=2}
}

You can implement mergeWith extension function:
infix fun Map<String, String>.mergeWith(anotherMap: Map<String, String>): Map<String, String> {
return (keys + anotherMap.keys).associateWith {
setOf(this[it], anotherMap[it]).filterNotNull().joinToString()
}
}
And use it:
val mapC = mapA mergeWith mapB

Related

Cut pairs with empty values from map

I'd like to filter out all the pairs with empty values
val mapOfNotEmptyPairs: Map<String, String> = mapOf("key" to Some("value"), "secondKey" to None)
expected:
print(mapOfNotEmptyPairs)
// {key=value}
Vanilla Kotlin
val rawMap = mapOf<String, String?>(
"key" to "value", "secondKey" to null)
// Note that this doesn't adjust the type. If needed, use
// a cast (as Map<String,String>) or mapValues{ it.value!! }
val filteredMap = rawMap.filterValues { it != null }
System.out.println(filteredMap)
p.s When using Arrow Option
val rawMap = mapOf<String, Option<String>>(
mapOf("key" to Some("value"), "secondKey" to None)
val transformedMap = rawMap
.filterValues { it.isDefined() }
.mapValues { it.value.orNull()!! }
p.p.s When using Arrow Option and their filterMap extension function;
val rawMap = mapOf<String, Option<String>>(
mapOf("key" to Some("value"), "secondKey" to None)
val transformedMap = rawMap
.filterMap { it.value.orNull() }
val mapOfNotEmptyPairs =
mapOf("key" to Some("value"), "secondKey" to None)
.filterValues { it is Some<String> } // or { it !is None } or { it.isDefined() }
.mapValues { (_, v) -> (v as Some<String>).t }

Kotlin - replace item in a map

I'm write a function that should replace an item in map. I have reach it using HashMap but is possible to write something similar in a "kotlinmatic way"?
fun HashMap<Int, String>.ignoreFields(path: String, fieldsToIgnore: FieldsToIgnore) = {
val filtered: List<Field> = fieldsToIgnore.ignoreBodyFields.filter { it.tagFile == path }
filtered.forEach {
val updatedJson = JsonPath.parse(JsonPath.parse(this[it.order])
.read<String>(whatevervariable))
.delete(it.field)
.apply { set("equalJson", this) }
.jsonString()
this.replace(it.order, updatedJson)
}
return this
}
update using map based on answers:
fun Map<Int, String>.ignoreFields(path: String, fieldsToIgnore: FieldsToIgnore): Map<Int, String> {
val filtered = fieldsToIgnore.ignoreBodyFields.filter { it.tagFile == path }
return this.mapValues {m ->
val field = filtered.find { it.order == m.key }
if (field != null) {
JsonPath.parse(JsonPath.parse(this[field.order])
.read<String>(whatevervariable))
.delete(field.field)
.apply { set(pathBodyEqualToJson, this) }
.jsonString()
} else {
m.value
}
}
}
You can use mapValues to conditionally use different value for same key. This will return a new immutable map
Update: filtered will now be a map of order to updatedJson
fun HashMap<Int, String>.ignoreFields(path: String,
fieldsToIgnore: FieldsToIgnore): Map<Int, String> {
val filtered: Map<Int, String> = fieldsToIgnore.ignoreBodyFields
.filter { it.tagFile == path }
.map {
val updatedJson = JsonPath.parse(JsonPath.parse(this[it.order])
.read<String>(whatevervariable))
.delete(it.field)
.apply { set("equalJson", this) }
.jsonString()
it.order to updatedJson
}
return this.mapValues {
filtered.getOrElse(it.key) { it.value }
}
}
A possible solution is to use mapValues() operator, e.g.:
fun Map<Int, String>.ignoreFields(ignoredFields: List<Int>): Map<Int, String> {
return this.mapValues {
if (ignoredFields.contains(it.key)) {
"whatever"
} else {
it.value
}
}
}
// Example
val ignoredFields = listOf<Int>(1,3)
val input = mapOf<Int, String>(1 to "a", 2 to "b", 3 to "c")
val output = input.ignoreFields(ignoredFields)
print(output)
// prints {1=whatever, 2=b, 3=whatever}

What's the equivalent of `mapNotNull` that result in map?

I can convert a List<Int?> to List<Int> using mapNotNull function as shown below.
#Test
fun main() {
val testData = listOf(1, null, 3, null)
val noNull = processAwayNull(testData)
}
private fun processAwayNull(testData: List<Int?>): List<Int> {
return testData.mapNotNull{ it }
}
How could I convert Map<String, Int?> to Map<String, Int>?
The below with testData.filter { it.value != null } doesn't works, as it still produce Map<String, Int?>.
#Test
fun main() {
val testData = mapOf("One" to 1, "Two" to null, "Three" to 3, "Four" to null)
val noNull = processAwayNull(testData)
}
private fun processAwayNull(testData: Map<String, Int?>): Map<String, Int> {
return testData.filter { it.value != null }
}
Well, not really out of the box (in the sense that you get Map<String, Int> immediately), but what about filterValues?
testData.filterValues { it != null } // gives Map<String, Int?> but without null-values
Combining or replacing that with mapValues (maybe you can use a default value instead of null?):
// combining:
testData.filterValues { it != null }
.mapValues { (_, value) -> value as Int }
// replacing:
testData.mapValues { (_, value) -> value ?: /* default value */ 0 }
Both give a Map<String, Int> but the first creates and fills 2 maps under the hood and the second uses 0 instead of null.
You can also simplify the filterValues-variant with an appropriate unchecked cast, as "we know it better":
testData.filterValues { it != null } as Map<String, Int> // unchecked cast, because: we really do know better, do we? ;-)
Alternatively, you could also just handle all entries the way you knew already (using mapNotNull) and then create a new map out of it:
testData.asSequence()
.mapNotNull { (key, value) ->
value?.let {
key to it
}
}
.toMap() // giving Map<String, Int>
If you require that more often you may even want to have your own extension function in place:
#Suppress("UNCHECKED_CAST")
fun <K, V> Map<K, V?>.filterValuesNotNull() = filterValues { it != null } as Map<K, V>
Now you can use it similar as to follows:
testData.filterValuesNotNull() // giving Map<String, Int>
Possible alternative with custom helper function:
inline fun <K, V, R> Map<K, V>.mapValuesNotNullToMap(transformValue: (V) -> R?): Map<K, R> =
buildMap {
this#mapValuesNotNullToMap.entries.forEach { (key, value) ->
transformValue(value)?.let { put(key, it) }
}
}

Invoking Action by reference in Kotlin

I've a Map of (key, value) where the value is a predefined function.
I want to iterate the input param in the Mp and check where the key is matching with the input parameter, then invoke the equivalent function, something like this
My code required to be something like below:
fun fn1: Unit { // using Unit is optional
println("Hi there!")
}
fun fn2 {
println("Hi again!")
}
fun MainFun(x: int){
val map: HashMap<Int, String> = hashMapOf(1 to fn1, 2 to fn2)
for ((key, value) in map) {
// if key = x then run/invoke the function mapped with x, for example if x = 1 then invoke fn1
}
}
Notes: I read something like below, but could not know how to us them:
inline fun <K, V> Map<out K, V>.filter(
predicate: (Entry<K, V>) -> Boolean
): Map<K, V> (source)
val russianNames = arrayOf("Maksim", "Artem", "Sophia", "Maria", "Maksim")
val selectedName = russianNames
.filter { it.startsWith("m", ignoreCase = true) }
.sortedBy { it.length }
.firstOrNull()
Hi I hope this would help you.
fun fn1() {
println("Hi there!")
}
fun fn2() {
println("Hi again!")
}
fun main(args: IntArray){
val map = hashMapOf(
1 to ::fn1,
2 to ::fn2)
map.filterKeys { it == args[0] } // filters the map by comparing the first int arg passed and the key
.map { it.value.invoke() } // invoke the function that passed the filter.
}
If the keyis RegEx then map.filterKeys { Regex(it).matches(x) } can be used, below full example of it Try Kotlin:
data class Person(val name: String,
val age: Int? = null)
val persons = listOf(Person("Alice"),
Person("Bob", age = 23))
fun old() {
val oldest = persons.maxBy { it.age ?: 0 }
println("The oldest is: $oldest")
}
fun young() {
val youngest = persons.minBy { it.age ?: 0 }
println("The youngest is: $youngest")
}
fun selection(x: String) {
val map = mapOf(
"old|big" to ::old,
"new|young" to ::young)
map.filterKeys { Regex(it).matches(x) }
.map { it.value.invoke() }
}
fun main(args: Array<String>) {
selection("new")
}
fun fn1() {
println("Hi there!")
}
fun fn2() {
println("Hi again!")
}
fun main(args: Array<Int>){
val map = hashMapOf(1 to ::fn1, 2 to ::fn2)
map.forEach { key, function -> function.invoke() }
}
This will do the work but your code does not even have the correct syntax. You should learn the basic first.

RxJava different output between Flowable and Observable with Window and Groupby

I'm using RxJava2 with code that boils down to something like this:
val whitespaceRegex = Regex("\\s+")
val queryRegex = Regex("query=([^&]+)", RegexOption.IGNORE_CASE)
val dateTimeFormatter = DateTimeFormatter.ISO_OFFSET_DATE_TIME
#JvmStatic
fun main(args: Array<String>) {
val cnt = AtomicLong()
val templateStr = "|date| /ignored/ query=|query|"
val random = ThreadLocalRandom.current()
var curDate = ZonedDateTime.of(LocalDate.of(2016, Month.JANUARY, 1), LocalTime.MIDNIGHT, ZoneId.of("UTC"))
val generator = Flowable.generate<String> { emitter ->
// normally these are read from a file, this is for the example
val next = cnt.incrementAndGet()
if (next % 3000 == 0L) {
curDate = curDate.plusDays(1)
}
if (next < 100000) {
val curStr = templateStr
.replace("|date|", dateTimeFormatter.format(curDate))
.replace("|query|", random.nextInt(1, 1000).toString())
emitter.onNext(curStr)
} else {
emitter.onComplete()
}
}
val source = generator
.map { line ->
val cols = line.split(whitespaceRegex)
val queryRaw = queryRegex.find(cols[2])?.groupValues?.get(1) ?: ""
val query = URLDecoder.decode(queryRaw, Charsets.UTF_8.name()).toLowerCase().replace(whitespaceRegex, " ").trim()
val date = dateTimeFormatter.parse(cols[0])
Pair(LocalDate.from(date), query)
}
.share()
source
.window(source.map { it.first }.distinctUntilChanged())
.flatMap { window ->
window
.groupBy { pair -> pair }
.flatMap({ grouping ->
grouping
.count()
.map {
Pair(grouping.key, it)
}.toFlowable()
})
}
.subscribe({ println("Result: $it}") }, { it.printStackTrace() }, { println("Done") })
}
When I use Observable.generate it works fine, but with Flowable.generate there is no output. This is counting how many queries occurred on a given day. The day increase sequentially so I form a window of each day, then count the queries with a groupBy. Do I need to do this differently with Flowable?
As akarnokd mentioned, this was due to flatMap having a default maxConcurrency of 128. I found this issue, https://github.com/ReactiveX/RxJava/issues/5126, which describes the reason in more detail. This fixes the problem:
val cnt = AtomicLong()
val templateStr = "|date| /ignored/ query=|query|"
val random = ThreadLocalRandom.current()
var curDate = ZonedDateTime.of(LocalDate.of(2016, Month.JANUARY, 1), LocalTime.MIDNIGHT, ZoneId.of("UTC"))
val generator = Flowable.generate<String> { emitter ->
val next = cnt.incrementAndGet()
if (next % 3000 == 0L) {
curDate = curDate.plusDays(1)
}
if (next < 1000000) {
val curStr = templateStr
.replace("|date|", dateTimeFormatter.format(curDate))
.replace("|query|", random.nextInt(1, 1000).toString())
emitter.onNext(curStr)
} else {
emitter.onComplete()
}
}
val source = generator
.map { line ->
val cols = line.split(whitespaceRegex)
val queryRaw = queryRegex.find(cols[2])?.groupValues?.get(1) ?: ""
val query = URLDecoder.decode(queryRaw, Charsets.UTF_8.name()).toLowerCase().replace(whitespaceRegex, " ").trim()
val date = dateTimeFormatter.parse(cols[0])
Pair(LocalDate.from(date), query)
}
.share()
source
.window(source.map { it.first }.distinctUntilChanged().doOnEach({println("Win: $it")}))
.flatMap( { window ->
window
.groupBy { pair -> pair }
.flatMap({ grouping ->
grouping
.count()
.map {
Pair(grouping.key, it)
}.toFlowable()
// fix is here
}, Int.MAX_VALUE)
// and here
}, Int.MAX_VALUE)
.subscribe({ println("Result: $it}") }, { it.printStackTrace() }, { println("Done") })