Kotlin - 在索引范围内的 IntArray 中查找最小值

问题描述

我正在将一些旧的 Java 代码重构到 Kotlin。有一个函数返回 Kotlin IntArray 范围内的元素所持有的最小值的索引,范围为 [a,b]。范围值认为 0,数组大小认为 1。

我想做一些类似……的事情

return data.minOf().indexOf()

...但只在 abdata 索引之间迭代。

功能如下:

// data is the IntArray property that I'm looping through. 
fun absMinIndex(a: Int = 0,b: Int = (data.size - 1)) : Int {
    var minVal = data[a]
    var minIndex = 0

    for (i in (a + 1)..b) {
        val e = data[i]
        if (e < minVal) {
            minVal = e
            minIndex = i
        }
    }
    return maxIndex
}

这个 [for 循环] 通过从不访问超出范围的索引,并且不生成复制的数组/子数组,很好地解决了这个问题。我想知道它是否可以做得“更漂亮”。

问题

是否有更惯用的 Kotlin 方法在不会对我当前解决方案的性能产生负面影响的范围内迭代数组?

为了清晰起见编辑了一些代码

解决方法

我相信这种方法会更惯用:

  1. 使用 IntRange 作为输入参数
  2. IntArray 定义扩展方法,提供自定义迭代器以将所需范围内的列表遍历到 IndexedValue 中:
fun IntArray.withIndexInRange(range: IntRange = 0..lastIndex) = Iterable {
    require(range.first >= 0 && range.last <= lastIndex)
    object : Iterator<IndexedValue<Int>> {
        private var index = range.first
        override fun hasNext() = index <= range.last
        override fun next() = IndexedValue(index,this@withIndexInRange[index++])
    }
}
  1. 使用 stdlib 中的 minByOrNull 方法查找最小值或为方便起见将其包装到另一个扩展方法中:
fun <T : Comparable<T>> Iterable<IndexedValue<T>>.indexOfMinOrNull() = minByOrNull { it.value }?.index

用法:

data.withIndexInRange(a..b).indexOfMinOrNull()

请注意,这会带来一些性能损失(创建和 GC 的 N 个额外对象),但正如 Donald Knuth 所说:

过早优化是万恶之源

所以,我相信更好的可读性是值得的。

,

正如 Tenfour04 所建议的那样,在不降低性能的情况下您无能为力。但是按照你的想法,改变你调用它的方式,你可以把它变成一个扩展函数。

fun IntArray.findIndexOfMinInRange(a: Int = 0,b: Int = this.size - 1): Int {
    var maxVal = get(a)
    var maxIndex = 0

    for (i in (a + 1)..b) {
        if (get(i) < maxVal) {
            maxVal = get(i)
            maxIndex = i
        }
    }
    return maxIndex
}

//and call it like this: 

data.findIndexOfMinInRange(0,15) //or without anything in the parentheses for the default values

我要更改的一件事是函数内的变量名称,我们正在搜索最小值,而不是最大值,以及最小值的索引,而不是最大值索引。也可能(可能)创建一个 data[it] 的 val 而不是访问它两次可能会更好(老实说不确定,我们会用 .get 换取几个字节的内存)。

总而言之,我可能会留在这里:

fun IntArray.findIndexOfMinInRange(fromIndex: Int = 0,toIndex: Int = this.size - 1): Int {
    var min = get(fromIndex)
    var indexOfMin = fromIndex

    for(i in (fromIndex + 1)..toIndex){
        val current = get(i)
        if (current < min) {
            min = current
            indexOfMin = i
        }
    }

    return indexOfMin
}

//would be called in the same way the one above

另外,请注意,如果您创建了一个特定大小的 IntArray,并且没有完全填充它,那么它将为未填充的默认值保留为 0。如果你这样做:

val data = IntArray(6)
data[0] = 10
data[1] = 11
data[2] = 100
data[3] = 9
data[4] = 50

那么实际的数组是[10,11,100,9,50,0]