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 }
Related
I am trying to do dictionary app using kotlin language. I built the project with mvvm and clean architecture. I have been trying to pull vocabulary information from the internet using jsoap. I am using flow for data. I couldnt find where the issiue is. Normally, the words should appear on the screen or I should be able to see the data when I println on the console.But I can't see it on the screen or on the console, probably because the data coming from the internet is as follows.
kotlinx.coroutines.flow.SafeFlow#1493a74
I am sharing my codes below
ExtractedData
data class ExtractedData(
var id :Int = 0,
var word:String = "",
var meaning :String = ""
)
I created ExtractedData class to represent vocabulary or word data from internet
WordInfoRepositoryImpl
class WordInfoRepositoryImpl #Inject constructor(
private val api:DictionaryApi
) : WordInfoRepository {
//get words with meanings on the internet using jsoap
override fun getEventsList(): Flow<Resource<MutableList<ExtractedData>>> = flow {
emit(Resource.Loading())
val listData = mutableListOf<ExtractedData>()
try {
val url = "https://ielts.com.au/australia/prepare/article-100-new-english-words-and-phrases-updated-2020"
val doc = withContext(Dispatchers.IO){
Jsoup.connect(url).get()//-->Here it gives the following warning even though I have it in withContext `Inappropriate blocking method call`
}
val table = doc.select("table")
val rows = table.select("tr")
val eventsSize = rows.size
for (i in 1 until eventsSize) {
val row = rows[i]
val cols = row.select("td")
val word = cols[0].text()
val meaning = cols[1].text()
listData.add(ExtractedData(i,word,meaning))
}
}
catch (e: IOException) {
emit(Resource.Error("IO Exception"))
}
catch (e : HttpException) {
emit(Resource.Error("HTTP EXCEPTION"))
}
emit(Resource.Success(listData))
}
}
getEventsList is in my WordInfoRepositoryImpl class in my data layer here I am pulling data from internet using jsoap
WordInfoRepository
interface WordInfoRepository {
fun getEventsList(): Flow<Resource<MutableList<ExtractedData>>>
}
this is the interface that I reference wordInforepositoryImpl in the data layer in my interface domain layer
GetWordsAndMeaningsOnTheInternetUseCase
class GetWordsAndMeaningsOnTheInternetUseCase#Inject constructor(
private val repository: WordInfoRepository
){
operator fun invoke() : Flow<Resource<MutableList<ExtractedData>>> {
return repository.getEventsList()
}
}
GetWordsAndMeaningsOnTheInternetUseCase is my usecase in my domain layer
ViewModel
#HiltViewModel
class MostUsedWordScreenViewModel #Inject constructor(
private val getWordsAndMeaningsOnTheInternetUseCase: GetWordsAndMeaningsOnTheInternetUseCase
) : ViewModel() {
private var searchJob: Job? = null
private val _state = mutableStateOf(MostUsedWordState())
val state: State<MostUsedWordState> = _state
init {
fetchData()
}
private fun fetchData() {
searchJob?.cancel()
searchJob = viewModelScope.launch(IO) {
getWordsAndMeaningsOnTheInternetUseCase().onEach { result ->
when (result) {
is Resource.Success -> {
_state.value = state.value.copy(
mostWordUsedItems = result.data ?: mutableListOf(),
isLoading = false
)
}
is Resource.Error -> {
_state.value = state.value.copy(
mostWordUsedItems = result.data ?: mutableListOf(),
isLoading = false
)
}
is Resource.Loading -> {
_state.value = state.value.copy(
mostWordUsedItems = result.data ?: mutableListOf(),
isLoading = true
)
}
}
}
}
}
}
MostUsedWordScreen
#Composable
fun MostUsedWordScreen(viewModel: MostUsedWordScreenViewModel = hiltViewModel()) {
val state = viewModel.state.value
println("state --- >>> "+state.mostWordUsedItems)
LazyColumn(
modifier = Modifier.fillMaxSize()
) {
items(state.mostWordUsedItems.size) { i ->
val wordInfo = state.mostWordUsedItems[i]
if(i > 0) {
Spacer(modifier = Modifier.height(8.dp))
}
MostUsedWordItem(word = wordInfo)
if(i < state.mostWordUsedItems.size - 1) {
Divider()
}
}
}
}
#Composable
fun MostUsedWordItem(word : ExtractedData ) {
// println("this is MostUsedWordItem")
Column(modifier = Modifier
.padding(5.dp)
.fillMaxWidth()) {
Text(text = word.word,
modifier = Modifier.padding(3.dp),
textAlign = TextAlign.Center,
fontSize = 18.sp,
)
}
}
It is included in the MostUsedWordScreenViewModel and MostUsedWordScreen presententation layer
Where I println("state --- >>> "+state.mostWordUsedItems) in MostUsedWordScreen, the state console shows as empty like this System.out: state --- >>> []
I tried to explain as detailed as I can, I hope you can understand.
A Flow doesn't do anything until you call a terminal operator on it. You called onEach, which is not a terminal operator. You should use collect. Or you can avoid the nesting inside a launch block by using onEach and launchIn, which does the same thing as launching a coroutine and calling collect() on the flow. You don't need to specify Dispatchers.IO here because nothing in your Flow is blocking. You correctly wrapped the blocking call in withContext(Dispatchers.IO), and the warning is a false positive. That's a well-known bug in their compiler inspection.
searchJob = getWordsAndMeaningsOnTheInternetUseCase().onEach { result ->
when (result) {
is Resource.Success -> {
_state.value = state.value.copy(
mostWordUsedItems = result.data ?: mutableListOf(),
isLoading = false
)
}
is Resource.Error -> {
_state.value = state.value.copy(
mostWordUsedItems = result.data ?: mutableListOf(),
isLoading = false
)
}
is Resource.Loading -> {
_state.value = state.value.copy(
mostWordUsedItems = result.data ?: mutableListOf(),
isLoading = true
)
}
}
}.launchIn(viewModelScope)
By the way, you need to move your emit(Success...) inside your try block. The way it is now, when there is an error, the error will immediately get replaced by a Success with empty list.
Side note, I recommend avoiding passing MutableLists around between classes. You have no need for them and it's a code smell. Sharing mutable state between classes is error-prone. I don't think there is any justification for using a Flow<MutableList> instead of a Flow<List>.
You rarely even need a MutableList locally in a function. For example, you could have done in your try block:
val listData = List(eventsSize - 1) {
val row = rows[it + 1]
val cols = row.select("td")
val word = cols[0].text()
val meaning = cols[1].text()
ExtractedData(i,word,meaning)
}
emit(Resource.Success(listData))
I have a string url like this:
exampleUrl = www.example.com/test?item=param1=1¶m2=11¶m3=111&item=param1=2¶m2=22¶m3=222
and i want to extract from it a Map of key values using item as key.
I wrote the below function
fun String.getUrlParams(): Map<String, List<String>> {
val params = HashMap<String, List<String>>()
val urlParts = this.split("\\?".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
if (urlParts.size > 1) {
val query = urlParts[1]
for (param in query.split("item=".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()) {
System.out.println(param)
val key = "item"
var value = URLDecoder.decode(param, "UTF-8")
var values: MutableList<String>? = params[key] as MutableList<String>?
if (values == null) {
values = ArrayList()
params[key] = values as ArrayList<String>
}
values?.add(value)
}
}
return params
}
But on printed data i am getting this -> {item=[, param1=1¶m2=11¶m3=111&, param1=2¶m2=22¶m3=222]}. It has an empty value on start and the & symbol on the end of second value.
The correct one should be -> {item=[param1=1¶m2=11¶m3=111, param1=2¶m2=22¶m3=222]}
What am i missing?
Thanks in advance
Splitting on something that appears right at the start or end of a String will give you an empty String at the start or end of the results.
Instead of dropLastWhile you can filter all empty Strings.
You can use "&?item=" to avoid having the trailing & in your first block.
After removing the unnecessary toTypedArray() and Java-specific code you have:
fun String.getUrlParams(): Map<String, List<String>> {
val params = HashMap<String, List<String>>()
val urlParts = split("\\?".toRegex()).filter(String::isNotEmpty)
if (urlParts.size > 1) {
val query = urlParts[1]
for (param in query.split("&?item=".toRegex()).filter(String::isNotEmpty)) {
val key = "item"
val value = URLDecoder.decode(param, "UTF-8")
var values: MutableList<String>? = params[key] as MutableList<String>?
if (values == null) {
values = ArrayList()
params[key] = values
}
values.add(value)
}
}
return params
}
Cleaning it up a bit gives:
fun String.getUrlParams(): Map<String, List<String>> {
val urlParts = split("\\?".toRegex()).filter(String::isNotEmpty)
if (urlParts.size < 2) {
return emptyMap()
}
val query = urlParts[1]
return listOf("item").associateWith { key ->
query.split("&?$key=".toRegex()).filter(String::isNotEmpty)
.map { URLDecoder.decode(it, "UTF-8") }
}
}
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}
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
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") })