【问题标题】:Kotlin - Find minimum value in an IntArray within range of indicesKotlin - 在索引范围内的 IntArray 中查找最小值
【发布时间】:2021-09-19 06:46:54
【问题描述】:

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

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

return data.minOf().indexOf()

...但仅在 dataab 索引之间进行迭代。

函数如下:

// 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 方法在不会对我当前解决方案的性能产生负面影响的范围内迭代数组?

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

【问题讨论】:

  • 如果不引入 Ints 的装箱,我认为你不能让它更漂亮,这会影响性能。

标签: loops kotlin iterator idioms


【解决方案1】:

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

  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 所说:

过早的优化是万恶之源

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

【讨论】:

  • 值得注意的是你不需要自定义迭代器——你可以做类似intArray.sliceArray(a..b).withIndex().minByOrNull { it.value }!!.index + a
  • 是的,但这不是那么可读,并且会分配一个新数组
【解决方案2】:

正如 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]

【讨论】:

  • 我认为for (i in...range.forEach 更可取。除了更具可读性之外,编译器还优化了 IntRange,因此不必对其进行实例化。不过,这是一个小班的单一分配,非常小。不过,在迭代 2D 集合时要记住一些事情。
  • @Tenfour04 我不打算发布.forEach 部分,但确实尝试查看它的外观,但忘记改回来了。
  • Alex T,感谢您注意到变量名称。你可能已经猜到了,有一个类似的结构函数可以返回最大值,当我复制东西时我一定把它们弄混了。我会进行编辑以清除它。我将把它标记为正确答案,因为它回答了我的问题,坦率地说,我认为使用扩展函数正是我需要做的来重构/缩小其中的一些代码。
  • 感谢推荐扩展功能的用户!
猜你喜欢
  • 2018-07-27
  • 1970-01-01
  • 2015-10-25
  • 1970-01-01
  • 2019-02-11
  • 1970-01-01
  • 1970-01-01
  • 2020-09-04
  • 2018-11-14
相关资源
最近更新 更多