How can I fix this particular "Val cannot be reassigned" error? - android

I have this code that keeps giving me a "Val cannot be reassigned" error but I can't seem to change the variable to a var instead of val. I simply want to be able to set a string value to my cell reference so I can access the values later like this myStringsArrayList.add(deviceData.cellOne).
Here is my code:
val cells = listOf(
deviceData.cellOne,
deviceData.cellTwo,
deviceData.cellThree,
deviceData.cellFour,
deviceData.cellFive,
deviceData.cellSix,
deviceData.cellSeven,
deviceData.cellEight,
deviceData.cellNine,
deviceData.cellTen,
deviceData.cellEleven,
deviceData.cellTwelve,
deviceData.cellThirteen,
deviceData.cellFourteen
)
for ((i, cell) in cells.withIndex()) {
val value = data[2 + i].toDouble() / 100 + 3.52
val cellNumberString = (i + 1).toString()
val formattedString = "Cell $cellNumberString: %.2fV".format(value)
cell = formattedString // THIS IS WHERE THE PROBLEM IS (cell is a val)
}
Does anyone know how I can get around this and achieve the functionality that I want?
I tried using a listIterator() but it hasn't seemed to work the way that I want it to.
Here is my attempt with the listIterator():
val cells = mutableListOf(
deviceData.cellOne,
deviceData.cellTwo,
deviceData.cellThree,
deviceData.cellFour,
deviceData.cellFive,
deviceData.cellSix,
deviceData.cellSeven,
deviceData.cellEight,
deviceData.cellNine,
deviceData.cellTen,
deviceData.cellEleven,
deviceData.cellTwelve,
deviceData.cellThirteen,
deviceData.cellFourteen)
val iterate = cells.listIterator()
while (iterate.hasNext()) {
var cell = iterate.next()
val value = data[2 + iterate.nextIndex()].toDouble() / 100 + 3.52
val cellNumberString = (iterate.nextIndex() + 1).toString()
val formattedString = "Cell $cellNumberString: %.2fV".format(value)
cell = formattedString
}

You can't make that a var, and there's no reason to anyway!
for ((i, cell) in cells.withIndex()) {
...
cell = formattedString // THIS IS WHERE THE PROBLEM IS (cell is a val)
}
You're creating a for loop there on a collection, with an index, and giving it a block of code to run for each loop. So when the loop runs, you're provided with two parameters - the current item from the collection, and its index in that collection.
These are just internal variables for use in the loop - you can't reassign them, because they're the values being passed in. Even if you could, you'd just be changing the values of those local variables.
What you're probably trying to do is update the cells list, taking the current item in the loop, finding it in cells, and replacing it. You'd have to actually update cells to do that! Change the list itself. You could do that with cells[i] = formattedString - but because you're currently iterating over that cells collection, you shouldn't modify it!
You could copy the source list, but the typical Kotlin way is to create a new list, using map (which transforms values):
cells.mapIndexed { i, cell ->
val value = data[2 + i].toDouble() / 100 + 3.52
val cellNumberString = (i + 1).toString()
// last expression is the return value, i.e. the formatted string
"Cell $cellNumberString: %.2fV".format(value)
}
That will spit out a new (immutable) list where each cell has been mapped to that formatted string version.
You could make cells a var and just reassign it:
cells = cells.mapIndexed { ... }
or just chain the map call when you initialise the val, so that end result is what gets assigned:
val cells = listOf(
...
).mapIndexed { ... }
But you're not actually using cell in that loop anyway, just using the index to generate values. You can create a list like this:
val data = List(14) { i ->
val value = data[2 + i].toDouble() / 100 + 3.52
// you can calculate the index inside the string by using braces
"Cell ${i + 1}: %.2fV".format(value)
}
It all depends whether you need to keep that list of devicedata values around for anything (if so use it to create another list)

