0

Kotlin for competitive programming suggests the following code for reading console input.

readLine()!!.split(" ").map{ str -> str.toInt() } //read space separated Integer from console

Until now for every competitive problem I've used the same approach and to be honest, it has never disappointed.

But for certain problems where the count of input integers is very large (close to 2 * 10^6) this method is just too slow and results in TLE (Time Limit Exceeded).

Is there even a faster way to read input from console?

David Soroko
  • 8,521
  • 2
  • 39
  • 51
iCantC
  • 2,852
  • 1
  • 19
  • 34

3 Answers3

2

If you suspect that the .split() call is the bottleneck, you could explore some of the alternatives in this thread.

If you suspect that the toInt() call is the bottleneck, perhaps you could try parallelizing the streams using the Java 8 stream API. For example:

readLine()!!.split(" ").parallelStream().map { str -> str.toInt() }...

For best performance, you could probably combine the two methods.

Lukas Strobel
  • 108
  • 1
  • 5
0

I believe, toInt() convertions are more expensive, than split(" ").

Are you sure you need to convert to Int all strings of the input in the very beginning?

It depends on a task, but sometimes part of this convertions could be avoided. For instance, if you have a task "check if there is no negative numbers in the input", you may convert strings to Int one by one, and if you met a negative one, no need to convert others.

0

I think that JMH could be useful here. You can run the benchmark similar to the one bellow and try to identify your bottlenecks.

Note that this is in Mode.SingleShotTime, and so emulates the scenarion where JIT has little opprotunity to do it's thing.

import org.openjdk.jmh.annotations.*
import java.util.concurrent.TimeUnit
import kotlin.random.Random

//@BenchmarkMode(Mode.AverageTime)
@BenchmarkMode(Mode.SingleShotTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@State(Scope.Benchmark)
open class SplitToInt {

    val count = 2 * 1_000_000

    lateinit var stringToParse: String
    lateinit var tokensToParse: List<String>

    @Setup(Level.Invocation)
    fun setup() {
        stringToParse = (0..count).map { Random.nextInt(0, 100) }.joinToString(separator = " ")
        tokensToParse = (0..count).map { Random.nextInt(0, 100) }.map { it.toString() }
    }

    @Benchmark
    open fun split() =
        stringToParse.split(" ")

    @Benchmark
    open fun map_toInt() =
        tokensToParse.map { it.toInt() }


    @Benchmark
    open fun split_map_toInt() =
        stringToParse.split(" ").map { it.toInt() }
}

The stats on my machine are:

Benchmark                   Mode  Cnt    Score   Error  Units
SplitToInt.map_toInt          ss        48.666          ms/op
SplitToInt.split              ss       124.899          ms/op
SplitToInt.split_map_toInt    ss       186.981          ms/op

So splitting the string and mapping to list of Ints takes ~ 187 ms. Allowing JIT to warm up (Mode.AverageTime) gives me:

Benchmark                   Mode  Cnt    Score    Error  Units
SplitToInt.map_toInt        avgt    5   30.670 ±  6.979  ms/op
SplitToInt.split            avgt    5  108.989 ± 23.569  ms/op
SplitToInt.split_map_toInt  avgt    5  120.628 ± 27.052  ms/op

Whether this is fast or slow depends on the curmstances but are you sure that the input transformation here is the reason you get TLE?

Edit: If you do think that split(" ").map{ str -> str.toInt() } is too slow, you could replace creating the two lists (one from split and one from map) with a single list by splitting and transforming in one go. I wrote a quick hack off kotlin.text.Regex.split that does that and it is about 20% faster.

If in your use case you need to examine only part of the input, splitToSequence is probably a better option.

David Soroko
  • 8,521
  • 2
  • 39
  • 51