I have a codebase where all my data calculations and data formatting occur within a single function. Are there any performance benefits to separating these out into separate functions or does separating them only improve readability?
I know that I should separate them but I don't really know all the reasons why.
Here is the function I am referring to:
private fun processData(data: ByteArray) {
progressBar.visibility = GONE
Log.d(TAG, "displayDiagnosticData: ")
val bmsVersionView = findViewById<TextView>(R.id.textview_bms_version)
val boardVersionView = findViewById<TextView>(R.id.textview_board_version)
val cellOneView = findViewById<TextView>(R.id.textview_cell_1)
val cellTwoView = findViewById<TextView>(R.id.textview_cell_2)
val cellThreeView = findViewById<TextView>(R.id.textview_cell_3)
val cellFourView = findViewById<TextView>(R.id.textview_cell_4)
val cellFiveView = findViewById<TextView>(R.id.textview_cell_5)
val cellSixView = findViewById<TextView>(R.id.textview_cell_6)
val cellSevenView = findViewById<TextView>(R.id.textview_cell_7)
val cellEightView = findViewById<TextView>(R.id.textview_cell_8)
val cellNineView = findViewById<TextView>(R.id.textview_cell_9)
val cellTenView = findViewById<TextView>(R.id.textview_cell_10)
val cellElevenView = findViewById<TextView>(R.id.textview_cell_11)
val cellTwelveView = findViewById<TextView>(R.id.textview_cell_12)
val cellThirteenView = findViewById<TextView>(R.id.textview_cell_13)
val cellFourteenView = findViewById<TextView>(R.id.textview_cell_14)
val packTotalView = findViewById<TextView>(R.id.textview_diagnostic_voltage)
val packSocView = findViewById<TextView>(R.id.textview_diagnostic_soc)
val chargeTempView = findViewById<TextView>(R.id.textview_charge_temp)
val dischargeTempView = findViewById<TextView>(R.id.textview_discharge_temp)
val chargeCurrentView = findViewById<TextView>(R.id.textview_diagnostic_charge_current)
// val dischargeCurrentView = findViewById<TextView>(R.id.textview_diagnostic_discharge_current)
val dischargeCircuitStateView = findViewById<TextView>(R.id.textview_discharge_circuit)
val chargeCircuitStateView = findViewById<TextView>(R.id.textview_charge_circuit)
val balanceCircuitStateView = findViewById<TextView>(R.id.textview_balance_circuit)
val emptyCircuitStateView = findViewById<TextView>(R.id.textview_empty_circuit)
val bmsVersion = data[0] + (data[1] * 256)
val cellOne = data[2].toDouble() / 100 + 3.52
val cellTwo = data[3].toDouble() / 100 + 3.52
val cellThree = data[4].toDouble() / 100 + 3.52
val cellFour = data[5].toDouble() / 100 + 3.52
val cellFive = data[6].toDouble() / 100 + 3.52
val cellSix = data[7].toDouble() / 100 + 3.52
val cellSeven = data[8].toDouble() / 100 + 3.52
val cellEight = data[9].toDouble() / 100 + 3.52
val cellNine = data[10].toDouble() / 100 + 3.52
val cellTen = data[11].toDouble() / 100 + 3.52
val cellEleven = data[12].toDouble() / 100 + 3.52
val cellTwelve = data[13].toDouble() / 100 + 3.52
val cellThirteen = data[14].toDouble() / 100 + 3.52
val cellFourteen = data[15].toDouble() / 100 + 3.52
val totalVoltage = 47.8 + (data[16].toDouble() / 10)
val chargeTempCelsius = data[19]
val dischargeTempCelsius = data[20]
val chargeTempFahr = (chargeTempCelsius * 9.0 / 5.0) + 32.0
val dischargeTempFahr = (dischargeTempCelsius * 9.0 / 5.0) + 32.0
val chargeCurrent = data[21]
// val dischargeCurrent = (data[23].toDouble() * 100 + data[22]).toInt()
val chargeCircuitState = data[25].toInt()
val dischargeCircuitState = data[26].toInt()
val balanceCircuitState = data[27].toInt()
val emptyCircuitState = data[28].toInt()
val chargeCircuit: String = if (chargeCircuitState == 1) {
"On"
}else {
"Off"
}
val dischargeCircuit: String = if (dischargeCircuitState == 1) {
"On"
}else {
"Off"
}
val balanceCircuit: String = if (balanceCircuitState == 1) {
"On"
}else {
"Off"
}
val emptyCircuit: String = if (emptyCircuitState == 1) {
"On"
}else {
"Off"
}
val bmsVersionString = SpannableString("BMS Version: $bmsVersion")
bmsVersionString.setSpan(StyleSpan(Typeface.BOLD), 0, 11, 0)
val boardVersionString = SpannableString("Board Version: 2.1")
boardVersionString.setSpan(StyleSpan(Typeface.BOLD), 0, 13, 0)
val cellOneString = SpannableString("Cell 1: %.2fV".format(cellOne))
cellOneString.setSpan(StyleSpan(Typeface.BOLD), 0, 6, 0)
val cellTwoString = SpannableString("Cell 2: %.2fV".format(cellTwo))
cellTwoString.setSpan(StyleSpan(Typeface.BOLD), 0, 6, 0)
val cellThreeString = SpannableString("Cell 3: %.2fV".format(cellThree))
cellThreeString.setSpan(StyleSpan(Typeface.BOLD), 0, 6, 0)
val cellFourString = SpannableString("Cell 4: %.2fV".format(cellFour))
cellFourString.setSpan(StyleSpan(Typeface.BOLD), 0, 6, 0)
val cellFiveString = SpannableString("Cell 5: %.2fV".format(cellFive))
cellFiveString.setSpan(StyleSpan(Typeface.BOLD), 0, 6, 0)
val cellSixString = SpannableString("Cell 6: %.2fV".format(cellSix))
cellSixString.setSpan(StyleSpan(Typeface.BOLD), 0, 6, 0)
val cellSevenString = SpannableString("Cell 7: %.2fV".format(cellSeven))
cellSevenString.setSpan(StyleSpan(Typeface.BOLD), 0, 6, 0)
val cellEightString = SpannableString("Cell 8: %.2fV".format(cellEight))
cellEightString.setSpan(StyleSpan(Typeface.BOLD), 0, 6, 0)
val cellNineString = SpannableString("Cell 9: %.2fV".format(cellNine))
cellNineString.setSpan(StyleSpan(Typeface.BOLD), 0, 6, 0)
val cellTenString = SpannableString("Cell 10: %.2fV".format(cellTen))
cellTenString.setSpan(StyleSpan(Typeface.BOLD), 0, 7, 0)
val cellElevenString = SpannableString("Cell 11: %.2fV".format(cellEleven))
cellElevenString.setSpan(StyleSpan(Typeface.BOLD), 0, 7, 0)
val cellTwelveString = SpannableString("Cell 12: %.2fV".format(cellTwelve))
cellTwelveString.setSpan(StyleSpan(Typeface.BOLD), 0, 7, 0)
val cellThirteenString = SpannableString("Cell 13: %.2fV".format(cellThirteen))
cellThirteenString.setSpan(StyleSpan(Typeface.BOLD), 0, 7, 0)
val cellFourteenString = SpannableString("Cell 14: %.2fV".format(cellFourteen))
cellFourteenString.setSpan(StyleSpan(Typeface.BOLD), 0, 7, 0)
val packTotalString = SpannableString("Pack Total: %.1fV".format(totalVoltage))
packTotalString.setSpan(StyleSpan(Typeface.BOLD), 0, 10, 0)
val socString = SpannableString("SOC: ${data[17].toInt()}%")
socString.setSpan(StyleSpan(Typeface.BOLD), 0, 3, 0)
val chargeTempString = SpannableString("Charge Temp: ${chargeTempFahr.toInt()}" + "°F")
chargeTempString.setSpan(StyleSpan(Typeface.BOLD), 0, 11, 0)
val dischargeTempString = SpannableString("Discharge Temp: ${dischargeTempFahr.toInt()}" + "°F")
dischargeTempString.setSpan(StyleSpan(Typeface.BOLD), 0, 15, 0)
val chargeCurrentString = SpannableString("Charge Current: $chargeCurrent" + "A")
chargeCurrentString.setSpan(StyleSpan(Typeface.BOLD), 0, 14, 0)
// val dischargeCurrentString = "Discharge Current: $dischargeCurrent" + "A"
val chargeCircuitStateString = SpannableString("Charge Circuit: $chargeCircuit")
chargeCircuitStateString.setSpan(StyleSpan(Typeface.BOLD), 0, 14, 0)
val dischargeCircuitStateString = SpannableString("Discharge Circuit: $dischargeCircuit")
dischargeCircuitStateString.setSpan(StyleSpan(Typeface.BOLD), 0, 17, 0)
val balanceCircuitStateString = SpannableString("Balance Circuit: $balanceCircuit")
balanceCircuitStateString.setSpan(StyleSpan(Typeface.BOLD), 0, 15, 0)
val emptyCircuitStateString = SpannableString("Empty Circuit: $emptyCircuit")
emptyCircuitStateString.setSpan(StyleSpan(Typeface.BOLD), 0, 13, 0)
bmsVersionView.text = bmsVersionString
boardVersionView.text = boardVersionString
cellOneView.text = cellOneString
cellTwoView.text = cellTwoString
cellThreeView.text = cellThreeString
cellFourView.text = cellFourString
cellFiveView.text = cellFiveString
cellSixView.text = cellSixString
cellSevenView.text = cellSevenString
cellEightView.text = cellEightString
cellNineView.text = cellNineString
cellTenView.text = cellTenString
cellElevenView.text = cellElevenString
cellTwelveView.text = cellTwelveString
cellThirteenView.text = cellThirteenString
cellFourteenView.text = cellFourteenString
packTotalView.text = packTotalString
packSocView.text = socString
chargeTempView.text = chargeTempString
dischargeTempView.text = dischargeTempString
chargeCurrentView.text = chargeCurrentString
// dischargeCurrentView.text = dischargeCurrentString
chargeCircuitStateView.text = chargeCircuitStateString
dischargeCircuitStateView.text = dischargeCircuitStateString
balanceCircuitStateView.text = balanceCircuitStateString
emptyCircuitStateView.text = emptyCircuitStateString
}
Performance? No. Maintainability? Yes. Separation of concerns is one of the core tenets of modern software architecture. Combining the two things makes a difficult to read, difficult to debug mess. Separating them allows you to concentrate on one thing at a time, and makes it easier for people maintaining the code (which may even be you 6 months from now when fixing a bug and you've forgotten how it works) to understand the logic and flow of the program.
That function you posted would not be accepted in any professional codebase. It's too long, it does too many things. It needs to be broken up.
Your bigger problem there is that you're repeating yourself, a lot. Not only does that make the whole thing longer, and arguably harder to read, it also makes it hard to maintain like Gabe says, and makes it far more likely a bug will sneak in there. Imagine you needed to add another row of cells - there's a lot of boilerplate involved, a lot of repetitive work, and that's where humans tend to mess up
Like as an example of the kind of thing you can do - see how your cell data is basically taken from a range of values in data, with the same calculation applied to each? You could do this instead:
val cells = (2..15).map { index -> data[index].toDouble() / 100 + 3.52 }
or, to keep things more explicit and separate:
// Except give this a good name since it's doing something specific
// Because this is some kind of conversion formula, putting it in its own function
// makes it easy to maintain and document, and it's clear exactly what it is
fun getCellValue(dataValue: Int) = dataValue.toDouble() / 100 + 3.52
val cells = (2..15).map { index -> getCellValue(data[index]) }
Now you have one or two lines replacing 14 lines of initialisation code. It's also easier to make changes - if the format of data changes, you can easily change the range of indices to use, or the formula applied to each value. It's one place, instead of on each line, where you have to update each one and make sure you haven't made a typo or skipped one.
And when you have structured data like that, it can make your other code easier to work with too - because instead of needing to work with separate variables, you can work with indices and loop over things instead of writing each step out:
// no need for a separate line for each with hardcoded values if you can work it out
// (Because it's a separate function, you can use it for the other display lines too,
// it's not cell-specific)
fun SpannableString.applyBoldPrefix() = apply {
val colonIndex = indexOf(':')
if (colonIndex >= 0) setSpan(StyleSpan(Typeface.BOLD), 0, colonIndex, 0)
}
// you could also just pass in the index and look up cells[index] here -
// this is a more functional approach, but whatever's good
fun getCellDisplayString(cellIndex: Int, cellData: Double) =
SpannableString("Cell ${cellIndex + 1}: %.2fV".format(cellData))
.applyBoldPrefix()
// lots of ways to define/grab a set of views programmatically - putting them all
// in a container you can look up is one way. You can also generate resource identifier
// strings, like "R.id.textview_cell_$i" and look that up
val cellTextViews = findViewById<ViewGroup>(R.id.containerHoldingCells)
.children.filterIsInstance<TextView>
// now you can just iterate over stuff to complete the repetitive task
cellTextViews.forEachIndexed { i, view ->
view.text = getCellDisplayString(i, cells[i])
}
That's about half your code right there. I wouldn't necessarily structure everything this way (I feel like given you're working with a data format here, a more formal structure definition would be helpful, and you can generalise more too) and it's a bit rough-and-ready, but hopefully it gives you a general sense of how you can cut things down, but also make it easier to maintain them and try out changes
I condensed my code a lot more and created functions to process separate chunks of data. I also was able to get rid of a lot of my repetitious code. For anyone who is interested, here is the updated code:
private fun String.withStyling() = SpannableString(this).apply {
setSpan(StyleSpan(Typeface.BOLD), 0, indexOfFirst { it == ':' }, 0)
}
private fun processDiagnosticData(data: ByteArray) {
binding.progressBarCyclic.visibility = GONE
Log.d(TAG, "displayDiagnosticData: ")
processCells(data)
processTemps(data[19], data[20])
processCircuits(data)
processOtherData(data)
}
// Process cells 1-14 and display.
private fun processCells(data: ByteArray) {
val cellViews = listOf(
binding.textviewCell1,
binding.textviewCell2,
binding.textviewCell3,
binding.textviewCell4,
binding.textviewCell5,
binding.textviewCell6,
binding.textviewCell7,
binding.textviewCell8,
binding.textviewCell9,
binding.textviewCell10,
binding.textviewCell11,
binding.textviewCell12,
binding.textviewCell13,
binding.textviewCell14
)
for ((i, cellView) in cellViews.withIndex()) {
val value = data[2 + i].toDouble() / 100 + 3.52
val cellNumberString = (i + 1).toString()
val formattedString = "Cell $cellNumberString: %.2fV".format(value).withStyling()
cellView.text = formattedString
}
}
// Process charge/discharge temps and display.
private fun processTemps(chargeTempCel: Byte, dischargeTempCel: Byte) {
val chargeTempFahr = chargeTempCel * 9.0 / 5.0 + 32.0
val dischargeTempFahr = dischargeTempCel * 9.0 / 5.0 + 32.0
val chargeTempString = "Charge Temp: $chargeTempFahr°F".withStyling()
val dischargeTempString = "Discharge Temp: $dischargeTempFahr°F".withStyling()
binding.textviewChargeTemp.text = chargeTempString
binding.textviewDischargeTemp.text = dischargeTempString
}
// Process circuit states and display.
private fun processCircuits(data: ByteArray) {
val circuitViews = listOf(
binding.textviewChargeCircuit,
binding.textviewDischargeCircuit,
binding.textviewBalanceCircuit,
binding.textviewEmptyCircuit
)
val circuitNames = listOf(
"Charge Circuit: ",
"Discharge Circuit: ",
"Balance Circuit: ",
"Empty Circuit: "
)
for ((i, circuit) in circuitViews.withIndex()) {
val value = if (data[25 + i].toInt() == 1) {
"On"
} else {
"Off"
}
val formattedString = (circuitNames[i] + value).withStyling()
circuit.text = formattedString
}
}
// Process the rest of the data and display.
private fun processOtherData(data: ByteArray) {
val totalVoltage = 47.8 + (data[16].toDouble() / 10)
val packSoc = data[17].toInt()
val chargeCurrent = data[21]
// val dischargeCurrent = (data[23].toDouble() * 100 + data[22]).toInt()
val bmsVersionString = "BMS Version: ${data[0] + (data[1] * 256)}".withStyling()
val boardVersionString = "Board Version: 2.1".withStyling()
val totalVoltageString = "Pack Total: %.1fV".format(totalVoltage).withStyling()
val packSocString = "SOC: ${packSoc}%".withStyling()
val chargeCurrentString = "Charge Current: ${chargeCurrent}A".withStyling()
// val dischargeCurrent = "Discharge Current: $dischargeCurrentA".withStyling()
binding.textviewBmsVersion.text = bmsVersionString
binding.textviewBoardVersion.text = boardVersionString
binding.textviewDiagnosticVoltage.text = totalVoltageString
binding.textviewDiagnosticSoc.text = packSocString
binding.textviewDiagnosticChargeCurrent.text = chargeCurrentString
// binding.textviewDiagnosticDischargeCurrent.text = dischargeCurrentString
}
The old Sensor.TYPE_ORIENTATION sensor returned a pitch between -180° and 180°. This was a nice API which included filtering and worked great. Sadly Sensor.TYPE_ORIENTATION was deprecated and is not available on modern phones.
The blessed replacement for Sensor.TYPE_ORIENTATION is a complicated combination of Context.SENSOR_SERVICE and TYPE_MAGNETIC_FIELD and the SensorManager.getRotationMatrix() and SensorManager.getOrientation() functions. You're on your own when it comes to filtering. (As an aside I used iirj - the trivial low pass filters I found on Stackoverflow did not work as well as whatever Sensor.TYPE_ORIENTATION did)
The documentation for getOrientation claims that it returns a pitch between -π to π. This can't be true since the implementation is values[1] = (float) Math.asin(-R[7]); (asin returns values between -π/2 and π/2)
Is there any way to get the full 360° of pitch and roll from the rotation matrix?
This is a known issue that Google won't fix. I created my own getOrientation function based on Gregory G. Slabaugh's paper
// Based on pseudo code from http://www.close-range.com/docs/Computing_Euler_angles_from_a_rotation_matrix.pdf
object EulerAngleHelper {
private const val R11 = 0
private const val R12 = 1
private const val R13 = 2
private const val R21 = 3
private const val R22 = 4
private const val R23 = 5
private const val R31 = 6
private const val R32 = 7
private const val R33 = 8
private const val AZIMUTH = 0
private const val PITCH = 1
private const val ROLL = 2
private const val PHI_Z = AZIMUTH
private const val PSI_X = PITCH
private const val THETA_Y = ROLL
fun getOrientation(r: DoubleArray, values: DoubleArray): DoubleArray {
when {
r[R31] < -0.98 -> {
values[PHI_Z] = 0.0 // Anything; can set to 0
values[THETA_Y] = Math.PI / 2
values[PSI_X] = values[PHI_Z] + atan2(r[R12], r[R13])
}
r[R31] > 0.98 -> {
values[PHI_Z] = 0.0 // Anything; can set to 0
values[THETA_Y] = -Math.PI / 2
values[PSI_X] = values[PHI_Z] + atan2(-r[R12], -r[R13])
}
else -> {
values[THETA_Y] = -asin(r[R31])
val cosTheta = cos(values[THETA_Y])
values[PSI_X] = atan2(r[R32] / cosTheta, r[R33] / cosTheta)
values[PHI_Z] = atan2(r[R21] / cosTheta, r[R11] / cosTheta)
}
}
return values
}
}
I've only tested the pitch and roll.
I'm working on an audio editing app, and I need to store the samples as floats in an efficient way - memory as well as performance-wise. Right now I'm using simply Kotlin's List, but I've heard about potential gains from using FloatArray type. I made a synthetic test to see the benefits and the results are a bit weird. Any advice on the techniques and collections I should use for large datasets of this nature could earn you a cookie, if I had one and knew your location.
So I have two alternative channel implementations (as in - channel in an audio file), to hold my data.
Here is my code for the channel based on FloatArray:
class ArrayChannel {
private var mData : FloatArray = FloatArray(0)
private var mLastWrittenIndex = 0
fun getSamples(startIndex : Int = 0, endIndex : Int = mLastWrittenIndex) : FloatArray
= mData.sliceArray(IntRange(0, endIndex - 1))
fun insertSamples(samples : FloatArray, startIndex : Int = mLastWrittenIndex) {
if (mData.size - mLastWrittenIndex < samples.size) {
val newData = FloatArray(mData.size + samples.size )
mData.copyInto(newData, 0, 0, startIndex)
samples.copyInto(newData, startIndex)
mData.copyInto(newData, startIndex + samples.size, startIndex)
mData = newData
} else {
mData.copyInto(mData, startIndex + samples.size, startIndex, mLastWrittenIndex)
samples.copyInto(mData, startIndex)
}
mLastWrittenIndex += samples.size
}
fun getSamplesSize(): Int = mLastWrittenIndex
}
And here is my code for the channel based on List:
class Channel {
private var mData = mutableListOf<Float>()
fun getSamples(startIndex : Int = 0, endIndex : Int = mData.size) : List<Float>
= mData.slice(IntRange(0, endIndex - 1))
fun insertSamples(samples : List<Float>, startIndex : Int = mData.size) {
mData.addAll(startIndex, samples)
}
fun getSamplesSize() : Int = mData.size
}
Here is the measuring code:
val initialValuesArray = FloatArray(1000000) {Random.nextFloat()}
val valuesToAddArray = FloatArray(1000000) {Random.nextFloat()}
val initialValuesList = MutableList(1000000) {Random.nextFloat()}
val valuesToAddList = MutableList(1000000) {Random.nextFloat()}
var startTime = System.currentTimeMillis()
val arrayChannel = ArrayChannel()
arrayChannel.insertSamples(initialValuesArray)
arrayChannel.insertSamples(valuesToAddArray, 0)
println("Array time: ${System.currentTimeMillis() - startTime}")
startTime = System.currentTimeMillis()
val listChannel = Channel()
listChannel.insertSamples(initialValuesList)
listChannel.insertSamples(valuesToAddList, 0)
println("List time: ${System.currentTimeMillis() - startTime}")
Now, the average results from a direct fun main() call in Android studio are as following:
Array time: 56
List time: 6
A change in the allocation of the array, instead of mData.size + samples.size to mData.size + samples.size * 2, makes these different, in a very weird way:
Array time: 17
List time: 48
When I run the same code inside of an Activity instead of some main Kotlin function, the results match more what I was expecting and are promising:
2020-08-17 21:15:33.325 D/ARRAY_TIME: 15
2020-08-17 21:15:33.481 D/LIST_TIME: 156
Why the code behaves this way and what do you think would be a good way of handling lots of numerical data in the Android environment?
Info
I have an Item class file as follows:
class Item(var color:String, var numValue:Int, var drawableID:Int){
init {
color = this.color
numValue = this.numValue
drawableID = this.drawableID
}
}
In the main code I create an array which contains 104 objects by default attributes:
var myItemClassArray = Array(104) { Item("", -99, -99) }
Also I have pictures in my drawable folder and I have their IDs in an array which is drawablesIDs:Array<Int>, and it contains 53 elements.
Problem
I want to assign my Item attributes as in this picture: https://i.stack.imgur.com/wFVsn.png I can do it for a similar problem (which has 106 objects and 53 drawables) with the code given in below:
for (i in 0 until 106) {
if (i < 13) {
myItemClassList[i+2].color = "kirmizi"
myItemClassList[i+2].numValue = i+1
myItemClassList[i+2].drawableID = drawablesIDs[i+1]
} else if (i in 13..25) {
myItemClassList[i+2].color = "siyah"
myItemClassList[i+2].numValue = (i+1)-13
myItemClassList[i+2].drawableID = drawablesIDs[i+1]
} else if (i in 26..38) {
myItemClassList[i+2].color = "yesil"
myItemClassList[i+2].numValue = (i+1)-26
myItemClassList[i+2].drawableID = drawablesIDs[i+1]
} else if (i in 39..51) {
myItemClassList[i+2].color = "mavi"
myItemClassList[i+2].numValue = (i+1)-39
myItemClassList[i+2].drawableID = drawablesIDs[i+1]
} else if (i in 52..64) {
myItemClassList[i+2].color = "kirmizi"
myItemClassList[i+2].numValue = (i+1)-52
myItemClassList[i+2].drawableID = drawablesIDs[(i+1)-52]
} else if (i in 65..77) {
myItemClassList[i+2].color = "siyah"
myItemClassList[i+2].numValue = (i+1)-65
myItemClassList[i+2].drawableID = drawablesIDs[i+1-65+13]
} else if (i in 78..90) {
myItemClassList[i+2].color = "yesil"
myItemClassList[i+2].numValue = (i+1)-78
myItemClassList[i+2].drawableID = drawablesIDs[i+1-78+26]
} else if (i in 91..103) {
myItemClassList[i+2].color = "mavi"
myItemClassList[i+2].numValue = (i+1)-91
myItemClassList[i+2].drawableID = drawablesIDs[i+1-91+39]
} else {
myItemClassList[0].color = "sahte"
myItemClassList[0].drawableID = drawablesIDs[0]
myItemClassList[1].color = "sahte"
myItemClassList[1].drawableID = drawablesIDs[0]
}
}
Is there a cleaner way to do this?
One can use lambda expression to create an array. For example:
val test = Array(28){i-> examples[i]}
This works fine with one "i" parameter. But if I want to try something like this:
val test = Array(28){if(i<13)-> examples[i]}
it gives me an error because of it's syntax is wrong.
More Simple Question
Let's say we have an array which has numbers from 0 to 28 like this:
val testNumbers= Array(28){i->i}
Now I want to create an array which will contain numbers from 0 to 10 using lambda.
How do I this:
val player6 = Array(10){(it<10) -> testNumbers[it]} // gives an syntax error
From what I could gather from your picture, I've made these three assumptions:
The numValue is grouped in groups of 13 items
Each group receives a color in the order: kirmizi -> siyah -> yesil -> mavi, then it cycles again
The drawable IDs cycles every 52 items
Based on this, I came up with the following solution:
data class Item(var color: String = "", var numValue: Int = -99, var drawableId: Int = -99)
fun main() {
val colors = listOf("kirmizi", "siyah", "yesil", "mavi")
val drawableIDs = (0..52).toList() // This is just a stub. in your case it will be your drawable lists
val edgeCase = arrayListOf(Item("sahte", drawableId = drawableIDs[0]), Item("sahte", drawableId = drawableIDs[0]))
val pattern = (0 until 104)
.map { index -> Pair(index, index / 13) }
.map { (index, group) ->
Item(
color = colors[group % 4],
numValue = index+1,
drawableId = drawableIDs[(index % 52) + 1]
)
}
val total = pattern + edgeCase
total.forEach { println(it) }
}
You can play around with it on this kotlin playground.
Is there a cleaner way to do this?
From what I gather, you want to initialize only the first 13 values of a contiguous array with 28 spaces, leaving the rest with either their default values or null.
The reason why your code doesn't work is because the Array initializer expects you to return an object. the if block by itself is not an expression in kotlin, so it doesn't evaluate to a value, so you need to provide an else branch for it to work.
val examples = Array(28) { if (i < 13) examples[i] else defaultExample }
This is stated in the Kotlin documentation for control flow:
If you're using if as an expression rather than a statement (for example, returning its value or assigning it to a variable), the expression is required to have an else branch.
More simple question
In this case you could just use take:
// If you don't want to modify it
val player6 = testNumbers.take(10)
.toTypedArray() // Since take returns a List, you need to turn it back into an array
// If you want to modify the items
val player6 = testNumbers.take(10)
.map { value -> modifyNumber(value) }
.toTypedArray()
Tip: In kotlin if you declare your constructor parameter with val or var they are already attributes from your class and you don't need to initialize manually in the init block.
/*
* You don't need to do this:
* class Item(var color: String, var numValue: Int, var drawableId: Int) {
* init {
* this.color = color
* this.numValue = numValue
* this.drawableId = drawableId
* }
* }
*/
// Kotlin already does it for you
class Item(var color: String, var numValue: Int, var drawableId: Int)
fun main() {
val myitem = Item("blue", 20, 100)
println(myitem.color)
println(myitem.numValue)
println(myitem.drawableId)
}
Here is a possible solution :
fun getColor(i: Int) = when (i) {
in 0..1 -> "sahte"
in 2..13, in 52..64 -> "kirmizi"
in 65..77, in 13..25 -> "siyah"
in 26..38, in 78..90 -> "yesil"
in 39..51, in 91..103 -> "mavi"
else -> ""
}
fun getNumValue(i: Int) = when (i) {
in 0..1 -> -99
in 2..13 -> i - 1
in 13..25 -> (i - 1) - 13
in 26..38 -> (i - 1) - 26
in 39..51 -> (i - 1) - 39
in 52..64 -> (i - 1) - 52
in 65..77 -> (i - 1) - 65
in 78..90 -> (i - 1) - 78
in 91..103 -> (i - 1) - 91
else -> -99
}
fun getDrawableID(i: Int) = when (i) {
in 0..1 -> drawablesIDs[0]
in 2..13, in 13..25, in 26..38, in 39..51 -> drawablesIDs[i - 1]
in 52..64 -> drawablesIDs[(i - 1) - 52]
in 65..77 -> drawablesIDs[i - 1 - 65 + 13]
in 78..90 -> drawablesIDs[i - 1 - 78 + 26]
in 91..103 -> drawablesIDs[i - 1 - 91 + 39]
else -> -99
}
val myItemClassArray = Array(104) {
Item(getColor(it), getNumValue(it), getDrawableID(it))
}
Maybe there is some mistakes in the different ranges.
The main advantages are :
each mapping is testable independently
no mutability