Thanks to the great help from Tenfour04, I've got wonderful code for handling CSV files.
However, I am in trouble like followings.
How to call these functions?
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
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.
providing a generic type parameter
val listWithNullableInts = mutableListOf<Int?>(3, 7)
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.
I have a very basic question regarding spread operator. When we do
let numbers = [1,2,3,4];
console.log(...numbers);
While it flattens out the list, is the output format actually a string like "1 2 3 4" OR are they still separate number list?
Spread syntax allows an iterable such as an array expression or string to be expanded in places where zero or more arguments (for function calls) or elements (for array literals) are expected, or an object expression to be expanded in places where zero or more key-value pairs (for object literals) are expected.
Source: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax
In your example you would have
let numbers = [1,2,3,4];
console.log(...numbers);
// This is the same
console.log(1, 2, 3, 4);
Your values are spreaded as seperate values out of the array. You can do this with functions as well:
const numbers = [1, 2];
const add = (a, b) => { return a + b; };
console.log(add(...numbers)); // -> add(1, 2) -> 3
const numbers = [1, 2, 3];
const [ firstNumber, ...restOfTheNumbers ] = numbers;
console.log(firstNumber, restOfTheNumbers); // 1 [ 2, 3 ]
I have a Polymer 1.0 custom element which has a property called obj which is an object, say
obj = { a: 1,
b: 2}
How do I remove one of the keys?
I have tried this.set('obj.a', undefined)
and this.set('obj.a', null)
but result is {a: null (or undefined),
b: 2}
where what I want is just to remove 'a' leaving {b:2}
is there a correct way of doing it?
Use delete. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/delete
Example:
let x = {a: 1, b: 2, '#sss': 3};
delete x.a; // x is now {b: 2, '#sss': 3}
delete x['#sss']; // x is now {b: 2}
How do I insert an element at arbitrary position of Immutable.js List?
You are looking for the splicemethod:
Splice returns a new indexed Iterable by replacing a region of this Iterable with new values.
splice(index: number, removeNum: number, ...values: any[])
Where you can specify the index and if you write 0 as removeNum it will just insert the values at the given position:
var list = Immutable.List([1,2,3,4]);
console.log(list.toJS()); //[1, 2, 3, 4]
var inserted = list.splice(2,0,100);
console.log(list.toJS()); //[1, 2, 3, 4]
console.log(inserted.toJS()); //[1, 2, 100, 3, 4]
Demo JSFiddle.
It is worth pointing out that you can also use the insert method which is in fact synonymous with list.splice(index, 0, value) but it feels more intuitive and improves readability greatly.
const myImmutableList = Immutable.fromJS(['foo', 'baz'])
const newList = myImmutableList.insert(1, 'bar')
console.log(newList.toJS()) //["foo", "bar", "baz"]
It is in ScalaDoc but without much documentation. It seems that it always returns the first parameter.
Function.const(1)(2) for instance returns 1.
Why does it exist and why is it useful?
It's useful for passing as an argument to a higher-order function. For example, to replace all elements of a list with the same element:
scala> List(1, 2, 3, 4, 5).map(Function.const(7))
res1: List[Int] = List(7, 7, 7, 7, 7)
You could of course also write
scala> List(1, 2, 3, 4, 5).map(_ => 7)
res2: List[Int] = List(7, 7, 7, 7, 7)
Depending on the context, one might be more readable than the other.
To give a more theoretical answer: const is the K combinator of the SKI calculus. It pops sometimes up when you work with quite abstract concepts where you don't have much "to work with". Consider a (Haskell style) Functor trait:
trait Functor[F[_]] {
def fmap[A,B](f:A=>B, fa: F[A]):F[B]
//(<$) in Haskell
def left[A,B](a:A, fb:F[B]):F[A]
}
Now fmap needs to be abstract, as it is the very essence of a functor. But we can write a general implementation of left, and here we need const:
trait Functor[F[_]] {
def fmap[A,B](f:A=>B, fa: F[A]):F[B]
//(<$) in Haskell
def left[A,B](a:A, fb:F[B]):F[A] =
fmap(Function.const(a), fb)
}
Test with Option:
case object OptionFunctor extends Functor[Option] {
def fmap[A,B] (f:A=>B, fa:Option[A]):Option[B] = fa match {
case Some(a) => Some(f(a))
case None => None
}
}
//left works:
OptionFunctor.left("test",Some(42))
//--> Option[java.lang.String] = Some(test)
OptionFunctor.left("test",None:Option[Int])
//--> Option[java.lang.String] = None
As you can see left does what it should (wrapping a value in some functor when we have already a "role model" or "pattern" for this functor in the second argument). Defining it very abstract without knowing anything about the kind of functor was only possible by using const.