【问题标题】:How to slice vararg argumentHow to slice vararg argument
【发布时间】:2022-11-20 12:34:41
【问题描述】:

I wrote an extension function to get an element of an JSON object by its name:

fun JSONObject.obj (name: String): JSONObject? =
    try { this.getJSONObject(name) }
    catch (e: JSONException) { null }

Now I want to extend this for nested JSON objects. I wrote the following:

tailrec fun JSONObject.obj (first: String, vararg rest: String): JSONObject? =
    if (rest.size == 0)
        obj(first)
    else
        obj(first)?.obj(rest[0], *rest.drop(1).toTypedArray())

But this looks quite inefficient to me.

What is the best way to slice a vararg argument?

【问题讨论】:

  • If you're just asking about the slicing, a vararg is just an array, so you can use rest.sliceArray(1 until rest.size) to avoid converting to a list and back
  • @cactustictacs Using sliceArray is probably also not efficient. It seems to be a misnomer, because the function copies the array.
  • It makes a new array of the length you want and copies items into it, i.e. fills it with references to the Strings in the original array. You're not going to get much more efficient than that - what else are you expecting? What do you mean bysliceif not copying a range of elements into another array?

标签: kotlin variadic-functions


【解决方案1】:

We could use vararg only in the public function, but then internally use list for recursion:

fun JSONObject.obj (first: String, vararg rest: String): JSONObject? = obj(first, rest.asList())

private tailrec fun JSONObject.obj (first: String, rest: List<String>): JSONObject? =
    if (rest.size == 0)
        obj(first)
    else
        obj(first)?.obj(rest[0], rest.subList(1, rest.size))

Both asList() and subList() don't copy data, but only wrap the existing collection. Still, this is far from ideal, because it creates a new object for each iteration and it may create a chain of views (it depends on internal implementation of subList()). Alternatively, the internal function could receive an array and offset - this will solve both above problems.

Generally, I suggest to not try turning Kotlin into something it is not. It has limited support for functional constructs, but it is not a functional language. Without the linked list implementation which could be easily split into head and tail, this style of code will be always inefficient and/or cumbersome. You can look for such implementation, for example in Arrow or kotlinx.collections.immutable. The latter has ImmutableList with optimized subList() - you can use it with the solution provided above to avoid creating a chain of lists.

Update

As a matter of fact, basic lists implementations in the Java stdlib also provide optimized subList(): AbstractList.java. Therefore, the above solution using simply asList() should be fine, at least when targeting JVM.

【讨论】:

  • If the tailrec function is private, it can also work on an iterator.
【解决方案2】:

Instead of slicing, why don't you try just iterating over all the objects and getting the JSONObjects? I think this would be much more efficient.

fun JSONObject.obj(vararg names: String): JSONObject? {
    var jsonObject = this
    for (name in names) {
        if (!jsonObject.has(name))
            return null
        jsonObject = jsonObject.getJSONObject(name)
    }
    return jsonObject
}

【讨论】:

  • I was so happy that Kotlin has tail call optimization. I had to use it. ;-)
猜你喜欢
  • 2022-02-24
  • 2022-12-02
  • 1970-01-01
  • 1970-01-01
  • 2022-11-20
  • 2022-12-02
  • 2022-12-02
  • 2022-12-01
  • 2011-04-06
相关资源
最近更新 更多