Які структури даних ви можете використовувати, щоб отримати можливість видалення та заміни O (1)? Або як ви можете уникнути ситуацій, коли вам потрібні зазначені структури?
ST
Монада в Haskell робить це чудово.
Які структури даних ви можете використовувати, щоб отримати можливість видалення та заміни O (1)? Або як ви можете уникнути ситуацій, коли вам потрібні зазначені структури?
ST
Монада в Haskell робить це чудово.
Відповіді:
Існує велика кількість структур даних, що використовують лінощі та інші хитрощі для досягнення амортизованого постійного часу або навіть (для деяких обмежених випадків, таких як черги ) постійних оновлень часу для багатьох видів проблем. Кандидатська дисертація Кріса Окасакі "Чисто функціональні структури даних" та однойменна книга - це найкращий приклад (можливо, перший великий), але з цього часу ця сфера розвинулася . Ці структури даних, як правило, не тільки суто функціональні в інтерфейсі, але також можуть бути реалізовані в чистому Haskell і подібних мовах, і є повністю стійкими.
Навіть не маючи жодного з цих сучасних інструментів, прості врівноважені двійкові пошукові дерева дають оновлення з логарифмічним часом, так що мутаційну пам'ять можна змоделювати в гіршому випадку з логарифмічним сповільненням.
Є й інші варіанти, які можна вважати шахрайством, але дуже ефективні щодо зусиль щодо впровадження та ефективності в реальному світі. Наприклад, лінійні типи або унікальні типи дозволяють оновити на місці в якості стратегії реалізації концептуально чистої мови, не дозволяючи програмі утримуватися до попереднього значення (пам'ять, яка буде вимкнена). Це менш загально, ніж стійкі структури даних: Наприклад, ви не можете легко скласти журнал скасування, зберігаючи всі попередні версії стану. Це все ще потужний інструмент, хоча AFAIK ще не доступний на основних функціональних мовах.
Інший варіант безпечного введення змінного стану у функціональну обстановку - це ST
монада в Хаскелл. Він може бути реалізований без мутації та заборонених unsafe*
функцій, він веде себе так, ніби це був просто фантазійний обгортку, який передає стійку структуру даних неявно (див. State
). Але завдяки деякому типовому системному хитруванню, яке застосовує порядок оцінювання та запобігає уникненню, його можна сміливо реалізувати за допомогою мутації на місці з усіма перевагами від продуктивності.
Одна дешева змінна структура - це аргумент стека.
Погляньте на типовий факторний розрахунок у стилі SICP:
(defn fac (n accum)
(if (= n 1)
accum
(fac (- n 1) (* accum n)))
(defn factorial (n) (fac n 1))
Як бачимо, другий аргумент до fac
використовується як змінний акумулятор, щоб містити продукт, що швидко змінюється n * (n-1) * (n-2) * ...
. Немає видно змінної змінної, і немає способу ненавмисно змінити акумулятор, наприклад, з іншої нитки.
Це, звичайно, обмежений приклад.
Ви можете отримати незмінні пов'язані списки з дешевою заміною головного вузла (і шляхом розширення будь-якої частини, що починається з голови): ви просто зробите нову точку голови на той самий наступний вузол, що і стара стара голова. Це добре працює з багатьма алгоритмами обробки списків (що завгодно fold
).
Ви можете отримати досить хороші показники з асоціативних масивів на основі, наприклад, на HAMT . За логікою ви отримуєте новий асоціативний масив із зміненою парою ключ-значення. Реалізація може обмінюватися більшістю загальних даних між старими та новоствореними об'єктами. Однак це не O (1); зазвичай ви отримуєте щось логарифмічне, принаймні, в гіршому випадку. З іншого боку, дерева, що непорушні, зазвичай не зазнають покарання за продуктивність порівняно з деревами, що змінюються. Звичайно, для цього потрібні певні накладні об'єм пам’яті, як правило, далеко не надмірні.
Інший підхід заснований на ідеї, що якщо дерево потрапляє в ліс і його ніхто не чує, воно не повинно видавати звуку. Тобто, якщо ви зможете довести, що трохи мутований стан ніколи не залишає локальної області, ви можете безпечно мутувати дані всередині нього.
Clojure має перехідні процеси, які є змінними "тінями" незмінних структур даних, які не протікають за межами локальної сфери. Clean використовує Uniques для досягнення чогось подібного (якщо я правильно пам’ятаю). Іржа допомагає робити подібні речі зі статистично перевіреними унікальними покажчиками.
ref
та обмеження їх у певному обсязі. Побачити IORef
або STRef
. І, звичайно, є TVar
s і MVar
s, які схожі, але з розумною паралельною семантикою (stm for TVar
s та mutex на основі MVar
s)
Те, що ви запитуєте, трохи надто широке. O (1) зняття та заміна з якої посади? Голова послідовності? Хвіст? Довільна позиція? Структура даних, що використовується, залежить від цих деталей. Однак, 2-3 пальця здаються однією з найбільш універсальних стійких структур даних:
Ми представляємо 2-3 пальчикових дерев, функціональне зображення стійких послідовностей, що підтримують доступ до кінців за амортизованим постійним часом, а також конкатенацію та розщеплення за часом логарифмічним розміром на менший шматок.
(...)
Далі, визначаючи роздільну операцію в загальній формі, ми отримуємо структуру даних загального призначення, яка може слугувати послідовністю, чергою пріоритетів, деревом пошуку, чергою пошуку пріоритетів тощо.
Як правило, стійкі структури даних мають логарифмічну ефективність при зміні довільних позицій. Це може бути або не бути проблемою, оскільки константа в алгоритмі O (1) може бути високою, а логарифмічне уповільнення може бути "поглинене" в більш повільний загальний алгоритм.
Що ще важливіше, стійкі структури даних полегшують міркування щодо вашої програми, і це завжди повинен бути ваш режим роботи за замовчуванням. Ви повинні надавати перевагу стійким структурам даних, коли це можливо, і використовувати мутабельну структуру даних лише після того, як ви профілювали та визначили, що стійка структура даних є вузьким місцем продуктивності. Все інше - передчасна оптимізація.