Iterating over list with lambda forEach Kotlin - android

I have a list of 30 random numbers that correspond to 1 of 8 colours, and I need to iterate over the 8 colors(or 30 numbers) and find the number of times each colour occurs. I need to do this using lambdas and functional programming, so no traditional for loops.
val iterator = colours.toList().iterator()
iterator.forEach{
println("$it count: " + (numbers
.map{a -> colours[a]}
.count{it == ("$it")}))
}
The problem currently is my output for count is just 50, not the specific number of times a colour occurs.
If I do it like this:
println("Red count:" + (numbers
.map{a -> colours[a]}
.count{it == ("red")}))
it outputs the correct number, but not with the loop.
What it ouputs:
green count: 50
red count: 50
what it should output (for example)
green count:9
red count:3
Thanks in advance

Add a named parameter to your forEach loop. The implicit name "it" is getting shadowed by the count function.
val iterator = colours.toList().iterator()
iterator.forEach { colour ->
println("$colour count: " + (numbers
.map{a -> colours[a]}
.count{it == ("$colour")}))
}

You don't really need to do a nested iteration here. Currently you're operating at O(n^2) since you have to traverse the list once for every element. Since you know you're working with a small number of potential values, you could instead just group them by value and then map the values to the size of the resulting lists, i.e.
val colourNames = listOf("red", "green", "blue", "yellow", "orange", "indigo", "violet", "black")
// Generates 30 random numbers between 0 and 8 (exclusive)
val randomColours = (0 until 30).map { (0 until colourNames.size).random() }
val result = randomColours
.groupBy { color -> colourNames[color] } // outputs a Map<String, List<Int>>
.mapValues { (color, colorCountList) -> colorCountList.size } // Map<String, Int>
println(result) // {yellow=4, orange=4, red=5, indigo=3, blue=8, green=2, violet=2, black=2}

Related

How do you call a variable created earlier in a when in kotlin?

