Я программирую на Kotlin и у меня есть MutableList, из которого я хотел бы удалить первые n
элементы из этого конкретного экземпляра списка. Это означает, что о таких функциях, как MutableList.drop(n)
, не может быть и речи.
Одним из решений, конечно, было бы зацикливаться и вызывать MutableList.removeFirst()
n
раз, но это кажется неэффективным, так как O(n
). Другим способом было бы выбрать другой тип данных, но я бы предпочел не загромождать свой проект, реализовав для этого свой собственный тип данных, если я могу этого избежать.
Есть ли более быстрый способ сделать это с помощью MutableList? Если нет, существует ли другой тип данных встроенный, который может достичь этого менее чем за O(n
)?
Спасибо @rost! Я нашел другой способ, который кажется более быстрым, чем итеративный вызов removeFirst(), который я описываю здесь: stackoverflow.com/a/71508562/9977691
На мой взгляд, лучший способ добиться этого
abstract fun subList(fromIndex: Int, toIndex: Int): List<E>
.
https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-list/sub-list.html
Под капотом он создает новый экземпляр списка (класс SubList для AbstractClass) с элементами между выбранными индексами.
С использованием:
val yourList = listOf<YourType>(...)
val yourNewList = yourList.subList(5, yourList.size)
// return list from 6th elem to last
Недостатком этого подхода является то, что он сохраняет исходный список со всеми его элементами в памяти до тех пор, пока есть ссылка на подсписок. (Во многих случаях это может не быть проблемой, но об этом стоит помнить.)
Один метод, который кажется более быстрым, если n
достаточно велик, выглядит следующим образом:
listSize - n
байты во временном списке,Вот краткий тест для некоторых примеров значений, которые подходят для моего варианта использования:
val numRepetitions = 15_000
val listSize = 1_000
val maxRemove = listSize
val rnd0 = Random(0)
val rnd1 = Random(0)
// 1. Store the last `listSize - n` bytes to keep in a temporary list,
// 2. Clear original list
// 3. Add temporary list to original list
var accumulatedMsClearAddAll = 0L
for (i in 0 until numRepetitions) {
val l = Random.nextBytes(listSize).toMutableList()
val numRemove = rnd0.nextInt(maxRemove)
val numKeep = listSize - numRemove
val startTime = System.currentTimeMillis()
val expectedOutput = l.takeLast(numKeep)
l.clear()
l.addAll(expectedOutput)
val endTime = System.currentTimeMillis()
assert(l == expectedOutput)
accumulatedMsClearAddAll += endTime - startTime
}
// Iteratively remove the first byte `n` times.
var accumulatedMsIterative = 0L
for (i in 0 until numRepetitions) {
val numRemove = rnd1.nextInt(maxRemove)
val l = Random.nextBytes(listSize).toMutableList()
val expectedOutput = l.takeLast(listSize - numRemove)
val startTime = System.currentTimeMillis()
for (ii in 0 until numRemove) {
l.removeFirst()
}
val endTime = System.currentTimeMillis()
assert(l == expectedOutput)
accumulatedMsIterative += endTime - startTime
}
println("clear+addAll removal: $accumulatedMsClearAddAll ms")
println("Iterative removal: $accumulatedMsIterative ms")
Выход:
Clear+addAll removal: 478 ms
Iterative removal: 12683 ms
Как вы рассчитали эти тайминги? Общеизвестно, что микротесты сложны; слишком легко измерить запуск, оптимизацию и т. д. JVM вместо вашего кода. Кроме того, не будет ли компромисс между подходами зависеть как от количества удаленных элементов, так и от количества оставшихся? Удаление 2 элементов из списка из 10 000 элементов сильно отличается от удаления 99 9998.
@gidds Вы правы в обоих случаях, теперь я уточнил свой ответ, чтобы отразить это: (1) если n
достаточно велико, подход clear + addAll кажется более быстрым, и (2) эталонный тест, по общему признанию, довольно наивен, но четкий + Подход addAll был стабильно быстрее после повторного запуска тестов несколько раз при переключении заказов.
@gidds Мне стало любопытно, и я поэкспериментировал со значением maxRemove
в моем обновленном тесте. Метод clear+addAll кажется в среднем быстрее, если n
больше примерно 40. Размер списка не оказал значительного влияния.
К сожалению, в вашем случае ваш путь итеративный. Советую прочитать про работу списка под капотом, особое внимание следует уделить пересозданию списка при добавлении элементов и правильному использованию метода "trimToSize" при уменьшении количества элементов внутри списка.