You may wanna use Interator this way:
val list = mutableListOf("One", "Two", "Three", "Four")
println(list.joinToString(" "))
val iterator = list.listIterator()
while(iterator.hasNext()) {
val value = iterator.next()
if (value == "Two") {
iterator.set("xxxxxx")
}
}
println(list.joinToString(" "))
Use the 'set' method.

Related

Create list/array of Textviews to change .text via index

So I was hoping to make a list/array of textviews so that I can iterate a loop and set the .text value of the TextViews as I go. Otherwise I would have to set the values in the code statically which would be a whole lot messier and potentially not even feasible for my needs.
So in the code below the idea would be to iterate the loop and when the correct value is confirmed that [index] would then set the corresponding
var refillToken : Double = (0).toDouble()
var tweetStored : BooleanArray = BooleanArray(20)
var tweetActive : BooleanArray = BooleanArray(20)
var userID: MutableList<String> = mutableListOf("")
var textViewToken = 0
while (refillToken > 0) {
var token: Int = 0
while (token < (tweetStored.size)) {
if (tweetStored[token] == true) {
tweetActive[token] = true
textView[textViewToken].text = userID[token]
textViewToken++
refillToken--
token++
if (refillToken < 0) {
break
}
}
}
}
}
I know my loop is probably messy by sane people standards but it makes sense to me and (hopefully) isn't the issue at play. Have found a few articles or ideas searching for the past two hours but they're either 10 years old (and I think deprecated), for java or don't work for whatever reason.
You need to get a value and then add it to the textview and change this value after every action on the page.
Use variable assignment for this task

Iterative queue-based flood fill algorithm 'expandToNeighborsWithMap()' function is unusually slow

