I created a RollingWindow class in order to have a fixed number of most recent data points stored in an array.
class RollingWindow<T> (private val length: Int) {
private val window = Array<Any?>(length) {null}
private var count = 0
fun push(t: T) {
if (length == 0)
return
window[currentIndex()] = t
count++
}
#Suppress("UNCHECKED_CAST")
fun toArray(): Array<T> {
if (length == 0)
return arrayOf<Any>() as Array<T>
val firstHalf = window
.copyOfRange(currentIndex(), window.size)
.filterNotNull()
.toTypedArray()
val secondHalf = window
.copyOfRange(0, currentIndex())
.filterNotNull()
.toTypedArray()
val arr = arrayOf(*firstHalf, *secondHalf) as Array<T>
print(arr.contentToString())
//this works fine but for some reason the class cast exception is thrown from the unit test
return arr
}
override fun toString() = toArray().contentToString()
private fun currentIndex() = count % length
}
I wrote some unit tests and am getting a ClassCastException
#Test
fun testRollingWindowNotFull() {
val doubleWindow = RollingWindow<Double>(5)
doubleWindow.push(2.5)
doubleWindow.push(6.8)
assertArrayEquals(arrayOf(2.5, 6.8), doubleWindow.toArray()) //this passes
val variableInWindow = RollingWindow<Double>(5)
variableInWindow.push(25.6)
variableInWindow.push(24.32)
val windowArray = variableInWindow.toArray() // ClassCastException only gets thrown here or whenever it's stored in a variable. If I use variableInWindow.toArray() inline it's fine, as shown in previous assertion
assertArrayEquals(arrayOf(25.6, 24.32), windowArray) // This never gets reached
}
While running the test I tried casting the Array<Any> into an Array<T>. Casting within the RollingWindow class works fine, no errors, but I specifically get an error in the Unit Test. Here's the StackTrace:
Sep 13, 2021 1:55:49 PM org.junit.platform.launcher.core.EngineDiscoveryOrchestrator lambda$logTestDescriptorExclusionReasons$7
INFO: 0 containers and 4 tests were Method or class mismatch
[Ljava.lang.Object;#7a8051ce[Ljava.lang.Object;#3ba12a08[Ljava.lang.Object;#725e196
class [Ljava.lang.Object; cannot be cast to class [Ljava.lang.Double; ([Ljava.lang.Object; and [Ljava.lang.Double; are in module java.base of loader 'bootstrap')
java.lang.ClassCastException: class [Ljava.lang.Object; cannot be cast to class [Ljava.lang.Double; ([Ljava.lang.Object; and [Ljava.lang.Double; are in module java.base of loader 'bootstrap')
at collections.RollingWindowTest.testRollingWindowNotFull(RollingWindowTest.kt:24)
Unlike all other generics in classes in Kotlin, the Array class and the Array class only has a reified type, and it is an invariant type. You cannot ever successfully cast an Array of one type to an Array of another type unless you are casting it to have a covariant or contravariant type.
I think the only reason the first part of your test passes is that there must be a compiler optimization that omits the reified cast inside the toArray() function if the returned object is used as an Any or Array<out Any>. The Java-defined assertArrayEquals function takes two Object[] parameters which get mapped to either Array<Any> or Array<out Any>, and perhaps in this case it's doing the latter cast because it sees that nothing is put into the arrays in this function.
So the compiler as an optimization may be replacing your reified cast to Array<Double> with a non-reified cast to Array<out Double>, which is a safe cast.
You might want to consider using List or MutableList instead of Array to avoid having to deal with these problems.
Related
Okay, so to try to learn the kotlin language, I am trying to implement a heap.
class Heap <T : Comparable<T>>(private var heap: Array<T>, private var size: Int){
I am having some difficulties with how the nullable types behave. In the beginning I gave a array of elements, but I started having difficulties when I wanted to allocate more elements
private fun increaseSize(){
if(size == heap.size){
//okay this is garbage
var enlargedHeap = arrayOfNulls<Comparable<*>>(heap.size * 2) as Array<T>
System.arraycopy(heap, 0, enlargedHeap, 0, size)
heap = enlargedHeap
}
}
So here I am guessing I am changing the heap from Array<T> to Array<T?> which makes sense. So I changed the constructor as well, to take Array<T?> and it suggests some pretty interesting things wherever I try to access heap[x]
else heap[pos1].compareTo(heap[pos2]) < 0
to
else heap[pos2]?.let { heap[pos1]?.compareTo(it) }!! < 0
then I looked at a tooltip stating that arrayOfNulls<Comparable<*>>(heap.size * 2) as Array<T> returned Array<T>
But when doing the arrayOfNulls it indeed returns an array with null values. However I get an error if I try to heap[x] = null
stating
Null cannot be a value of non-null type T
If I change the constructor to take a Array<T?>, then input arrays have to explicitly nullable too.
Which I fixed with
class Heap <T : Comparable<T>>{
private var heap: Array<T?>
private var size = 0
constructor(heap: Array<T>, size){
this.heap = heap as Array<T?>
this.size = size
}
But now it does not accept arrays that has null values when I try to instantiate it with an array with nullable values
var arr = arrayOfNulls<String>(9)
var heap = Heap(arr, arr.size-1)
So now I need two constructors? What is going on
Type mismatch. Required: Comparable Found:String?
Even with Àrray<T?> there is errors with the compareTo's not accepting nullable values.
Even with checks it still gives an error
return if(pos2 > size || (heap[pos1] == null || heap[pos2] == null)) false
else heap[pos1].compareTo(heap[pos2]) < 0
Type mismatch. Required:T Found: T?
How do I allow the array to contain null values?
Hopefully without making Array<T> nullable since it breaks the compareTo's and multiple constructors seems like bad design.
Arrays are tricky to work with in a generic class because they have reified types.
I think you need to move the backing array property out of the constructor so the constructor won't be ambiguous about nullability. Otherwise type inference won't work with the constructor, and you would also be forced to use a nullable-typed array as the constructor parameter even if you want the heap to have a non-nullable type. Anyway, it's good that you copy the array from the constructor rather than using it directly as a backing array, because otherwise there is the possibility that some outside object could still be modifying that backing array.
Since Arrays always have reified type, I think you also need to use a backing array of type Any?. This will mean you have to cast objects that are passed out of the heap in the public functions. You can make a single get function for that, and use it internally with this[] to avoid having to put casts all over your class. The unchecked casting is safe as long as you don't put anything besides a T in the part of the array below size, and you don't try to get values above size, since they're always null and T might not be nullable.
class Heap <T : Comparable<T>>(initialValues: Array<T>, private var size: Int) {
init {
if (size > initialValues.size)
error("Initial array is too small for initial size.")
}
private var heap = Array<Any?>(size){ initialValues[it] }
#Suppress("UNCHECKED_CAST")
private operator fun get(index: Int) = heap[index] as T
private fun increaseSize(){
var enlargedHeap = arrayOfNulls<Any>(heap.size * 2)
System.arraycopy(heap, 0, enlargedHeap, 0, size)
heap = enlargedHeap
}
fun pop(): T {
if (size == 0)
error("Cannot pop when size is 0")
size--
val out = this[size] // Use this class's getter to avoid having to cast
heap[size] = null
return out
}
fun push(value: T) {
if (size == heap.size)
increaseSize()
heap[size] = value
size++
}
}
You might also consider moving the size parameter out of the constructor to make usage simpler and avoid the ambiguous case of what happens if the input size is smaller than the array you pass in.
class Heap <T : Comparable<T>>(initialValues: Array<T>) {
private var size = initialValues.size
private var heap = Array<Any?>(size){ initialValues[it] }
//...
Please use Array<T?>, instead of Array<T>.
E.g. in your class signature, properties should be:
class Heap <T : Comparable<T>>(private var heap: Array<T?>, private var size: Int) {
If you need input with not null values, please use something like this:
private var heap = initialHeap.toArray(); // copy input parameter to internal array
I didn't find how to declare in Kotlin an array with predefined size without initializate it.
This is OK:
lateinit var v:Array<Int>
But I guess that one cannot put a size specification in array type.
If one needs to specify a size. one have to do:
var v2:Array<Int> = Array<Int>(2){5}
In this case, all elements of the vector are equal to 5
Below there is a example with classes and arrays:
data class But(
val fufo: Int=0,
val tp: Int = 1
)
typealias ArBut = Array<But>
data class CArray (
var arrayC: ArBut = ArBut(2){But()}
)
val a = CArray(arrayOf(But(2,2),But(5,4),But(3,3)))
println(a.arrayC[2])
It works! The interesting part is that how the initialization is not part of type, you can put arrays of any size in the class without bounding check. It would be different if the size was part of type spec.
Now an exemple, using matrix. Notice that the syntax is a little bit intricate.
data class CMatr (
val matrC: Array<ArBut> = Array<ArBut>(2){ArBut(0){But()}}
)
val m = CMatr(arrayOf( arrayOf(But(2,2),But(5,4)),
arrayOf(But(-2,2),But(3,4)), arrayOf(But(1,1),But(5,3)) ))
println(m.matrC[2][1]) // Also works!
Is it impossible put size in array type specification or I'm missing something?
for primitive types :
this is how you do it. instead of using kotlin built-in functions like intArrayOf(args...) you use the constructor for IntArray
here is the example :
// Array of integers of a size of N
val arr = IntArray(N)
// Array of integers of a size of N initialized with a default value of 2
val arr = IntArray(N) { 2 }
for reference types :
for reference type objects you can do
val cars: Array<Car?> = arrayOfNulls(N)
//returns an array of nullable Car objects with null values and size of N
and if you want to an array of non null objects you need to initialize them when creating array
val cars: Array<Car> = Array<Car>(5){ Car() }
//returns an array of non nullable car objects that has been initialized
//with the method you provided in this case Car constructor with size of N
Yes, array size is not part of its type in Kotlin and there's no way to make it a part. This isn't specific to arrays; types can't depend on values in any way in Kotlin.
Size initialization in the type allow checking bound violation in runtime.
Array bounds are always checked at runtime on JVM. Even if a compiler wanted not to do it, it can't.
For your example, with the Butclass, you could use:
var arrayC: Array<But?> = arrayOfNulls(2) // no initialization required
or:
var arrayC: Array<But> = Array<But>(2){But()} // initialization required
But either way will not forbbid you of creating a new instance of a bigger array and assign it to the variable.
EDIT
The way I see it, there are two approaches to solve this.
The first would be to declare your array property as a var and test the assignement in your setter:
class Test {
var array: Array<Int> = Array(3){0}
set(value) {
if(value.size > 3)
throw IllegalArgumentException("The array size cannot be bigger than 3")
field = value
}
}
fun main(args: Array<String>) {
val test = Test()
test.array = arrayOf(0, 1, 2) // Ok
test.array = arrayOf(0, 1, 2, 3) // throws IllegalArgumentException
}
Or, if you want to deal with it at compile time, you can make your property final and initialize it with the size you want.
class Test {
val array: Array<Int> = Array(3){0}
}
fun main(args: Array<String>) {
val test = Test()
for (i in 0..2) // Ok
test.array[i] = i
for (i in 0..3) // throws ArrayIndexOutOfBoundsException
test.array[i] = i
test.array = arrayOf(0, 1, 2, 3) // compile time error: Val cannot be reassigned
}
I am trying to create an array of Char using another array of Int's size. Code doesn't compile:
object Main {
def main(args: Array[String]): Unit = {
val mapping = Map(1 -> "ABC", 2 -> "DEF")
val a = mapping.keySet.toArray
val c = Array[Char](a.length)
}
}
The compiler throws an error: "type mismatch; found : Int required: Char"
when I change the code above to:
val c = Array[Char](2) // no compiler error
Looks like the compiler is interpreting my input not as a size param but instead thinking it's a Char such as an initial element of the Char array
Since in java this code would compile without a problem I was wondering
what's the proper way of using another array length as a size param to init a different array in Scala?
You should be using .ofDim in your last line
val c = Array.ofDim[Char](a.length)
The second one works
val c = Array[Char](2)
as the compiler is treating 2 itself as a character.
Array type in scala has one confusing aspect, let me help you clarify it:
1.Array type has a class and a object, the object is called companion object for the specific class.
2.object Array has an apply method which in the code you use, but it can not be construct the same as the companion class.
To this snippet of code, the solution is:
object Main {
def main(args: Array[String]): Unit = {
val mapping = Map(1 -> "ABC", 2 -> "DEF")
val a = mapping.keySet.toArray
val c = new Array[Char](a.length)
}
}
Please be careful about the change of it, add new keyword to the created Array class.
In Swift, I have a function that I am passing an array to, and then using that array in another function. I keep getting this error:
Cannot convert value of type 'Array[String]' to expected argument type 'Set<String>'
#objc func getProductInfo(productIDs: Array<String>) -> Void {
print(productIDs) //this works with correct data
SwiftyStoreKit.retrieveProductsInfo(productIDs) { result in
...
The rest works, and is tested when I pass in a regular array of ["Monthly", "Yearly", "etc..."].
["Monthly", "Yearly", "etc..."] is not an array, it's an array literal. Set can be implicitly initialized with an array literal.
let ayeSet: Set<String> = ["a"] // Compiles
But, it cannot be implicitly initialized with an array.
let bees: Array<String> = ["b"]
let beeSet: Set<String> = bees // Causes Compiler Error
However, if you explicitly initialize it, then it will work.
let sees: Array<String> = ["c"]
let seeSet: Set<String> = Set(sees) // Compiles
So, in your example explicitly initialization should work.
#objc func getProductInfo(productIDs: Array<String>) -> Void {
print(productIDs) //this works with correct data
SwiftyStoreKit.retrieveProductsInfo(Set(productIDs)) { result in
...
You just need to change you method parameter type. SwiftyStoreKit method is expecting a String Set. Your method declaration should be:
func getProductInfo(productIDs: Set<String>)
I've face the issue using the same lib.
This should work
SwiftyStoreKit.retrieveProductsInfo(Set(productIDs))
I created a simple class with one field. class Test{int value;}
If I use the "preserve references" feature and set it to "all" (i.e. both objects and arrays), then when I simply serialize an array of Test objects, it gets serialized as a JSON object with a special "$values" member with the array values, along with the expected "$id" property to preserve the array reference. That much is fine, but once again the whole thing breaks on deserialization.
Stepping through the source code, I discovered that simply because the test for "IsReadOnlyOrFixedSize" is true, it sets a flag "createdFromNonDefaultConstructor" to true, which doesn't even make any sense, because although it is a fixed size array, it is created from a default constructor, unless it considers any fixed size array constructor a non-default constructor. The bottom line is that it should be able to handle something so basic, and yet it throws this error: "Cannot preserve reference to array or readonly list, or list created from a non-default constructor".
How can I deserialize a basic array while preserving all references in JSON.NET without getting an error?
Got the same issue, I used List<T> instead of T[] to fix it.
You are most likely missing a call to ToObject(...) and a type cast.
This should work:
class Test { public int Value; }
class Program
{
static void Main(string[] args)
{
var array = new Test[2];
var instance = new Test {Value = 123};
array[0] = instance;
array[1] = instance;
var settings = new JsonSerializerSettings
{
PreserveReferencesHandling = PreserveReferencesHandling.All
};
string serialized = JsonConvert.SerializeObject(array, settings);
// Explicitly call ToObject() and cast to the target type
var deserialized = (Test[]) ((JArray)JsonConvert.DeserializeObject(serialized, settings)).ToObject(typeof(Test[]));
Debug.Assert(deserialized[0].Value == 123);
}
}