1

Thanks to the great help from Tenfour04, I've got wonderful code for handling CSV files.

However, I am in trouble like followings.

  1. How to call these functions?
  2. How to initialize 2-dimensional array variables?

Below is the code that finally worked.

MainActivity.kt

package com.surlofia.csv_tenfour04_1

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import java.io.File
import java.io.IOException
import com.surlofia.csv_tenfour04_1.databinding.ActivityMainBinding

var chk_Q_Num: MutableList<Int> = mutableListOf  (
    0,
    1, 2, 3, 4, 5,
    6, 7, 8, 9, 10,
    11, 12, 13, 14, 15,
    16, 17, 18, 19, 20,
)

var chk_Q_State: MutableList<String> = mutableListOf  (
    "z",
    "a", "b", "c", "d", "e",
    "f", "g", "h", "i", "j"
)


class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // setContentView(R.layout.activity_main)

        binding = ActivityMainBinding.inflate(layoutInflater)

        val view = binding.root

        setContentView(view)

        // Load saved data at game startup. It will be invalid if performed by other activities.

        val filePath = filesDir.path + "/chk_Q.csv"
        val file = File(filePath)

        binding.fileExists.text = isFileExists(file).toString()

        if (isFileExists(file)) {
            val csvIN = file.readAsCSV()

            for (i in 0 .. 10) {
                chk_Q_Num[i] = csvIN[i][0].toInt()
                chk_Q_State[i] = csvIN[i][1]
            }

        }

        // Game Program Run


        val csvOUT = mutableListOf(
            mutableListOf("0","OK"),
            mutableListOf("1","OK"),
            mutableListOf("2","OK"),
            mutableListOf("3","Not yet"),
            mutableListOf("4","Not yet"),
            mutableListOf("5","Not yet"),
            mutableListOf("6","Not yet"),
            mutableListOf("7","Not yet"),
            mutableListOf("8","Not yet"),
            mutableListOf("9","Not yet"),
            mutableListOf("10","Not yet")
        )

        var tempString = ""

        for (i in 0 .. 10) {
            csvOUT[i][0] = chk_Q_Num[i].toString()
            csvOUT[i][1] = "OK"

            tempString = tempString + csvOUT[i][0] + "-->" + csvOUT[i][1] + "\n"
        }

        binding.readFile.text = tempString

        // and save Data
        file.writeAsCSV(csvOUT)


    }


    // https://www.techiedelight.com/ja/check-if-a-file-exists-in-kotlin/

    private fun isFileExists(file: File): Boolean {
        return file.exists() && !file.isDirectory
    }

    @Throws(IOException::class)
    fun File.readAsCSV(): List<List<String>> {
        val splitLines = mutableListOf<List<String>>()
        forEachLine {
            splitLines += it.split(", ")
        }
        return splitLines
    }

    @Throws(IOException::class)
    fun File.writeAsCSV(values: List<List<String>>) {
        val csv = values.joinToString("\n") { line -> line.joinToString(", ") }
        writeText(csv)
    }
}

chk_Q.csv

0,0
1,OK
2,OK
3,Not yet
4,Not yet
5,Not yet
6,Not yet
7,Not yet
8,Not yet
9,Not yet
10,Not yet

1. How to call these functions?

The code below seems work well. Did I call these funtions in right way? Or are there better ways to achieve this?

read

if (isFileExists(file)) {
    val csvIN = file.readAsCSV()

    for (i in 0 .. 10) {
        chk_Q_Num[i] = csvIN[i][0].toInt()
        chk_Q_State[i] = csvIN[i][1]
    }
}

write

file.writeAsCSV(csvOUT)

2. How to initialize 2-dimensional array variables?

val csvOUT = mutableListOf(
    mutableListOf("0","OK"),
    mutableListOf("1","OK"),
    mutableListOf("2","OK"),
    mutableListOf("3","Not yet"),
    mutableListOf("4","Not yet"),
    mutableListOf("5","Not yet"),
    mutableListOf("6","Not yet"),
    mutableListOf("7","Not yet"),
    mutableListOf("8","Not yet"),
    mutableListOf("9","Not yet"),
    mutableListOf("10","Not yet")
)

I would like to know the clever way to use a for loop instead of writing specific values one by one.

