Самый быстрый способ удалить первые n элементов из MutableList

Я программирую на Kotlin и у меня есть MutableList, из которого я хотел бы удалить первые n элементы из этого конкретного экземпляра списка. Это означает, что о таких функциях, как MutableList.drop(n), не может быть и речи.

Одним из решений, конечно, было бы зацикливаться и вызывать MutableList.removeFirst()n раз, но это кажется неэффективным, так как O(n). Другим способом было бы выбрать другой тип данных, но я бы предпочел не загромождать свой проект, реализовав для этого свой собственный тип данных, если я могу этого избежать.

Есть ли более быстрый способ сделать это с помощью MutableList? Если нет, существует ли другой тип данных встроенный, который может достичь этого менее чем за O(n)?

К сожалению, в вашем случае ваш путь итеративный. Советую прочитать про работу списка под капотом, особое внимание следует уделить пересозданию списка при добавлении элементов и правильному использованию метода "trimToSize" при уменьшении количества элементов внутри списка.

rost 17.03.2022 08:49

Спасибо @rost! Я нашел другой способ, который кажется более быстрым, чем итеративный вызов removeFirst(), который я описываю здесь: stackoverflow.com/a/71508562/9977691

Filip Östermark 17.03.2022 08:58
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
2
49
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

На мой взгляд, лучший способ добиться этого 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

Недостатком этого подхода является то, что он сохраняет исходный список со всеми его элементами в памяти до тех пор, пока есть ссылка на подсписок. (Во многих случаях это может не быть проблемой, но об этом стоит помнить.)

gidds 17.03.2022 10:14
Ответ принят как подходящий

Один метод, который кажется более быстрым, если n достаточно велик, выглядит следующим образом:

  1. Сохраните последние listSize - n байты во временном списке,
  2. Очистить экземпляр исходного списка
  3. Добавить временный список в исходный список

Вот краткий тест для некоторых примеров значений, которые подходят для моего варианта использования:

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 17.03.2022 10:12

@gidds Вы правы в обоих случаях, теперь я уточнил свой ответ, чтобы отразить это: (1) если n достаточно велико, подход clear + addAll кажется более быстрым, и (2) эталонный тест, по общему признанию, довольно наивен, но четкий + Подход addAll был стабильно быстрее после повторного запуска тестов несколько раз при переключении заказов.

Filip Östermark 17.03.2022 10:19

@gidds Мне стало любопытно, и я поэкспериментировал со значением maxRemove в моем обновленном тесте. Метод clear+addAll кажется в среднем быстрее, если n больше примерно 40. Размер списка не оказал значительного влияния.

Filip Östermark 17.03.2022 10:42

Другие вопросы по теме