I'm trying to list the first 100 of a shuffled list. I'm telling it to shuffle if the list is at 0 and then increment. I then am trying to call that list in another section of the when but it's not working. How can I accomplish this?
when (countF) {
0 -> {
//shuffle at 0
val randomChaos = chaosList.asSequence().shuffled().take(chaosList.count()).toList()
cResult.text = randomChaos.elementAt(countF) + countF + "\n\n\n\n\n\n\n\n" + this.cResult.text
countF++
}
1-99 -> {
//show 1-99
cResult.text = randomChaos.elementAt(countF) + countF + "\n\n\n\n\n\n\n\n" + this.cResult.text
countF++
}
100 -> countF = 0
You would need to create the val randomChaos before the when enclosure for it to be available in the scope of multiple branches of the when statement.
That said, the way you're getting a random element is very convoluted. take(chaosList.count()) is completely redundant. And since you don't use multiple sequence operators, creating a sequence is also redundant. Finally, you are only pulling a single item from the random list, so it's unnecessary to create a shuffled list in the first place. Using elementAt() on a shuffled list is no different than picking any element out of that shuffled list, or simply picking a random item out of a list that isn't shuffled at all.
Also, the first two branches of your when statement currently would produce exactly the same results so they can be merged.
Based on what you described, I'm guessing you had this when statement inside a loop that tries to run it 100 times so you can list all the items. For that to work, you would need to shuffle the list one time outside the loop, and then you could iterate its elements in the loop.
However, there are functions that can make it easier to do what you're suggesting. Here's an example:
val randomChaos = chaosList.shuffled()
cResult.text = randomChaos.asSequence()
.take(100)
.withIndex()
.joinToString("\n") { (i, value) ->
"$value-$i"
}
In this case, using a Sequence helps avoid creating an intermediate list to hold the first 100 values.
var randomChaos = chaosList.shuffled()
fun cShuf() { randomChaos = chaosList.shuffled() }
cRoll.setOnClickListener() {
cResult.movementMethod = ScrollingMovementMethod()
if (countF < 1) { cShuf() }
cResult.text = randomChaos.elementAt(countF) + "\n\n\n\n\n\n\n\n" + this.cResult.text
countF++
if (countF > 100) countF = 0
}
I have figured out how to use a function to generate a new shuffe of the list once I've hit > 100 shown.
My issue with making it a function was I was trying to use val variable in the function but the variable already existed so I didn't need to use val, just the name of the variable.

Modifying a SnapshotStateList throws ConcurrentModificationException

The documentation of SnapshotStateList states that it is similar to a regular mutable list. I have a use case where I need to modify all the elements in the list (set case). This does not change the size of the list, but I'm running into ConcurrentModificationException.
I have created a very simplified version of my usecase here. The following kotlin list works fine:
val myList2 = mutableListOf("a", "b", "c")
myList2.forEachIndexed { index, _ ->
// Modify item at index
myList2[index] = "x"
}
But I get a concurrent modification exception here:
val myList = mutableStateListOf("a", "b", "c")
myList.forEachIndexed { index, _ ->
// Modify item at index but I get an exception
myList[index] = "x"
}
How can I modify all elements of mutableStateList() in place without getting the concurrent modification exception?
Edit:
I can create a copy of the mutableStateList to iterate over which works fine but since I'm not changing the size of the list, is it possible to do it in place?
Some possible workarounds are to use replaceAll to transform the list in-place (as long as you don't need the index), or just use an old-fashioned loop over indices if you do
val listA = mutableListOf("A","B","C")
// this works
listA.forEachIndexed { i, s ->
listA[i] = s.lowercase()
}
val listB = mutableStateListOf("A","B","C")
// this fails - as you noted
listB.forEachIndexed { i, s ->
listB[i] = s.lowercase()
}
// this works, as long as you don't need the index
listB.replaceAll { s -> s.lowercase() }
// this also works, and lets you have the index
for(i in listB.indices) {
listB[i] = listB[i].lowercase()
}

Kotlin list transform data stream

I'm building a chat platform, where I'm reading my list of messages from Local Room DB (ChatModel). I need to add date separators between these messages. I've to use multiple view holders and thus created a sealed class for differentiating items
sealed class ChatUiModel {
data class ChatItem(val message: ChatModel) : ChatUiModel()
data class DateSeparatorItem(val time: String) : ChatUiModel()
}
I require to convert the list with date separate items in between 2 models of the list, I'm not proficient with Collection functions in kotlin and confused between map/flatmap etc.
.observe(viewLifecycleOwner) { messages ->
messages.map {
// if item prev.date < item next.date
ChatUiModel.DateSeparatorItem(it.date.toReadableTime())
ChatUiModel.ChatItem(it)
}
chatAdapter.submitList(messages)
}
Reached to this
val items = mutableListOf<ChatUiModel>()
val data = messages.listIterator()
for (item in data) {
if (data.hasPrevious())
if (data.previous().time < item.time)
items.add(ChatUiModel.DateSeparatorItem(item.time))
items.add(ChatUiModel.ChatItem(item))
}
Timber.i("CHAT = $items")
An easy way to prepare the list can be:
messages
.groupBy { it.date }
.map { (date, chatModels) ->
listOf(DateSeparatorItem(date)) + chatModels.map { ChatItem(it) }
}
.flatten()
try it yourself
Here we first group all the messages by their data to get a Map<Long, List<ChatModel>. Then we map each entry of the map to a new list containing the DateSeparator and the ChatItems for that date. Finally, we flatten the entire list to get the desired List<ChatUiModel>.
In the code that I linked, I have used Long for the date. If you have a String you can easily interconvert them using java.time APIs.
If your messages list is not sorted initially, add a sortedBy function before groupBy to sort it first.
(this ended up long but I thought you'd like an explanation of what's going on - you can just skip to the end for the solutions if you want)
Ok, so this is a little tricky if you're not familiar with all the utility functions and general functional manipulation - what you're basically doing is transforming incoming messages into ChatItems, but you also want to compare each message to the previous one, and output a DateSeparatorItem first where necessary, right?
A straight map isn't going to work - that just transforms each item into another item (it's mapping one value to another), and sometimes you want to transform one item into two (a date item and a chat item).
You could map each message item into a list, and make that contain either a chat item, or a date+chat. So that would give you a list of lists, which you could then flatten so you just get all those items in order, in a single list. That's basically what flatmap does!
So now you need to be able to compare multiple messages, so you can check the dates. Kotlin has this windowed function that acts like a sliding view across your collection, so it can transform [1, 2, 3, 4] into [[1, 2], [2, 3], [3, 4]], and then you can work on those groups. There's a more convenient zipWithNext function that only produces Pairs instead of arbitrarily sized Lists - i.e. [(1, 2), (2, 3), (3, 4)], but windowed has a useful option - partialWindows allows that window to keep moving to the end of the list, even as it runs out of items to fill the full window:
listOf(1, 2, 3, 4).windowed(size=3, partialWindows=true).run(::println)
>> [[1, 2, 3], [2, 3, 4], [3, 4], [4]]
If we do this for a window of size 2, we get every original message, and also the one following it if there is one (zipWithNext will stop when it runs out of complete pairs):
listOf(1, 2, 3, 4).windowed(size=2, partialWindows=true).run(::println)
>> [[1, 2], [2, 3], [3, 4], [4]]
We can use this!
Your logic right now is taking a message and comparing it to the previous one to see if a date needs inserting before the chat item - I'd suggest flipping that around, and inserting a date after the current item by checking the next item's timestamp. That's because windowed is giving you each item along with the next one, so you don't get to look at the previous one.
We're working with a list here, and we need to compare the first item to the second one (checking if there even is one), but we can be a little bit cheeky and just compare list.first() with list.last(). We know there's gonna be either one or two items - and if there's only one item in the list (i.e. it's the last message) then we're comparing it with itself, and since we're only adding the date item if the timestamps are different... well they won't be if it's the same item! So there won't be any rogue date items added at the end. Probably worth documenting the code if you do that since it might not be clear - you can write some more explicit logic if you want.
Here's a few ways to do the final thing:
Kotlin Playground example
data class Message(val text: String, val time: Int)
val messages = listOf(
Message("hey", 1),
Message("u up", 1),
Message("lol", 3),
Message("wow", 10)
)
fun withMutableList() {
messages.windowed(size=2, partialWindows=true)
// or map followed by flatten()
.flatMap { items ->
val current = items.first()
val next = items.last()
// creating a mutable list with the chat item, optionally adding a date
mutableListOf<ChatUiModel>(ChatItem(current)).apply {
if (next.time > current.time) add(DateItem(next.time))
}
}
.forEach(::println)
}
fun withNulls() {
messages.windowed(size=2, partialWindows=true)
.flatMap { items ->
val current = items.first()
val next = items.last()
// either adding a date or a null, nulls get removed later
listOf(
ChatItem(current),
if (next.time > current.time) DateItem(next.time) else null
)
}
.filterNotNull()
.forEach(::println)
}
fun withSequence() {
sequence {
messages.windowed(size=2, partialWindows=true)
.forEach { items ->
val current = items.first()
val next = items.last()
// just yielding a stream of items, nice and neat!
yield(ChatItem(current))
if (next.time > current.time) yield(DateItem(next.time))
}
}.forEach(::println)
}
all giving this output:
ChatItem(message=Message(text=hey, time=1))
ChatItem(message=Message(text=u up, time=1))
DateItem(time=3)
ChatItem(message=Message(text=lol, time=3))
DateItem(time=10)
ChatItem(message=Message(text=wow, time=10))