I am creating a pixel art editor for Android, and as for all pixel art editors, a paint bucket (fill tool) is a must need.
To do this, I did some research on flood fill algorithms online.
I stumbled across the following video which explained how to implement an iterative flood fill algorithm in your code. The code used in the video was JavaScript, but I was easily able to convert the code from the video to Kotlin:
https://www.youtube.com/watch?v=5Bochyn8MMI&t=72s&ab_channel=crayoncode
Here is an excerpt of the JavaScript code from the video:
Converted code:
Tools.FILL_TOOL -> {
val seedColor = instance.rectangles[rectTapped]?.color ?: Color.WHITE
val queue = LinkedList<XYPosition>()
queue.offer(MathExtensions.convertIndexToXYPosition(rectangleData.indexOf(rectTapped), instance.spanCount.toInt()))
val selectedColor = getSelectedColor()
while (queue.isNotEmpty() && seedColor != selectedColor) { // While the queue is not empty the code below will run
val current = queue.poll()
val color = instance.rectangles.toList()[convertXYDataToIndex(instance, current)].second?.color ?: Color.WHITE
if (color != seedColor) {
continue
}
instance.extraCanvas.apply {
instance.rectangles[rectangleData[convertXYDataToIndex(instance, current)]] = defaultRectPaint // Colors in pixel with defaultRectPaint
drawRect(rectangleData[convertXYDataToIndex(instance, current)], defaultRectPaint)
for (index in expandToNeighborsWithMap(instance, current)) {
val candidate = MathExtensions.convertIndexToXYPosition(index, instance.spanCount.toInt())
queue.offer(candidate)
}
}
}
}
Now, I want to address two major issues I'm having with the code of mine:
Performance
Flooding glitch (fixed by suggestion from person in the comments)
Performance
A flood fill needs to be very fast and shouldn't take less than a second, the problem is, say I have a canvas of size 50 x 50, and I decide to fill in the whole canvas, it can take up to 8 seconds or more.
Here is some data I've compiled for the time it's taken to fill in a whole canvas given the spanCount value:
spanCount
approx time taken in seconds to fill whole canvas
10
<1 seconds
20
~2 seconds
40
~6 seconds
60
~15 seconds
100
~115 seconds
The conclusion from the data is that the flood fill algorithm is unusually slow.
To find out why, I decided to test out which parts of the code are taking the most time to compile. I came to the conclusion that the expandToNeighbors function is taking the most time out of all the other tasks:
Here is an excerpt of the expandToNeighbors function:
fun expandToNeighbors(instance: MyCanvasView, from: XYPosition): List<Int> {
var asIndex1 = from.x
var asIndex2 = from.x
var asIndex3 = from.y
var asIndex4 = from.y
if (from.x > 1) {
asIndex1 = xyPositionData!!.indexOf(XYPosition(from.x - 1, from.y))
}
if (from.x < instance.spanCount) {
asIndex2 = xyPositionData!!.indexOf(XYPosition(from.x + 1, from.y))
}
if (from.y > 1) {
asIndex3 = xyPositionData!!.indexOf(XYPosition(from.x, from.y - 1))
}
if (from.y < instance.spanCount) {
asIndex4 = xyPositionData!!.indexOf(XYPosition(from.x, from.y + 1))
}
return listOf(asIndex1, asIndex2, asIndex3, asIndex4)
}
To understand the use of the expandToNeighbors function, I would recommend watching the video that I linked above.
(The if statements are there to make sure you won't get an IndexOutOfBoundsException if you try and expand from the edge of the canvas.)
This function will return the index of the north, south, west, and east pixels from the xyPositionData list which contains XYPosition objects.
(The black pixel is the from parameter.)
The xyPositionData list is initialized once in the convertXYDataToIndex function, here:
var xyPositionData: List<XYPosition>? = null
var rectangleData: List<RectF>? = null
fun convertXYDataToIndex(instance: MyCanvasView, from: XYPosition): Int {
if (rectangleData == null) {
rectangleData = instance.rectangles.keys.toList()
}
if (xyPositionData == null) {
xyPositionData = MathExtensions.convertListOfSizeNToListOfXYPosition(
rectangleData!!.size,
instance.spanCount.toInt()
)
}
return xyPositionData!!.indexOf(from)
}
So, the code works fine (kind of) but the expandToNeighbors function is very slow, and it is the main reason why the flood fill algorithm is taking a long time.
My colleague suggested that indexOf may be slowing everything down, and that I should probably switch to a Map-based implementation with a key being XYPosition and a value being Int representing the index, so I replaced it with the following:
fun expandToNeighborsWithMap(instance: MyCanvasView, from: XYPosition): List<Int> {
var asIndex1 = from.x
var asIndex2 = from.x
var asIndex3 = from.y
var asIndex4 = from.y
if (from.x > 1) {
asIndex1 = rectangleDataMap!![XYPosition(from.x - 1, from.y)]!!
}
if (from.x < instance.spanCount) {
asIndex2 = rectangleDataMap!![XYPosition(from.x + 1, from.y)]!!
}
if (from.y > 1) {
asIndex3 = rectangleDataMap!![XYPosition(from.x, from.y - 1)]!!
}
if (from.y < instance.spanCount) {
asIndex4 = rectangleDataMap!![XYPosition(from.x, from.y + 1)]!!
}
return listOf(asIndex1, asIndex2, asIndex3, asIndex4)
}
It functions the same way, only this time it uses a Map which is initialized here:
var xyPositionData: List<XYPosition>? = null
var rectangleData: List<RectF>? = null
var rectangleDataMap: Map<XYPosition, Int>? = null
fun convertXYDataToIndex(instance: MyCanvasView, from: XYPosition): Int {
if (rectangleData == null) {
rectangleData = instance.rectangles.keys.toList()
}
if (xyPositionData == null) {
xyPositionData = MathExtensions.convertListOfSizeNToListOfXYPosition(
rectangleData!!.size,
instance.spanCount.toInt()
)
}
if (rectangleDataMap == null) {
rectangleDataMap = MathExtensions.convertListToMap(
rectangleData!!.size,
instance.spanCount.toInt()
)
}
return xyPositionData!!.indexOf(from)
}
Converting the code to use a map increased the speed by around 20%, although the algorithm is still slow.
After trying to make the algorithm work faster, I'm out of ideas and I'm unsure why the expandToNeighbors function is taking a long time.
Implementation-wise it is quite messy unfortunately because of the whole list index to XYPosition conversions, but at least it works - the only problem is the performance.
So I have two one major problem.
I've actually pushed the fill tool to GitHub as a KIOL (Known Issue or Limitation), so the user can use the fill tool if they want, but they need to be aware of the limitations/issues. This is so anyone can have a look at my code and reproduce the bugs.
Link to repository:
https://github.com/realtomjoney/PyxlMoose
Edit
I understand that this question is extremely difficult to answer and will require a lot of thinking. I would recommend cloning PyxlMoose and reproduce the errors, then work from there. Relying on the code snippets isn't enough.
Formula for converting XY position to an index
Somebody in the comments suggested a formula for converting an XYPosition to an index value, I came up with the following method which works:
fun convertXYPositionToIndex(xyPosition: XYPosition, spanCount: Int): Int {
val positionX = xyPosition.x
val positionY = xyPosition.y
return (spanCount - positionY) + (spanCount * (positionX - 1))
}
The only problem is - it increases the speed by around 50% but it's still taking around 10-15 seconds to fill in an area of 80 by 80 pixels, so it has helped to a large degree although it's still very slow.
I think the performance issue is because of expandToNeighbors method generates 4 points all the time. It becomes crucial on the border, where you'd better generate 3 (or even 2 on corner) points, so extra point is current position again. So first border point doubles following points count, second one doubles it again (now it's x4) and so on.
If I'm right, you saw not the slow method work, but it was called too often.
How I fixed it:
Getting rid of the toList() calls.
Creating an convertXYPositionToIndex() function.
Here is my new code:
Tools.FILL_TOOL -> {
val seedColor = instance.rectangles[rectTapped]?.color ?: Color.WHITE
val queue = LinkedList<XYPosition>()
val spanCount = instance.spanCount.toInt()
queue.offer(MathExtensions.convertIndexToXYPosition(rectangleData.indexOf(rectTapped), spanCount))
val selectedColor = getSelectedColor()
while (queue.isNotEmpty() && seedColor != selectedColor) {
val current = queue.poll()
val color = instance.rectangles[rectangleData[convertXYDataToIndex(spanCount, current)]]?.color ?: Color.WHITE
if (color != seedColor) {
continue
}
instance.rectangles[rectangleData[convertXYDataToIndex(spanCount, current)]] = defaultRectPaint // Colors in pixel with defaultRectPaint
instance.extraCanvas.drawRect(rectangleData[MathExtensions.convertXYPositionToIndex(current, spanCount)], defaultRectPaint)
for (index in expandToNeighborsWithMap(spanCount, current)) {
val candidate = MathExtensions.convertIndexToXYPosition(index, spanCount)
queue.offer(candidate)
}
}
val timeTakenForThis = (System.currentTimeMillis()-startTime)
totalTime += timeTakenForThis
}
Expand to neighbors func:
fun expandToNeighborsWithMap(spanCount: Int, from: XYPosition): List<Int> {
val toReturn = mutableListOf<Int>()
if (from.x > 1) {
toReturn.add(MathExtensions.convertXYPositionToIndex(XYPosition(from.x - 1, from.y), spanCount))
}
if (from.x < spanCount) {
toReturn.add(MathExtensions.convertXYPositionToIndex(XYPosition(from.x + 1, from.y), spanCount))
}
if (from.y > 1) {
toReturn.add(MathExtensions.convertXYPositionToIndex(XYPosition(from.x, from.y - 1), spanCount))
}
if (from.y < spanCount) {
toReturn.add(MathExtensions.convertXYPositionToIndex(XYPosition(from.x, from.y + 1), spanCount))
}
return toReturn
}
It takes less than a second for canvas sizes of 100 by 100 and 200 by 200, so I'd say it's in the usable stage now.
I would say this is one of the simplest Android flood fill algorithms out there to understand, so if anyone is making an app similar to mine and they want a flood fill tool they can copy my code.
A guy in the comments called EvilTalk helped me with this.

