Чому додавання до списку в Scala має складну часові (O) n?


13

Я щойно прочитав, що час виконання операції додавання для List(: +) лінійно зростає з розміром List.

Звернення до Listздається схоже на досить поширену операцію. Чому ідіоматичний спосіб зробити це - попередньо допомігши компоненти, а потім змінити список? Це також не може бути помилкою дизайну, оскільки реалізація може бути змінена в будь-який момент.

З моєї точки зору, і попереднє, і додавання повинно бути O (1).

Чи є законна причина цього?


2
Залежить від вашого визначення поняття "легітимний". Scala сильно входить до змінних структур даних, всюдисущих анонімних списків, функціональної композиції тощо. Реалізація списку за замовчуванням (без зайвого змінного покажчика на хвіст списку) добре працює для цього стилю. Якщо вам потрібен більш потужний список, принаймні дуже просто написати власний контейнер, який майже не відрізняється від стандартного.
Кіліан Фот

1
Перехресний сайт, пов’язаний - додавання елемента до кінця списку в Scala - там є дещо про його природу. Це здається , що список у Скалі незмінний, тому вам потрібно скопіювати його, що O (N).

Ви можете використовувати одну з безлічі доступних змінних структур даних або незмінні структури даних з часом додавання (вектор) O (1), який надає Scala. List[T]припускає, що ви використовуєте його так, як ви б чистим функціональним мовою - як правило, працюючи з голови з деконструкцією та претендує на себе.
KChaloux

3
Попереднє додавання наступного вказівника вузла нової голови до існуючого непорушного списку - який не може змінитися. То O (1).

1
Для семінарної роботи та аналізу на загальну тему вимірювання складності структури даних у чистому ПЗ прочитайте дисертацію Окасакі, яка згодом була опублікована як книга. Це дуже цінується і дуже добре читає для тих, хто навчається на певній програмі, щоб зрозуміти, як думати про організацію даних у програмі. Це також добре написане і легко читати і дотримуватися настільки якісний текст.
Джиммі Хоффа

Відповіді:


24

Я трохи розгорну свій коментар. Структура List[T]даних, від scala.collection.immutableоптимізована для роботи так, як працює незмінний список на більш чисто функціональній мові програмування. Це дуже швидкий набір часу, і передбачається, що ви будете працювати над головою майже весь свій доступ.

У незмінних списках є дуже швидкі передумови через те, що вони моделюють свої зв'язані списки у вигляді серії "мінусових комірок". Клітина визначає єдине значення та вказівник на наступну клітинку (класичний стиль одиночно пов'язаного списку):

Cell [Value| -> Nil]

Коли ви додаєте до списку, ви дійсно просто створюєте одну нову клітинку, а решта існуючого списку вказується на:

Cell [NewValue| -> [Cell[Value| -> Nil]]

Оскільки список незмінний, ви можете це зробити без фактичного копіювання . Немає небезпеки зміни старого списку і приведення всіх значень вашого нового списку до недійсних. Однак ви втрачаєте можливість мати змінний вказівник на кінець свого списку як компроміс.

Це дуже добре рекурсивно працює над списками. Скажімо, ви визначили свою власну версію filter:

def deleteIf[T](list : List[T])(f : T => Boolean): List[T] = list match {
  case Nil => Nil
  case (x::xs) => f(x) match {
    case true => deleteIf(xs)(f)
    case false => x :: deleteIf(xs)(f)
  }
}

Це рекурсивна функція, яка працює виключно з заголовка списку і використовує переваги відповідності шаблонів через :: екстрактор. Це те, що ви багато бачите в таких мовах, як Haskell.

Якщо ви дійсно бажаєте швидкого додавання, Scala надає безліч змінних та незмінних структур даних на вибір. З боку, що змінюється, ви можете заглянути ListBuffer. Крім того, Vectorвід scala.collection.immutableмає швидкий час додавання.


Тепер я розумію! Це має повний сенс.
DPM

Я не знаю жодної Скали, але хіба elseце нескінченна петля? Я думаю, що це має бути щось на кшталт x::deleteIf(xs)(f).
svick

@svick Uh ... так. Так. Я написав це швидко і не підтвердив свій код, тому що мені довелося зустрітися: p (Має бути виправлено зараз!)
KChaloux

@Jubbat Тому headі tailдоступ з таким списком дуже швидко - швидше , ніж при використанні будь-якого хеша на основі карти або масиву - це відмінний тип для рекурсивних функцій. Це одна з причин, чому списки є основним типом у більшості функціональних мов (наприклад, Haskell або Scheme)
itsbruce

Відмінна відповідь. Я, можливо, я б додав TL; DR, який просто говорить "тому що ви повинні готуватися, а не додавати" (це може допомогти очистити базові припущення більшості розробників щодо Lists та додавання / попереднє додавання).
Даніель Б
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.