For example, something like bellow.

val csvOUT = mutableListOf(mutableListOf())
for (i in 0 .. 10) {
    csvOUT[i][0] = i
    csvOUT[i][1] = "OK"
}

But this gave me the following error message:

Not enough information to infer type variable T

It would be great if you could provide an example of how to execute this for beginners.


----- Added on June 15, 2022. ----- [Question 1] Regarding initialization, I got an error "keep stopping" when I executed the following code. The application is forced to terminate. Why is this?

val csvOUT: MutableList<MutableList<String>> = mutableListOf(mutableListOf())
    for (i in 0 .. 10) {
        csvOUT[i][0] = "$i"
        csvOUT[i][1] = "OK"
    }

[Error Message]

java.lang.RuntimeException: Unable to start activity ComponentInfo{com.surlofia.csv_endzeit_01/com.surlofia.csv_endzeit_01.MainActivity}: java.lang.IndexOutOfBoundsException: Index: 0, Size: 0

Surlofia
  • 29
  • 2
  • 8
  • It's not quite clear to me what problem you are facing. You want to populate `csvOUT` dynamically using a loop instead of hard-coding it? If so, please provide details about where the data is originating from and in which form. Also consider using the more "modern" `java.nio.file.Path` instead of `java.io.File`, see [this question for more](https://stackoverflow.com/questions/6903335/java-path-vs-file). – Endzeit Jun 12 '22 at 09:32
  • Could you see edited question with for loop code. And "import java.io.File" is add by auto, and not deprecated. So, I would like to not change. And forgive me, I am a biggenner and copy and paste good code from website. Thank you for your time. – Surlofia Jun 12 '22 at 10:06
  • Correction Wrong: biggenner Right: beginner – Surlofia Jun 12 '22 at 10:16
  • No worries, you do not have to change from `File` to `Path`. I just wanted to make you aware of the newer alternative. – Endzeit Jun 12 '22 at 11:02
  • @Endzeit index 0 size 0 error cause keep stopping. from <---- val csvOUT: MutableList> = mutableListOf(mutableListOf()) – Surlofia Jun 15 '22 at 01:57

1 Answers1

0

In my opinion there are basically two parts to your question. First you need an understanding of the Kotlin type system including generics. Secondly you want some knowledge about approaches to the problem at hand.

type-system and generics

The function mutableListOf you're using is generic and thus needs a single type parameter T, as can be seen by definition its taken from the documentation:

fun <T> mutableListOf(): MutableList<T>

Most of the time the Kotlin compiler is quite good at type-inference, that is guessing the type used based on the context. For example, I do not need to provide a type explicitly in the following example, because the Kotlin compiler can infer the type from the usage context.

val listWithInts = mutableListOf(3, 7)

The infered type is MutableList<Int>. However, sometimes this might not be what one desires. For example, I might want to allow null values in my list above. To achieve this, I have to tell the compiler that it should not only allow Int values to the list but also null values, widening the type from Int to Int?. I can achieve this in at least two ways.

  1. providing a generic type parameter
val listWithNullableInts = mutableListOf<Int?>(3, 7)
  1. defining the expected return type explicitly
val listWithNullableInts: MutableList<Int?> = mutableListOf(3, 7)

In your case the compiler does NOT have enough information to infer the type from the usage context. Thus you either have to provide it that context, e.g. by passing values of a specific type to the function or using one of the two options named above.

initialization of multidimensional arrays

There are questions and answers on creating multi-dimensional arrays in Kotlin on StackOverflow already.

One solution to your problem at hand might be the following.

val csvOUT: MutableList<MutableList<String>> = mutableListOf(mutableListOf())

for (i in 0 .. 10) {
    csvOUT[i][0] = "$i"
    csvOUT[i][1] = "OK"
}

You help the Kotlin compiler by defining the expected return type explicitly and then add the values as Strings to your 2D list.

If the dimensions are fixed, you might want to use fixed-size Arrays instead.

val csvArray = Array(11) { index -> arrayOf("$index", "OK") }

In both solutions you convert the Int index to a String however. If the only information you want to store for each level is a String, you might as well use a simple List<String and use the index of each entry as the level number, e.g.:

val csvOut = List(11) { "OK" }
val levelThree = csvOut[2] // first index of List is 0 

