Почему добавление к списку плохое?
Недавно я начал изучать scala, и я столкнулся с функцией ::
(cons), которая добавляется к списку.
В книге "Программирование в Scala" указано, что нет функции добавления, поскольку добавление к списку имеет производительность o (n), тогда как прединдж имеет производительность o (1)
Что-то просто поражает меня как неправильное в этом утверждении.
Не зависит ли производительность от реализации? Невозможно ли просто реализовать список с прямыми и обратными ссылками и сохранить первый и последний элементы в контейнере?
Второй вопрос, который я предполагаю, - это то, что я должен делать, когда у меня есть список, скажем 1,2,3, и я хочу добавить 4 до конца?
Ответы
Ответ 1
Ключ в том, что x :: somelist
не мутирует somelist
, а создает новый список, содержащий x, за которым следуют все элементы somelist
. Это можно сделать в O (1) раз, потому что вам нужно установить somelist
как преемника x
во вновь созданный, отдельно связанный список.
Если вместо этого использовались дважды связанные списки, x
также должен был быть установлен как предшественник главы somelist
, который изменил бы somelist
. Поэтому, если мы хотим иметь возможность делать ::
в O (1) без изменения исходного списка, мы можем использовать только отдельные списки.
Что касается второго вопроса: вы можете использовать :::
для объединения одного элемента списка в конец вашего списка. Это операция O (n).
List(1,2,3) ::: List(4)
Ответ 2
Другие ответы дали хорошие объяснения этому явлению. Если вы добавляете множество элементов в список в подпрограмме или если вы создаете список, добавляя элементы, функциональная идиома состоит в том, чтобы создать список в обратном порядке, минуя элементы в начале списка, затем переверните его в конце. Это дает вам O (n) производительность вместо O (n²).
Ответ 3
Prepending работает быстрее, потому что требуется только две операции:
- Создайте новый список node
- Укажите, что новый node указывает на существующий список
При добавлении требуется больше операций, потому что вам нужно пройти в конец списка, поскольку у вас есть только указатель на голову.
Я никогда не программировал в Scala раньше, но вы можете попробовать List Buffer
Ответ 4
Поскольку вопрос был просто обновлен, стоит отметить, что здесь все изменилось.
В сегодняшнем Scala вы можете просто использовать xs :+ x
для добавления элемента в конце любой последовательной коллекции. (Существует также x +: xs
для добавления. Мнемоника для многих операций по сборке Scala 2.8+ состоит в том, что col on идет рядом с col lection.)
Это будет O (n) со связанной по умолчанию реализацией List
или Seq
, но если вы используете Vector
или IndexedSeq
, это будет эффективно постоянное время. Scala Vector
, вероятно, Scala самая полезная коллекция в виде списка, в отличие от Java Vector
, которая в наши дни бесполезна.
Если вы работаете в Scala 2.8 или более поздней версии, введение коллекций является абсолютной необходимостью читать.
Ответ 5
В большинстве функциональных языков заметно выделяется структура данных с односвязным списком, так как это удобный тип неизменной коллекции. Когда вы говорите "список" на функциональном языке, это обычно означает то, что вы имеете в виду (односвязный список, обычно неизменный). Для такого типа append - O (n), тогда как cons - O (1).