Кіріспе

Өзгертілген кезде әрқашан өзінің алдыңғы нұсқасын сақтайтын дерек құрылымы. Есептеу техникасында тұрақты дерек құрылымы немесе уақытша емес дерек құрылымы – өзгертілген кезде әрқашан өзінің алдыңғы нұсқасын сақтайтын дерек құрылымы. Мұндай дерек құрылымдары іс жүзінде өзгермейтін болып табылады, себебі олардың операциялары құрылымды (көрінетін) орнында жаңартпайды, бірақ әрқашан жаңа жаңартылған құрылымды шығарады. Бұл термин Дрисколл, Сарнак, Слейтор және Таржанның 1986 жылғы мақаласында енгізілді. Дерек құрылымы ішінара тұрақты болып есептеледі, егер барлық нұсқаларға қол жеткізуге болады, бірақ тек соңғы нұсқаны ғана өзгертуге мүмкіндік бар. Дерек құрылымы толық тұрақты болып есептеледі, егер әрбір нұсқаға қол жеткізуге де, өзгертуге де болады. Егер екі бұрынғы нұсқадан жаңа нұсқа жасауға мүмкіндік беретін біріктіру немесе қосу операциясы болса, онда дерек құрылымы конфлюенттік тұрақты деп аталады. Тұрақты емес құрылымдар эфемерлік деп аталады. Дерек құрылымының осы түрлері логикалық және функционалдық бағдарламалауда кеңінен қолданылады. Толық тұрақты модельде дерек құрылымының кез келген нұсқасына жаңартулар мен сұраулар жіберуге рұқсат етіледі. Кейбір жағдайларда, арқандық дерек құрылымында болатындай, дерек құрылымының ескі нұсқаларын сұрау немесе жаңарту жылдамдығы төмендеуі мүмкін. Сонымен қатар, дерек құрылымы, толық тұрақты болумен қатар, егер бір дерек құрылымының екі нұсқасын біріктіріп, толық тұрақты жаңа нұсқа жасауға болады, онда ол конфлюенттік тұрақты деп аталады.

Деректердің ішінара тұрақты құрылымы

Деректер құрылымының бір түрі, онда пайдаланушы құрылымның кез келген нұсқасына сұраныс жібере алады, бірақ тек соңғы нұсқасын ғана жаңарта алады. Эфемерлік деректер құрылымын бірнеше техниканы қолдану арқылы ішінара тұрақты деректер құрылымына түрлендіруге болады. Осы техникалардың бірі – динамикалық кемелді хэштеуді пайдалана отырып құрылған Ван Эмде Боас ағашының кездейсоқ нұсқасын пайдалану. Бұл деректер құрылымы келесідей құрылады: m элементі бар қабатталған ағаш динамикалық кемелді хэштеуді қолдана отырып іске асырылады. Ағаш m элементті log(log n) өлшемді бөліктерге бөліп қысқартылады, мұнда 1-ші бөліктің элементтері 2-ші бөліктің элементтерінен кіші болады, және т.с.с. Әрбір бөліктің ең үлкен элементі қабатталған ағашта сақталады, ал әрбір бөлік ретсіз байланысты тізім ретінде құрылымда сақталады. Бұл деректер құрылымының көлемі құрылымда сақталған элементтер санымен шектеледі, яғни O(m). Жаңа максималды элементті енгізу орташа және амортизацияланған уақытта тұрақты O(1) уақытта жүзеге асырылады. Соңында, элементті табу сұранысын осы құрылымда ең нашар жағдайда O(log(log n)) уақытта жасауға болады.

Жазуға көшіру

Тұрақты дерек құрылымдарын құрудың бір әдісі – дерек құрылымындағы деректерді сақтау үшін массив сияқты платформа ұсынған уақытша дерек құрылымдарын пайдалану, ал дерек құрылымына кез келген өзгеріс енгізілгенде, дерек құрылымының толық көшірмесін жасау арқылы жазу кезінде көшіру (copy-on-write) принципін қолдану. Бұл тиімсіз техника, себебі әрбір жазу операциясы үшін негізгі дерек құрылымының толығымен көшірмесі жасалуы керек, нәтижесінде n өлшемді массивке m өзгеріс енгізу кезінде ең жаман жағдайда O(n·m) өнімділік көрсеткіштеріне жетеді.