Kotlin float number with 2 decimals to string without precision loss

In Android-Kotlin I am getting float number from backend (for example num = 10000000.47)
When I try to String.format it and add that number in my balanceTextview it shows it with exponent (something like 1.0E10).
I want to show number normally without exponent and with 2 decimals. (Without presicion loss!)
Tried to use DecimalFormat("#.##") but it didn't help me. Maybe I'm doing something wrong?
num = 10000000.47f
val dec = DecimalFormat("#.##")
var result = dec.format(num)
my result is: 10000000
It losts my decimal places
The issue is your number type. According to the documentation:
For variables initialized with fractional numbers, the compiler infers the Double type. To explicitly specify the Float type for a value, add the suffix f or F. If such a value contains more than 6-7 decimal digits, it will be rounded.
With an example that shows how information may get lost:
val pi = 3.14 // Double
val e = 2.7182818284 // Double
val eFloat = 2.7182818284f // Float, actual value is 2.7182817
If the value is specified as Double instead of Float, i.e.
val num = 10000000.47
instead of
val num = 10000000.47f
then your approach works as expected, but could be shortened to:
"%.2f".format(num)
(note that the shorter version will also print "100" as "100.00" which is different from your approach but potentially still desired behaviour)
If you receive a Float from the backend then the information is already lost on your side. Otherwise you should be able to fix the issue by improved parsing.
The extension function format is only available in the JVM. In Kotlin/native, you can use this instead:
fun Float.toPrecision(precision: Int) =
this.toDouble().toPrecision(precision)
fun Double.toPrecision(precision: Int) =
if (precision < 1) {
"${this.roundToInt()}"
} else {
val p = 10.0.pow(precision)
val v = (abs(this) * p).roundToInt()
val i = floor(v / p)
var f = "${floor(v - (i * p)).toInt()}"
while (f.length < precision) f = "0$f"
val s = if (this < 0) "-" else ""
"$s${i.toInt()}.$f"
}

