Операции над списком намеренно не симметричны.Структура данных List представляет собой односвязный список, в котором каждый узел (и данные, и указатель) являются неизменяемыми.Идея, лежащая в основе этой структуры данных, заключается в том, что вы выполняете изменения в начале списка, беря ссылки на внутренние узлы и добавляя новые узлы, которые указывают на них - разные версии списка будут использовать одни и те же узлы для конца списка.
Оператор :::
, который добавляет новый элемент в конец списка, должен создать новую копию всего списка, потому что в противном случае он изменил бы другие списки, которые совместно используют узлы со списком, в котором вы находитесь.добавление к.Вот почему :::
занимает линейное время.Scala имеет структуру данных, называемую ListBuffer
, которую вы можете использовать вместо оператора :::
, чтобы ускорить добавление в конец списка.По сути, вы создаете новый ListBuffer
, и он начинается с пустого списка.ListBuffer
поддерживает список полностью отдельный от любого другого списка, о котором знает программа, поэтому его можно безопасно изменить, добавив в конец.Когда вы закончите добавление в конец, вы вызываете ListBuffer.toList
, который выпускает список в мир, после чего вы больше не можете добавлять в конец, не копируя его.
foldLeft
и foldRight
также имеют сходную асимметрию.foldRight
требует, чтобы вы прошли по всему списку, чтобы добраться до конца списка, и отслеживали все места, которые вы посетили на пути туда, чтобы вы посетили их в обратном порядке.Обычно это делается рекурсивно и может привести к foldRight
переполнению стека в больших списках.foldLeft
, с другой стороны, имеет дело с узлами в порядке их появления в списке, поэтому он может забыть те, которые он уже посещал, и ему нужно знать только об одном узле за раз.Хотя foldLeft
также обычно реализуется рекурсивно, он может воспользоваться оптимизацией под названием исключение хвостовой рекурсии , в которой компилятор преобразует рекурсивные вызовы в цикл, потому что функция ничего не делает после возврата изрекурсивный вызов.Таким образом, foldLeft
не переполняет стек даже в очень длинных списках. EDIT : foldRight
в Scala 2.8 на самом деле реализовано путем обращения к списку и запуска foldLeft
в обратном списке - поэтому проблема хвостовой рекурсии не является проблемой - обе структуры данных правильно оптимизируют хвостовую рекурсию, и вы можете выбрать любой из них (вы попадаете в проблему сейчас, когда определяете reverse
в терминах reverse
- вам не нужно беспокоиться, если вы определяете свой собственный Обратный метод для удовольствия, но у вас не было бы опции foldRight, если бы вы определяли обратный метод Scala.)
Таким образом, вы должны предпочесть foldLeft
и ::
над foldRight
и :::
.
(В алгоритме, который будет объединять foldLeft
с :::
или foldRight
с ::
, вам необходимо принять решение для себя, что является более важным:место в стеке или время выполнения. Или вы должны использовать foldLeft
с ListBuffer
.)