Майлық түйін

Майлы түйін әдісі – түйіндердің өрістеріне енгізілген барлық өзгерістерді өрістердің ескі мәндерін жоймай, түйіндердің өзінде тіркеу. Бұл түйіндердің кез келген деңгейде «семіруіне» мүмкіндік беруді қажет етеді. Яғни, әрбір майлы түйін уақытша түйін сияқты бірдей ақпаратты және сілтеме өрістерін, сонымен қатар кез келген сандағы қосымша өріс мәндеріне орын қамтиды. Әрбір қосымша өріс мәніне өрістің аты және нұсқа таңбасы сәйкес келеді, ол аталған өріс нақты мәнге ие болу үшін қандай нұсқада өзгертілгенін көрсетеді. Сонымен қатар, әрбір майлы түйінде өзінің нұсқа таңбасы болады, ол түйіннің қандай нұсқада жасалғанын көрсетеді. Түйіндерде нұсқа таңбасы болудың жалғыз мақсаты – әр түйінде әр нұсқа үшін әрбір өріс аты бойынша тек бір ғана мән бар екеніне көз жеткізу. Құрылымды шарлау үшін түйіндегі әр бастапқы өріс мәні нөлдік нұсқа таңбасына ие болады.

Май түйінінің күрделілігі

Майлық түйін әдісін қолдану арқылы әрбір өзгерту үшін O(1) орын қажет: жаңа деректерді ғана сақтау керек. Әрбір өзгертуді өзгерту тарихының соңына сақтауға O(1) қосымша уақыт жұмсалады. Бұл амортизацияланған уақыт шегі, егер өзгерту тарихы кеңейтілген массивте сақталса. Деректерге қол жеткізу кезінде құрылымды аралау барысында әр түйіндегі дұрыс нұсқа анықталуы тиіс. Егер "m" өзгерту жасалса, онда әрбір қол жеткізу операциясы массивтегі ең жақын өзгертуді табуға кеткен уақыттан O(log m) баяулауға ұшырайды.

Жолды көшіру

Жолды көшіру әдісімен кез келген өзгертілетін түйінге дейінгі жолдағы барлық түйіндердің көшірмесі жасалады. Бұл өзгерістер деректер құрылымы бойынша қайта таратылуы керек: ескі түйінге сілтеме берген барлық түйіндер жаңа түйінге сілтеме беру үшін өзгертілуі тиіс. Бұл өзгерістер тамыр түйініне жеткенше тізбектей өзгерістерді тудырады.

Жолды көшірудің күрделілігі

m модификациясы бар болғанда, бұл O(log m) қосымша іздеу уақытын қамтиды. Модификациялау уақыты мен көлемі деректер құрылымындағы ең ұзын жолдың мөлшерімен және уақытша деректер құрылымындағы жаңарту құнымен шектеледі. Аталық көрсеткіштері жоқ тепе-теңдік екілік іздеу ағашындағы ең нашар жағдайда модификациялау уақыттық күрделілігі O(log n + жаңарту құны) болып табылады. Дегенмен, тізімде ең нашар жағдайда модификациялау уақыттық күрделілігі O(n + жаңарту құны) болып табылады.

Бірлескен

