kotlinmutablelist

MutableList of MutableLists in Kotlin: adding element error


Why I'm getting "java.lang.IndexOutOfBoundsException: Index 0 out of bounds for length 0" while running next code??? :

val totalList = mutableListOf<MutableList<Int>>()

fun main() {
    for (i in 0..15) {
        for (j in 0..10) {
            *some operations and calculations with **var element of type Int***
            totalList[i].add(element)
        }
    }
}

I was thinking that in such case while iterating through 'j' it should add elements to mutableList[i], after this it should start adding elements to mutableList[i + 1] etc.... But instead I am recieving IndexOutOfBoundsException....


Solution

  • val totalList = mutableListOf<MutableList<Int>>()

    All this does is create one list which is going to contain MutableList<Int> items. Right now, there's nothing in it (you've supplied no initial elements in the parentheses).

    Skip forward a bit, and you do this:

    totalList[0].add(element)
    

    You're trying to get the first element of that empty list and add to it. But there is no first element (index 0) because the list is empty (length 0). That's what the error is telling you.

    There's lots of ways to handle this - one thing you could do is create your lists up-front:

    // create the 16 list items you want to access in the loop
    // (the number is the item count, the lambda generates each item)
    val totalList = MutableList(16) { mutableListOf<Int>() }
    
    // then refer to that list's properties in your loop (no hardcoded 0..15)
    for (i in totalList.indices) {
        ...
        // guaranteed to exist since i is generated from the list's indices
        totalList[i].add(element)
    }
    

    Or you could do it the way you are now, only using getOrElse to generate the empty list on-demand, when you try to get it but it doesn't exist:

    for (i in 0..15) {
        for (j in 0..10) {
            // if the element at i doesn't exist, create a list instead, but also
            // add it to the main list (see below)
            totalList.getOrElse(i) {
                mutableListOf<Int>().also { totalList.add(it) }
            }.add(element)
        }
    }
    

    Personally I don't really like this, you're using explicit indices but you're adding new list items to the end of the main list. That implicity requires that you're iterating over the list items in order - which you are here, but there's nothing enforcing that. If the order ever changed, it would break.

    I'd prefer the first approach - create your structure of lists in advance, then iterate over those and fill them as necessary. Or you might want to consider arrays instead, since you have a fixed collection size you're "completing" by adding items to specific indices


    Another approach (that I mentioned in the comments) is to create each list as a whole, complete thing, and then add that to your main list. This is generally how you do things in Kotlin - the standard library contains a lot of functional tools to allow you to chain operations together, transform things, and create immutable collections (which are safer and more explicit about whether they're meant to be changed or they're a fixed set of data).

    for (i in 0..15) {
        // map transforms each element of the range (each number) to an item,
        // resulting in a list of items
        val items = (0..10).map { j ->
            // do whatever you're doing
    
            // the last expression in the lambda is its resulting value,
            // i.e. the item that ends up in the list
            element
        }
    
        // now you have a complete list of items, add them to totalList
        totalList.add(items)
    }
    

    (Or you could create the list directly with List(11) { j -> ... } but this is a more general example of transforming a bunch of things to a bunch of other things)

    That example there is kinda half and half - you still have the imperative for loop going on as well. Writing it all using the same approach, you can get:

    val totalList = (0..15).map { i ->
        (0..10).map { j ->
            // do stuff
            element
        }
    }
    

    I'd probably prefer the List(count) { i -> ... } approach for this, it's a better fit (this is a general example). That would also be better since you could use MutableList instead of List, if you really need them to be mutable (with the maps you could just chain .toMutableList() after the mapping function, as another step in the chain). Generally in Kotlin, collections are immutable by default, and this kind of approach is how you build them up without having to create a mutable list etc. and add items to it yourself