4

I'm using val globalList = listOf("a1" to "b1", "a2" to "b2") to create a large list of Pairs of strings. All is fine until you try to put more than 1000 Pairs into a List. The compiler either takes > 5 minutes or just crashes (Both in IntelliJ and Android Studio). Same happens if you use simple lists of Strings instead of Pairs.

Is there a better way / best practice to include large lists in your source code without resorting to a database?

DerTroglodyt
  • 121
  • 9
  • May be better to create a data file and read the data from there? – IR42 Mar 30 '20 at 11:49
  • That was exactly my first go. But Testing without handing over a Context is another mine field. So I thought, I'd go the "easy" way of a literal list... – DerTroglodyt Mar 30 '20 at 12:34

3 Answers3

3

You can replace a listOf(...) expression with a list created using a constructor or a factory function and adding the items to it:

val globalList: List<Pair<String, String>> = mutableListOf().apply {
    add("a1" to "b1")
    add("a2" to "b2")
    // ...
}

This is definitely a simpler construct for the compiler to analyze.

hotkey
  • 140,743
  • 39
  • 371
  • 326
  • 1
    Even more appropriate function to use is [buildList](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/build-list.html). But it's experimental at this moment. – vbezhenar Mar 30 '20 at 11:13
  • Thanks for the heads up. Sadly both options (apply and buildList) don't help. – DerTroglodyt Mar 30 '20 at 12:32
  • @DerTroglodyt Why doesn't the `apply` option help? – gidds Mar 30 '20 at 14:43
  • @gidds I can't tell you why it doesn't help :-) The compiler just behaves the same: at about 8000 it starts to struggle, above 10k it crashes. – DerTroglodyt Mar 30 '20 at 15:01
1

If you need something quick and dirty instead of data files, one workaround is to use a large string, then split and map it into a list. Here's an example mapping into a list of Ints.

val onCommaWhitespace = "[\\s,]+".toRegex()  // in this example split on commas w/ whitespace
val bigListOfNumbers: List<Int> = """
  0, 1, 2, 3, 4,
     :
     :
     :
  8187, 8188, 8189, 8190, 8191
""".trimIndent()
       .split(onCommaWhitespace)
       .map { it.toInt() }

Of course for splitting into a list of Strings, you'd have to choose an appropriate delimiter and regex that don't interfere with the actual data set.

broc.seib
  • 21,643
  • 8
  • 63
  • 62
0

There's no good way to do what you want; for something that size, reading the values from a data file (or calculating them, if that were possible) is a far better solution all round — more maintainable, much faster to compile and run, easier to read and edit, less likely to cause trouble with build tools and frameworks…

If you let the compiler finish, its output will tell you the problem.  (‘Always read the error messages’ should be one of the cardinal rules of development!)

I tried hotkey's version using apply(), and it eventually gave this error:

… Caused by: org.jetbrains.org.objectweb.asm.MethodTooLargeException: Method too large: TestKt.main ()V …

There's the problem: MethodTooLargeException.  The JVM allows only 65535 bytes of bytecode within a single method; see this answer.  That's the limit you're coming up against here: once you have too many entries, its code would exceed that limit, and so it can't be compiled.

If you were a real masochist, you could probably work around this to an extent by splitting the initialisation across many methods, keeping each one's code just under the limit.  But please don't!  For the sake of your colleagues, for the sake of your compiler, and for the sake of your own mental health…

gidds
  • 16,558
  • 2
  • 19
  • 26