Дрисколл, Сарнак, Слейтор, Таржан және трептерді тұрақты нұсқасын жасауға оңай бейімдеуге болады. Кейбір басқаларына аздап күш салу қажет, мысалы: кезектер, қос кезектер және кеңейтімдер, соның ішінде min deques (оларда минималды элементті қайтаратын қосымша O(1) операциясы бар) және кездейсоқ кіру декелері (оларда сублинейлік, көбінесе логарифмдік күрделілікпен кездейсоқ кіру операциясы бар). Деструктивті операцияларды пайдаланатын тұрақты дерек құрылымдары да бар, оларды таза функционалдық тілдерде (мысалы, Haskell, арнайы монадтардан – state немесе IO – тыс) тиімді жүзеге асыру мүмкін емес, бірақ C немесе Java сияқты тілдерде жүзеге асыруға болады. Мұндай дерек құрылымдары көбінесе басқаша жобалау арқылы болдырмауға болады. Таза тұрақты дерек құрылымдарын пайдаланудың басты артықшылығы – олар көп жіпті ортада көбінесе жақсы жұмыс істейді.

Хаскелл

Haskell – таза функционалдық тіл, демек, ол өзгертуге (мутацияға) жол бермейді. Сондықтан, тілдегі барлық дерек құрылымдары өзгермейтін (persistent) болып табылады, себебі функционалдық семантикаға сәйкес дерек құрылымының бұрынғы күйін сақтамау мүмкін емес. Бұндай өзгерістер дерек құрылымының бұрынғы нұсқаларын жарамсыз ететін болса, бұл анықтық қағидасын (referential transparency) бұзу болады. Haskell-дің стандартты кітапханасында байланыстырылған тізімдер, карталар (өлшемдік тепе-теңдік ағаштары түрінде іске асырылған) және жинақтар сияқты дерек құрылымдары үшін тиімді өзгермейтін (persistent) нұсқалары бар.

Клозур

Лисп отбасындағы көптеген бағдарламалау тілдері сияқты, Clojure-де де байланысты тізімнің іске асырылуы бар. Бірақ, басқа диалекттерден өзгешелігі, Clojure-дегі байланысты тізім конвенция бойынша тұрақты болатын емес, тұрақтылық міндетті түрде сақталады. Clojure тұрақты хэш-массив карталанған тәсілдерге негізделген тұрақты векторлар, карталар және жиынтықтардың тиімді іске асырылуын да қамтиды. Бұл деректер құрылымдары Java жинақтары шеңберінің тек оқуға арналған міндетті бөліктерін іске асырады. Clojure тілін жасаушылар өзгертілетін деректер құрылымдарына қарағанда тұрақты деректер құрылымдарын пайдалануды ұсынады, себебі олардың құндылық семантикасы бар. Бұл оларды жеңіл псевдонимдермен, оңай құрастырылатын және тілден тәуелсіз түрде жіберуге мүмкіндік береді. Осы деректер құрылымдары Clojure-дің параллель есептеулерді қолдау негізін құрайды, өйткені олар деректердің қақтығысын және атомдық салыстыру мен алмастыру семантикасын болдырмау үшін операцияларды оңай қайтадан орындауға мүмкіндік береді.

Ерекшелік

Elm бағдарламалау тілі Haskell сияқты толығымен функционалды, бұл оның барлық дерек құрылымдарын міндетті түрде тұрақты етеді. Ол байланысқан тізімдердің, сондай-ақ тұрақты массивтердің, сөздіктердің және жиынтықтардың тұрақты түрде іске асырылуын қамтиды. Elm деректерінің тұрақтылығын пайдаланатын арнайы виртуалды DOM іске асыруын қолданады. 2016 жылы Elm тілінің жасаушылары бұл виртуалды DOM-ның Elm тіліне React, Ember және Angular сияқты танымал JavaScript фреймворктарынан гөрі HTML-ді жылдам көрсетуге мүмкіндік беретінін мәлімдеді.

Жава

Java бағдарламалау тілі аса функционалды емес. Осыған қарамастан, негізгі JDK пакеті java.util.concurrent CopyOnWriteArrayList және CopyOnWriteArraySet құрылымдарын қамтиды, бұл өзгертулерді көшіру арқылы іске асырылған тұрақты құрылымдар. Дегенмен, Java-дағы әдеттегі бір мезгілде жұмыс істейтін ConcurrentHashMap картасы тұрақты емес. Толыққанды тұрақты жинақтар үшінші тараптың кітапханаларында немесе басқа JVM тілдерінде қолжетімді.

