How can one implement a generic function which creates an empty generic 2D array? In the following code sample an empty 1D array is created and has the expected type. However, when I call test2D I get an error:
java.lang.ClassCastException: [[Ljava.lang.Object; cannot be cast to [[Ljava.lang.Integer;
inline fun <reified T> make1D(mask: Array<T>) : Array<T> {
val res : Array<T> = arrayOf()
return res
}
#Test
fun test1D() {
val a : Array<Int> = arrayOf(0)
val b : Array<Int> = make1D(a)
assertEquals(0, b.size)
}
inline fun <reified T> make2D(mask: Array<Array<T>>) : Array<Array<T>> {
val res : Array<Array<T>> = arrayOf()
// I expect T to be equal to Int when calling from test below,
// and res to have Integer[][] type;
// however it has Object[][] type instead
return res
}
#Test
fun test2D() {
val a : Array<Array<Int>> = arrayOf(arrayOf(0))
val b : Array<Array<Int>> = make2D(a)
assertEquals(0, b.size)
}
I think you are one level too deep for the reified parameter. Maybe it is a bug, creating a YouTrack issue will help to find out. This code works when you let T be the whole inner array:
inline fun <reified T> make2D(mask: Array<T>): Array<T> {
val res: Array<T> = arrayOf<T>()
return res
}
#Test
fun test2D() {
val a: Array<Array<Int>> = arrayOf(arrayOf(0))
val b: Array<Array<Int>> = make2D(a)
assertEquals(0, b.size)
}
After you create a YouTrack issue, please post the issue number here for tracking.
Related
Is it possible in Kotlin to create a copy of a generic array with new size if I already have an instance of that array and pass a construction method for its items? I think about something like this:
fun <T> resize(arr: Array<T>, newSize: Int, creator: (Int) -> T): Array<T> {
...
}
Obviously I cannot call Array<T>(newSize) { i -> creator(i) } because the type T is not known at compile time. For efficicy reasons I do not want to use an inline function with a reified type. I also cannot use the arr.copyOf(newSize) method here because that would return an Array<T?>.
In Java I could use Arrays.copyOf(arr, newSize) even with a generic array because we don't have null safety here. But would this work in Kotlin as well? Or do you have any other ideas what I could do?
I would just add an extension method to Array<T> for this. You can still rely on Arrays.copyOf under the hood, but before returning the value fill any remaining spaces with the result of the creator block:
fun <T> Array<T>.resize(newSize: Int, creator: (Int) -> T): Array<T> {
val copiedArray = Arrays.copyOf(this, newSize)
for (i in size until newSize) { copiedArray[i] = creator(i) }
return copiedArray
}
For example:
val array = arrayOf("a", "b", "c")
// same: [a, b, c]
println("same: ${Arrays.toString(array.resize(3) { it.toString() })}")
// less: [a, b]
println("less: ${Arrays.toString(array.resize(2) { it.toString() })}")
// more: [a, b, c, 3, 4]
println("more: ${Arrays.toString(array.resize(5) { it.toString() })}")
I guess this will work:
fun <T> resize(arr: Array<T>, dest: Array<T>, creator: (Int) -> T): Array<T> {
val list = arrayListOf<T>()
// fill the list
for (i in 0 until dest.size) {
list.add(creator(i))
}
return list.toArray(dest)
}
fun callResize() {
val arr = Array(5) { " " }
val dest = Array(10) { "" }
val creator = { i: Int -> "$i"}
resize(arr, dest, creator)
}
The result is in the dest array.
The code below is creating a new map called nameTable, then adding an entry named example to it, then trying to print the name property of the Value.
When I run it, it seems that the plus operation didn't add a new entry to the map like I thought it would.
So what am I doing wrong?
class Person(name1: String, lastName1: String, age1: Int){
var name: String = name1
var lastName: String = lastName1
var age: Int = age1
}
var nameTable: MutableMap<String, Person> = mutableMapOf()
var example = Person("Josh", "Cohen", 24)
fun main (args: Array<String>){
nameTable.plus(Pair("person1", example))
for(entry in nameTable){
println(entry.value.age)
}
}
While we're at it, I would love some examples of how to add, remove, and get an entry from a map.
The reason for your confusion is that plus is not a mutating operator, meaning that it works on (read-only) Map, but does not change the instance itself. This is the signature:
operator fun <K, V> Map<out K, V>.plus(pair: Pair<K, V>): Map<K, V>
What you want is a mutating operator set, defined on MutableMap:
operator fun <K, V> MutableMap<K, V>.set(key: K, value: V)
So your code may be rewritten (with some additional enhancements):
class Person(var name: String, var lastName: String, var age: Int)
val nameTable = mutableMapOf<String, Person>()
val example = Person("Josh", "Cohen", 24)
fun main (args: Array<String>) {
nameTable["person1"] = example
for((key, value) in nameTable){
println(value.age)
}
}
The plus-method on Map creates a new map that contains the new entry. It does not mutate the original map. If you want to use this method, you would need to do this:
fun main() {
val table = nameTable.plus(Pair("person1", example))
for (entry in table) {
println(entry.value.age)
}
}
If you want to add the entry to the original map, you need to use the put method like in Java.
This would work:
fun main() {
nameTable.put("person1", example)
for (entry in nameTable) {
println(entry.value.age)
}
}
To get and remove entries from the MutableMap, you can use this:
nameTable["person1"] // Syntactic sugar for nameTable.get("person1")
nameTable.remove("person1")
It's too much trouble,You can assign values directly,like this:
#Test
#Throws(Exception::class)
fun main(){
val map:MutableMap<String,Person> = mutableMapOf()
map["Josh"]= Person("Josh", "Cohen", 24)
map.forEach { t, u ->
println("map key:$t,map value:${u.toString()}")
}
}
class Person(name1:String, lastName1:String, age1:Int){
var name:String = name1
var lastName:String = lastName1
var age:Int = age1
override fun toString(): String {
return "name:$name,lastNam:$lastName,age:$age \n"
}
}
You have to use
put
method.
class Person(name1:String, lastName1:String, age1:Int){
var name:String = name1
var lastName:String = lastName1
var age:Int = age1
}
var nameTable:MutableMap<String, Person> = mutableMapOf()
var example = Person("Josh", "Cohen", 24)
fun main (args: Array<String>){
nameTable.put("person1", example)
for(entry in nameTable){
println(entry.value.age)
}
}
val params: MutableMap<String, String> = HashMap<String, String>()
params.put("1", "A")
params.put("2", "B")
Suppose I have a generic class and I need a 2D array of generic type T. If I try the following
class Matrix<T>(width: Int, height: Int) {
val data: Array<Array<T>> = Array(width, arrayOfNulls<T>(height))
}
the compiler will throw an error saying "Cannot use 'T' as reified type parameter. Use a class instead.".
Just because the syntax has moved on a bit, here's my take on it:
class Array2D<T> (val xSize: Int, val ySize: Int, val array: Array<Array<T>>) {
companion object {
inline operator fun <reified T> invoke() = Array2D(0, 0, Array(0, { emptyArray<T>() }))
inline operator fun <reified T> invoke(xWidth: Int, yWidth: Int) =
Array2D(xWidth, yWidth, Array(xWidth, { arrayOfNulls<T>(yWidth) }))
inline operator fun <reified T> invoke(xWidth: Int, yWidth: Int, operator: (Int, Int) -> (T)): Array2D<T> {
val array = Array(xWidth, {
val x = it
Array(yWidth, {operator(x, it)})})
return Array2D(xWidth, yWidth, array)
}
}
operator fun get(x: Int, y: Int): T {
return array[x][y]
}
operator fun set(x: Int, y: Int, t: T) {
array[x][y] = t
}
inline fun forEach(operation: (T) -> Unit) {
array.forEach { it.forEach { operation.invoke(it) } }
}
inline fun forEachIndexed(operation: (x: Int, y: Int, T) -> Unit) {
array.forEachIndexed { x, p -> p.forEachIndexed { y, t -> operation.invoke(x, y, t) } }
}
}
This also allows you to create 2d arrays in a similar manner to 1d arrays, e.g. something like
val array2D = Array2D<String>(5, 5) { x, y -> "$x $y" }
and access/set contents with the indexing operator:
val xy = array2D[1, 2]
The problem is calling arrayOfNulls<T>(height) with the non-reified type parameter T. But we also can't make T reified, the compiler will throw the following error: "Only type parameters of inline functions can be reified"
So that's what we're going to do. Instead of the constructor we use an inlined factory method:
class Matrix<T> private(width: Int, height: Int, arrayFactory: (Int) -> Array<T>) {
class object {
inline fun <reified T>invoke(width: Int, height: Int)
= Matrix(width, height, { size -> arrayOfNulls<T>(size) })
}
val data: Array<Array<T>> = Array(width, { size -> arrayFactory(size) })
}
Notice, the constructor is now private, so calling Matrix() will correctly call the new invoke() method (related question). Because the method is inlined, we can use reified generics which makes it possible to call arrayOfNulls<T>.
I have a class called Controller that contains one Array property. Right now, my class is declared like that:
class Controller {
var myArray: [AnyObject]
init(bool: Bool) {
if bool == true {
myArray = [10, 11, 12]
} else {
myArray = ["Yo", "Ya", "Yi"]
}
}
}
The problem that I have with this code is that myArray is still (of course) of type [AnyObject] after my class initialization. Thus, every time I need to get an object out of myArray, I have to cast its type (Int or String) just like this:
let controller = Controller(bool: false)
let str = controller.array[0] as String
I want to be able to write let str = controller.array[0] //str: String without having to cast the real type of the objects inside myArray. Is there a way to do so? Do I have to use lazy init, struct, generic types?
Here is a attempt in pseudo code:
class Controller {
var myArray: Array<T> //Error: use of undeclared type 'T'
init(bool: Bool) {
if bool == true {
myArray = [10, 11, 12] as [Int] //myArray: Array<Int>
} else {
myArray = ["Yo", "Ya", "Yi"] as [String] //myArray: Array<String>
}
}
}
So as Oscar and Elijah pointed out (up votes to them), I am just trying to be a little more verbose here. You need to declare the generic, T, when you define the class.
This would mean you need to define what that generic's type is at initialization of the class.
class Foo<T> {
var items = [T]()
}
let stringFoo = Foo<String>()
stringFoo.items.append("bar")
stringFoo.items[0] = "barbar"
stringFoo.items // ["barbar"]
let intFoo = Foo<Int>()
intFoo.items.append(1)
intFoo.items[0] = 11
intFoo.items // [11]
So in your case, rather than passing a Bool for the init method, just define the generic's type at initialization instead.
class Controller<T> //You missed that T, right?
{
var myArray: Array<T>
/* initialization stuff */
}
var c = Controller<String>()
c.myArray[0] = "Hey!"
let str = c.myArray[0] //String, eventually!
class Controller<T> {
var array: Array<T> = []
...
}
I don't understand why this code is impossible in Scala:
def getColumns[T <: Array[_]] ():Array[(String,T)] ={
Array(Tuple2("test",Array(1.0,2.0,3.0)))
}
Compiler says:
Expression of type Array[(String,Array[Double])] doesn't conform to expected type Array[(String, T)]
I have the same error with this code:
def getColumns[T <: Array[Double]] ():Array[(String,T)] ={
Array(Tuple2("test",Array(1.0,2.0,3.0)))
}
It's more clear with my complete use case, finally I have chosen an Array[AnyVal]:
class SystemError(message: String, nestedException: Throwable) extends Exception(message, nestedException) {
def this() = this("", null)
def this(message: String) = this(message, null)
def this(nestedException : Throwable) = this("", nestedException)
}
class FileDataSource(path:String) {
val fileCatch = catching(classOf[FileNotFoundException], classOf[IOException]).withApply(e => throw new SystemError(e))
def getStream():Option[BufferedSource]={
fileCatch.opt{Source.fromInputStream(getClass.getResourceAsStream(path))}
}
}
class CSVReader(src:FileDataSource) {
def lines= src.getStream().get
val head = lines.getLines.take(1).toList(0).split(",")
val otherLines = lines.getLines.drop(1).toList
def getColumns():Array[(String,Array[_])] ={
val transposeLines = otherLines.map { l => l.split(",").map {
d => d match {
case String => d
case Int => d.toInt
case Double => d.toDouble
}}.transpose }
head zip transposeLines.map {_.toArray}
}
}
Can you give me some explanation or good links to understand the issues?
Because your function must be able to work with any T (any array type), however, it will always return an Array[(String,Array[Double])].
A simpler working signature would be:
def getColumns[T](): Array[(String,Array[T])]
But in the body of the function you will have to create an array of type T.
Your function signature requires a T in the compound return type, but you give it a Array[Double]. Note that T <: Array[Double], not the other way around. The following code works:
def getColumns[T >: Array[Double]] ():Array[(String,T)] ={
Array(Tuple2("test",Array(1.0,2.0,3.0)))
}
Shouldn't be what you want but just for elaborating the problem.