This would also work with more complicated data structures instead of Strings. You simply would have to adjust your fun File.writeAsCSV(values: List<List<String>>) to accept a different type as the values parameter. Assume a simple data class you might end up with something along the lines of:

data class LevelState(val state: String, val timeBeaten: Instant?)

val levelState = List(11) { LevelState("OK", Instant.now()) }

fun File.writeAsCSV(values: List<LevelState>) {
    val csvString = values
        .mapIndexed { index, levelState -> "$index, ${levelState.state}, ${levelState.timeBeaten}" }
        .joinToString("\n")
    
    writeText(csvString)
}

If you prefer a more "classical" imperative approach, you can populate your 2-dimensional Array / List using a loop like for in.

val list: MutableList<MutableList<String>> = mutableListOf() // list is now []

for (i in 0..10) {
    val innerList: MutableList<String> = mutableListOf()
    innerList.add("$i")
    innerList.add("OK")
    innerList.add("${Instant.now()}")

    list.add(innerList) 
    // list is after first iteration [ ["0", "OK", "2022-06-15T07:03:14.315Z"] ]
}

The syntax listName[index] = value is just syntactic sugar for the operator overload of the set operator, see the documentation on MutableList for example.

You cannot access an index, that has not been populated before, e.g. during the List's initialization or by using add; or else you're greeted with a IndexOutOfBoundsException.

If you want to use the set operator, one option is to use a pre-populated Array as such:

val array: Array<Array<String>>> = Array(11) { 
    Array(3) { "default" }
} // array is [ ["default, "default", "default"], ...]

array[1][2] = "myValue"

However, I wouldn't recommend this approach, as it might lead to left over, potentially invalid initial data, in case one misses to replace a value.

Surlofia
  • 29
  • 2
  • 8
Endzeit
  • 4,810
  • 5
  • 29
  • 52
  • > One solution to your problem at hand might be the following. --> App keeps stopping Why ? – Surlofia Jun 13 '22 at 04:04
  • 1
    > val csvArray = Array(11) { index -> arrayOf("$index", "OK") } --> val csvOUT = MutableList(11) { index -> mutableListOf("$index", "OK") } --> Good worked. I ilike this solution. – Surlofia Jun 13 '22 at 04:09
  • > more complicated data structures --> How to receive "Instant?" ??? --> csvOUT[i][2] = levelState[i].timeBeaten.toString() --> cause keeps stopping. – Surlofia Jun 13 '22 at 10:15
  • @Surlofia I'm not entirely sure what's your problem here. Are you wondering how to create an `Instant` based on a `String`? – Endzeit Jun 13 '22 at 16:18
  • Could you see my updated questions area? ----- Added on June 15, 2022. ----- [Question 1] and ----- Added on June 15, 2022. ----- [Question 2] – Surlofia Jun 14 '22 at 17:17
  • @Surlofia Would you be able to ask another separate question for this, as it does no longer relate to the original problem. If you do so, please add the received Exception and stacktrace. I'm unsure what "keep stopping" is supposed to mean. – Endzeit Jun 14 '22 at 18:28
  • "java.lang.IndexOutOfBoundsException: Index: 0, Size: 0" initialization seems failed. And Question 2 is my mistake, please forgive me. Anyway "val csvArray = Array(11) { index -> arrayOf("$index", "OK") }" working well. Thank you for your time. – Surlofia Jun 14 '22 at 19:33
  • @Surlofia You're welcome. So I assume it works now and you don't need any more geo for now? – Endzeit Jun 14 '22 at 20:30
  • If it is OK for you, could you check initialization method with "for loop" again. Please see the end of question area. update with question 1. – Surlofia Jun 15 '22 at 01:48
  • @Surlofia I've expanded my answer to include a more imperative approach. Please let me know if that answers your added question. Beware that I've written this on my phone without code completion / highlighting, so there might be some typos or minor syntax issues. But you should get the general idea. – Endzeit Jun 15 '22 at 05:06
  • I did not know the basics of using ".add" when the list is empty (= list is []) instead of assigning a value. Thanks for the great coaching. Thanks to you, I am now able to notice typos and small issues. I appreciate your easy-to-understand explanations for beginners. – Surlofia Jun 15 '22 at 08:30