JavaScript-ті қолдану

Популярлы JavaScript фронт-энд фреймворкі React жиі Flux архитектурасын іске асыратын күйді басқару жүйесімен бірге қолданылады, оның танымал іске асырылуы – JavaScript кітапханасы Redux. Redux кітапханасы Elm бағдарламалау тілінде қолданылатын күйді басқару үлгісінен шабыттанды, яғни ол пайдаланушылардың барлық деректерді өзгермейтін деп қарастыруын талап етеді. Осының салдарынан Redux жобасы кейбір жағдайларда пайдаланушыларға күштеп сақталатын және тиімді өзгермейтін деректер құрылымдары үшін кітапханаларды пайдалануды ұсынады. Бұл әдеттегі JavaScript объектілерін салыстыру немесе көшірме жасау кезіндегіден жоғары өнімділікке мүмкіндік береді. Мұндай кітапханалардың бірі – Immutable.js, ол Clojure және Scala тілдерінде қолданылатын және танымал болған деректер құрылымдарына негізделген. Redux құжаттамасында бұл күштеп өзгермейтін болуын қамтамасыз ететін мүмкін кітапханалардың бірі ретінде көрсетілген. Immer.js ерекше тәсіл ұсынады, онда "келесі өзгермейтін күйді қазіргі күйді өзгерту арқылы жасауға болады". Immer.js тиімді емес өзгермейтін деректер құрылымдарын емес, жергілікті JavaScript объектілерін пайдаланады және деректердің көлемі үлкен болған жағдайда өнімділік мәселелерін тудыруы мүмкін.

Алдын ала сөз

Прологтық терминдер өзінен табиғи түрде өзгермейді, сондықтан дерек құрылымдары әдетте тұрақты дерек құрылымдары болып табылады. Олардың өнімділігі Prolog жүйесі ұсынатын ортақ пайдалану және қоқыс жинауға байланысты. Prolog терминдерінің толыққанды емеуіне кеңейтімдер жасау әрқашан мүмкін болмайды, себебі іздеу кеңістігінің өте үлкен болуы мүмкін. Кешіктірілген мақсаттар бұл мәселені азайтуға көмектеседі. Дегенмен, кейбір Prolog жүйелері setarg/3 сияқты деструктивті операцияларды ұсынады, олар көшірумен/көшірусіз және күй өзгерісінің кері қайтарылуымен/кері қайтарылмауымен әртүрлі нұсқаларда келуі мүмкін. Кейбір жағдайларда setarg/3 шектеуді шешуші сияқты жаңа декларативтік қабатты қамтамасыз ету үшін пайдалы болады.

Скала

Scala бағдарламалау тілі "Объектілі-функционалдық стильді" қолдана отырып бағдарламаларды жасау үшін өзгермейтін деректер құрылымдарын пайдалануға ынталандырады. Scala-да байланыстырылған тізімдер, қызыл-қара ағаштар, сондай-ақ Clojure тілінде ұсынылған өзгермейтін хэш-массив картасы түріндегі деректер құрылымдарының көптеген іске асырылымдары бар.

Қоқыс жинау

Тұрақты деректер құрылымдары көбінесе деректер құрылымының кезекті нұсқалары негізгі жадты бөлісетіндей етіп іске асырылады. Сондықтан, мұндай деректер құрылымдарын тиімді пайдалану үшін, әдетте анықтама санау немесе белгілеу және тазалау сияқты автоматты қоқыс жинау жүйесі қажет болады. Дегенмен, тұрақты деректер құрылымдары қолданылатын кейбір платформаларда қоқыс жинауды қолданбауға болады. Бұл жадтың ағуына алып келуі мүмкін, бірақ кейбір жағдайларда қолданбаның жалпы өнімділігіне жағымды әсер ете алады.