Starts comparing from the first number, not the whole number in kotlin

Hello I need to compare 2 numbers and I used >, => but it doesn't compare whole number, it looks for the leftest(left) number and compare
for example the number is 92,236 and i want to compare it with 100,000, it says 92236 is bigger than 100,000 and it is because of the first number which is 9 and the first number of second number that is 1 so it says 100,000 is not bigger than 9236
here what I had done
class IncreaseMoneyFragment : Fragment() {
var decide = ""
val increaseEditText = mIncreaseMoneyBinding.increaseEdt.text.toString() (get value of edit text)
val currentPayment = it.payment (get loanPayment from database)
if (increaseEditText > currentPayment) {
Toast.makeText(activity, "more", Toast.LENGTH_SHORT).show()
val more = "بیشتر"
decide = more
} else {
Toast.makeText(activity, "less", Toast.LENGTH_SHORT).show()
val less = "کمتر"
decide = less
}
builder.setTitle(" مبلغ مورد نظر از مبلغ قسط وام $decide است. ادامه میدهید؟")
THANKS FOR HELPING ME :)
You are most likely comparing strings (text) and not numbers here. That's why it's using the alphabetical order instead of the integer order:
println("92236" > "100000") // true
println(92236 > 100000) // false
You probably want to convert your strings into integers instead:
if (increaseEditText.toInt() > currentPayment.toInt()) {
// ...
}
Note that toInt will crash if the strings are not actual numbers (for instance empty).
You can use toIntOrNull if you want more safety. It returns null if the string is not a number, so you can simply check for null and deal with this problem separately before comparing.

Filter list of string with contains and sorted by contains position

I have list of string say
var list = mutableListOf("clock", "kite", "rekt")
Now, I need to filter list according to character. Currently I am filtering by:
list.filter { it.contains("k") }
It gives me result
[clock, kite, rekt]
But this is where I have problem, I want to filter the list and also sort by its character position and expected result is
[kite, rekt, clock]
where the character "k" comes first is first at the list and so on.
I am confused what to do. Thank you in advance.
To sort it you have to use a Comparator. Comparator contract is described here in method compare here
Compares its two arguments for order. Returns zero if the arguments are equal, a negative number if the first argument is less than the second, or a positive number if the first argument is greater than the second.
So in our case we take two strings a and b and we look at what position the k is present.
Example:
a = "clock" // position of 'k' is 4
b = "kite" // position of 'k' is 0
Comparing those two 4 - 0 = 4 results in a positive number which means first argument is greater than the second.
a = "kite" // position of 'k' is 0
b = "rekt" // position of 'k' is 3
Comparing those two 0 - 3 = -3 results in a negative number which means first argument is less than the second.
Code:
var list = mutableListOf("clock", "kite", "rekt", "abcd")
list.retainAll { it.contains("k") }
// Ascending Order
list.sortWith(Comparator {a, b -> a.indexOf('k') - b.indexOf('k')})
println(list) // [kite, rekt, clock]
// Descending Order
list.sortWith(Comparator {a, b -> b.indexOf('k') - a.indexOf('k')})
println(list) // [clock, rekt, kite]
// Cleaner and more concise syntax thanks to #Tenfour04
list.sortWith( compareBy { it.indexOf('k') } )
list.sortWith( compareByDescending { it.indexOf('k') } )

Categories

Resources