List items iteration speed too slow when having another loop inside

I have two list, one with all possible devices and another with just few devices. I need pass final list with this condition:
if full list == one of the items in smaller list, make this item "active" too true, else leave it false.
I have no problem when working with full list >500 devices and small list >50, but when I have for example 2000 devices everything start to be too slow (on Google Pixel 2XL I need to wait about 6 seconds to job finish).
Question: how can I increase this loop speed?
What I have done so far:
devicesList.forEach { device ->
device.selected = false
items.forEach { it ->
if(it.id == device.id){
device.selected = true
}
}
But this is too slow for larger data
You can speed it up a bit by not using forEach, which uses an interator and instead use a for loop. You can also break once you locate your id, assuming they are unique
for (i in 0 until devicesList.size) {
val device = devicesList[i]
for (j in 0 until items.size) {
val item = items[j]
if (item.id == device.id) {
device.selected = true
break
}
}
}
Assuming your ids are unique, you could also make a duplicate of the items list and drop those that have been located, so each loop is shorter, like this
val copy = items.toMutableList()
for (i in 0 until devicesList.size) {
val device = devicesList[i]
for (j in 0 until copy.size) {
val item = copy[j]
if (item.id == device.id) {
device.selected = true
copy.remove(item)
break
}
}
}
You could also consider creating a map where the key is your id so you do not have to loop and instead you retrieve the item by id directly. You have to weight the cost of creating the map in the first place.
val map = items.associateBy { it.id }
for (i in 0 until devicesList.size) {
val device = devicesList[i]
device.selected = map[device.id] != null
}
Besides this, you should also move your logic to a background thread and wait for it to complete.
If all you need is make selected = true if a device's id exists in the list items, you can get all the ids of items like this:
val ids = items.map { it.id }
and then loop through devices:
devicesList.forEach { it.selected = it.id in ids }

Iterating over list with lambda forEach Kotlin

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}

Categories

Resources