Ця стаття потребує для відповідності Вікіпедії. |
В теорії мов програмування, ліниві обчислення, або виклик за потребою — це стратегія обчислення при якій обчислення виразу не виконується до того моменту, поки значення виразу не стане потрібним та уникаються повторні обчислення.
Перевагами лінивих обчислень є:
- можливість визначити потік керування як абстракції замість примітивів;
- можливість визначати потенційно нескінченні структури даних. Це дозволяє реалізовувати деякі алгоритми більш прямолінійно;
- покращення продуктивності за рахунок уникання непотрібних обчислень і невиконуваних гілок в умовних виразах.
Ліниві обчислення зазвичай комбінуються з запам'ятовуванням. Після обчислення значення функції для конкретного параметру або набору параметрів результат зберігається в таблиці, індексами якої є параметри. Коли функція буде викликана знову, буде зроблена перевірка, чи міститься вже обчислене значення функції з такими параметрами в таблиці. Якщо так, то збережений результат повертається. Якщо ні, то значення функції обчислюється і це значення додається в таблицю для повторного використання.
Ліниві обчислення можуть привести до зменшення використання пам'яті, тому що значення створюються лише якщо вони потрібні. Проте ліниві обчислення важко об'єднувати з імперативним програмуванням, наприклад у випадку введення/виведення чи обробки виняткових ситуацій, тому що порядок операцій стає невизначеним. Також ліниві обчислення можуть привести до витоків пам'яті.
Протилежними до лінивих обчислень є строгі обчислення, які застосовуються в більшості мов програмування.
Історія
Ліниві обчислення були розроблені для лямбда-числення Кристофером Водсвортом і для мов програмування Пітером Хендерсоном та Джеймсом Моррісом і Денієлом Фрідманом та Девідом Вайзом.
Застосування
Відкладені обчислення в основному використовуються в функціональних мовах програмування. При використанні відкладених обчислень вираз не обчислюється одразу з присвоєнням змінній результату. Тобто вираз x:=expression;
(присвоєння результату виразу до певної змінної) за задумом обчислюється і записується до змінної, але реально це значення не буде потрібне (і тому не обчислюється) до того моменту, поки ця змінна не з'явиться в подальших виразах, обчислення яких не можуть бути відкладені.
Відкладене обчислення має перевагу у обчисленні дуже великих або нескінченних списків без використання нескінченних циклів. Наприклад, можна створити функцію, що створює нескінченний список (який зазвичай називається потік) чисел Фібоначчі. Обчислення певного числа Фібоначчі буде просто зверненням до відповідного елементу списку.
Наприклад, на мові Haskell список, що містить всі числа Фібоначчі може бути створений так:
fibs = 0 : 1 : zipWith (+) fibs (tail fibs)
За умови, що програміст обережний, обчислюються тільки ті значення, що потрібні для кінцевого виразу. Проте певні обчислення можуть призвести до того, що програма намагатиметься обчислити нескінченне число елементів; наприклад, спроба отримати довжину списку або обчислити суму елементів призведе до того, що програма завершить роботу через нестачу пам'яті.
Умовні конструкції
У більшості розповсюджених мов програмування, вирази if обчислюються лінивим способом[].
У такому записі:
if a then b else c
обчислюється вираз (а); тоді і тільки тоді, коли (а) є істиною, обчислюється вираз (b); в іншому випадку обчислюється вираз (с). Тобто один з виразів ((b) або (с)) не буде обчислений.
Окрім того, якщо в умові (а) фігурує кон'юнкція або диз'юнкція, то вираз (а) за можливістю обчислюється за короткою схемою.
Робота з нескінченними структурами даних
Багато мов[] надають можливість створювати . Це дозволяє давати визначення даним на нескінченних проміжках або за допомогою рекурсії. Приклад такої програми на Haskell:
numberFromInfiniteList :: Int -> Int numberFromInfiniteList n = infinity !! n - 1 where infinity = [1..] main = print $ numberFromInfiniteList 4
У функції numberFromInfiniteList
, значення infinity є нескінченним проміжком, але поки дійсне значення (або більш конкретно — значення з конкретним індексом) не стане потрібним, список не обчислюється, а при обчисленні обчислюється тільки потрібна частина (тобто до вказаного індексу).
Інші використання
В операційних системах з віконним інтерфейсом виведення інформації на екран керується подіями виведення. Внаслідок цього операційна система уникає непотрібних обчислень для оновлення екрану. Іншим прикладом лінивості в сучасних комп'ютерних системах є копіювання при записі, при якому пам'ять виділяється лише коли збережене значення змінюється.
Лінивість може бути корисною для високої продуктивності. Прикладом є функція mmap в Unix, яка робить кероване потребою завантаження файлу в пам'ять, тобто тільки ті частини, які реально використовуються, завантажуються, і пам'ять на непотрібні блоки не виділяється.
MATLAB реалізує копіювання при модифікації, за яким масиви, які компілюються і змінюють своє розташування у пам'яті лише коли їх зміст змінився. Але такий підхід може призвести до помилки out of memory якщо змінити елемент після копіювання замість зміни під час копіювання.
Реалізація
Деякі мови програмування відкладають обчислення виразів за умовчанням, інші надають спеціальні функції або синтаксис для їх відкладення. Наприклад обчислення аргументів функції за умовчанням відкладається в мовах Miranda і Haskell. В багатьох інших мовах обчислення можуть бути затримані в явному вигляді з використанням спеціального синтаксису (в Scheme «delay
» і «force
», в OCaml «lazy
» і «Lazy.force
») . Об'єкт, який представляє такі відкладені обчислення називається ліниве майбутнє. Perl 6 використовує ліниве обчислення списків, тож можна створювати нескінченні списки та передавати їх до функцій, але на відміну від Haskell та Miranda, Perl 6 за умовчанням не використовує ліниві обчислення для арифметичних виразів та функцій.
Лінивість та ретельність
Контролювання ретельності в лінивих мовах
У мовах з лінивим програмуванням, таких як Haskell, за умовчанням вирази обчислюються лише коли їх значення стане потрібне; в деяких випадках можливо зробити код більш ретельним — або навпаки, зробити обчислення більш лінивими після того як вони були зроблені ретельними. Це можна зробити не використовуючи явні команд які форсують обчислення (що робить код більш ретельним) або уникаючи таких команд (що робить код більш лінивим). Під строгими обчисленнями зазвичай розуміють ретельність, але технічно це різні концепції.
Перевагами лінивих обчислень є:
- Можливість визначити потік керування як абстракції замість примітивів.
- Можливість визначати потенційно нескінченні структури даних. Це дозволяє реалізовувати деякі алгоритми більш прямолінійно.
- Покращення продуктивності за рахунок уникання непотрібних обчислень і невиконуваних гілок в умовних виразах.
Однак, в деяких компіляторах реалізована оптимізація, яка називається аналіз строгості, яка, в деяких випадках, дозволяє компілятору визначити чи буде змінна завжди використовуватись. В таких випадках вибір програміста використовувати ліниві обчислення чи ні стає непотрібним, тому що аналіз строгості буде форсувати строгі обчислення.
Симуляція лінивості в ретельних мовах
- Python
В Python 2.x функція range()
створює список цілих чисел. Увесь список зберігається у пам'яті після того, як присвоєння буде виконане. Ось приклад ретельного, або негайного обчислення:
>>> r = range(10) >>> print r [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] >>> print r[3] 3
У Python 3.x функція range()
повертає спеціальний об'єкт, який обчислює елементи списку на вимогу. Елементи цього об'єкту генеруються тільки тоді коли вони стають потрібні (тобто коли в прикладі обчислюється print(r[3])
), тож це буде прикладом лінивих, або відкладених обчислень:
>>> r = range(10) >>> print(r) range(0, 10) >>> print(r[3]) 3
Ця зміна до лінивих обчислень зберігає час виконання для великих проміжків які ніколи не будуть повністю використовуватися. У Python 2.x є функція xrange()
яка повертає об'єкт, що створює числа у заданому проміжку на вимогу. Перевагою функції xrange
є те, що такий об'єкт буде займати одну й ту саму кількість пам'яті.
Застосування
>>> r = xrange(10) >>> print(r) xrange(10) >>> lst = [x for x in r] >>> print(lst) [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
З версії 2.2 і далі, Python розширив ліниві обчислення реалізацією ітераторів (ліниві послідовності) на відміну від кортежів чи списків. Наприклад (Python 2):
>>> numbers = range(10) >>> iterator = iter(numbers) >>> print numbers [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] >>> print iterator <listiterator object at 0xf7e8dd4c> >>> print iterator.next() 0
Цей приклад показує як списки обчислюються при виклику, але у випадку з ітератором, перший елемент '0' друкується коли така потреба виникає.
.NET Framework
У фреймворку .NET ліниві обчислення можливі з використанням класу System.Lazy<T>
. У F# є спеціалізовані колекції, такі як Microsoft.FSharp.Collections.Seq
у яких є вбудована підтримка для лінивих обчислень.
let fibonacci = Seq.unfold (fun (x, y) -> Some(x, (y, x + y))) (0I,1I) fibonacci |> Seq.nth 1000
У C# і VB.NET, використовується клас System.Lazy<T>
.
public int Sum() { int a = 0; int b = 0; Lazy<int> x = new Lazy<int>(() => a + b); a = 3; b = 5; return x.Value; // returns 8 }
За умови, що програміст обережний, тільки обчислюються тільки значення, що потрібні для кінцевого виразу. Проте певні обчислення можуть привести до того, що програма намагатиметься обчислити нескінченне число елементів; наприклад, спроба отримання довжини списку або обчислити суму елементів призведе до того, що програма завершить роботу через недостачу пам'яті. Більш практичний приклад:
// recursive calculation of the n'th fibonacci number public int Fib(int n) { return (n == 1)? 1 : (n == 2)? 1 : Fib(n-1) + Fib(n-2); } public void Main() { Console.WriteLine("Which Fibonacci number do you want to calculate?"); int n = Int32.Parse(Console.ReadLine()); Lazy<int> fib = new Lazy<int>(() => Fib(n)); // function is prepared, but not executed bool execute; if(n > 100) { Console.WriteLine("This can take some time. Do you really want to calculate this large number? [y/n]"); execute = (Console.ReadLine() == "y"); } else execute = true; if(execute) Console.WriteLine(fib.Value); // number is only calculated if needed }
Інший спосіб — використання ключового слова yield
:
Умовні конструкції
// eager evaluation public IEnumerable<int> Fibonacci(int x) { IList<int> fibs = new List<int>(); int prev = -1; int next = 1; for (int i = 0; i < x; i++) { int sum = prev + next; prev = next; next = sum; fibs.Add(sum); } return fibs; } // lazy evaluation public IEnumerable<int> LazyFibonacci(int x) { int prev = -1; int next = 1; for (int i = 0; i < x; i++) { int sum = prev + next; prev = next; next = sum; yield return sum; } }
обчислюється вираз (а), тоді і тільки тоді коли (а) є істиною обчислюється вираз (b), в іншому випадку обчислюється вираз (с). Тобто один з виразів ((b) або (с)) не буде обчислений.
Примітки
Посилання
Ця стаття не містить . (13 березня 2021) |
Вікіпедія, Українська, Україна, книга, книги, бібліотека, стаття, читати, завантажити, безкоштовно, безкоштовно завантажити, mp3, відео, mp4, 3gp, jpg, jpeg, gif, png, малюнок, музика, пісня, фільм, книга, гра, ігри, мобільний, телефон, android, ios, apple, мобільний телефон, samsung, iphone, xiomi, xiaomi, redmi, honor, oppo, nokia, sonya, mi, ПК, web, Інтернет
Cya stattya potrebuye uporyadkuvannya dlya vidpovidnosti standartam yakosti Vikipediyi Bud laska dopomozhit polipshiti cyu stattyu Mozhlivo mistit zauvazhennya shodo potribnih zmin V teoriyi mov programuvannya linivi obchislennya abo viklik za potreboyu ce strategiya obchislennya pri yakij obchislennya virazu ne vikonuyetsya do togo momentu poki znachennya virazu ne stane potribnim ta unikayutsya povtorni obchislennya Perevagami linivih obchislen ye mozhlivist viznachiti potik keruvannya yak abstrakciyi zamist primitiviv mozhlivist viznachati potencijno neskinchenni strukturi danih Ce dozvolyaye realizovuvati deyaki algoritmi bilsh pryamolinijno pokrashennya produktivnosti za rahunok unikannya nepotribnih obchislen i nevikonuvanih gilok v umovnih virazah Linivi obchislennya zazvichaj kombinuyutsya z zapam yatovuvannyam Pislya obchislennya znachennya funkciyi dlya konkretnogo parametru abo naboru parametriv rezultat zberigayetsya v tablici indeksami yakoyi ye parametri Koli funkciya bude viklikana znovu bude zroblena perevirka chi mistitsya vzhe obchislene znachennya funkciyi z takimi parametrami v tablici Yaksho tak to zberezhenij rezultat povertayetsya Yaksho ni to znachennya funkciyi obchislyuyetsya i ce znachennya dodayetsya v tablicyu dlya povtornogo vikoristannya Linivi obchislennya mozhut privesti do zmenshennya vikoristannya pam yati tomu sho znachennya stvoryuyutsya lishe yaksho voni potribni Prote linivi obchislennya vazhko ob yednuvati z imperativnim programuvannyam napriklad u vipadku vvedennya vivedennya chi obrobki vinyatkovih situacij tomu sho poryadok operacij staye neviznachenim Takozh linivi obchislennya mozhut privesti do vitokiv pam yati Protilezhnimi do linivih obchislen ye strogi obchislennya yaki zastosovuyutsya v bilshosti mov programuvannya IstoriyaLinivi obchislennya buli rozrobleni dlya lyambda chislennya Kristoferom Vodsvortom i dlya mov programuvannya Piterom Hendersonom ta Dzhejmsom Morrisom i Deniyelom Fridmanom ta Devidom Vajzom ZastosuvannyaVidkladeni obchislennya v osnovnomu vikoristovuyutsya v funkcionalnih movah programuvannya Pri vikoristanni vidkladenih obchislen viraz ne obchislyuyetsya odrazu z prisvoyennyam zminnij rezultatu Tobto viraz x expression prisvoyennya rezultatu virazu do pevnoyi zminnoyi za zadumom obchislyuyetsya i zapisuyetsya do zminnoyi ale realno ce znachennya ne bude potribne i tomu ne obchislyuyetsya do togo momentu poki cya zminna ne z yavitsya v podalshih virazah obchislennya yakih ne mozhut buti vidkladeni Vidkladene obchislennya maye perevagu u obchislenni duzhe velikih abo neskinchennih spiskiv bez vikoristannya neskinchennih cikliv Napriklad mozhna stvoriti funkciyu sho stvoryuye neskinchennij spisok yakij zazvichaj nazivayetsya potik chisel Fibonachchi Obchislennya pevnogo chisla Fibonachchi bude prosto zvernennyam do vidpovidnogo elementu spisku Napriklad na movi Haskell spisok sho mistit vsi chisla Fibonachchi mozhe buti stvorenij tak fibs 0 1 zipWith fibs tail fibs Za umovi sho programist oberezhnij obchislyuyutsya tilki ti znachennya sho potribni dlya kincevogo virazu Prote pevni obchislennya mozhut prizvesti do togo sho programa namagatimetsya obchisliti neskinchenne chislo elementiv napriklad sproba otrimati dovzhinu spisku abo obchisliti sumu elementiv prizvede do togo sho programa zavershit robotu cherez nestachu pam yati Umovni konstrukciyi U bilshosti rozpovsyudzhenih mov programuvannya virazi if obchislyuyutsya linivim sposobom dzherelo U takomu zapisi if a then b else c obchislyuyetsya viraz a todi i tilki todi koli a ye istinoyu obchislyuyetsya viraz b v inshomu vipadku obchislyuyetsya viraz s Tobto odin z viraziv b abo s ne bude obchislenij Okrim togo yaksho v umovi a figuruye kon yunkciya abo diz yunkciya to viraz a za mozhlivistyu obchislyuyetsya za korotkoyu shemoyu Robota z neskinchennimi strukturami danih Bagato mov yaki nadayut mozhlivist stvoryuvati Ce dozvolyaye davati viznachennya danim na neskinchennih promizhkah abo za dopomogoyu rekursiyi Priklad takoyi programi na Haskell numberFromInfiniteList Int gt Int numberFromInfiniteList n infinity n 1 where infinity 1 main print numberFromInfiniteList 4 U funkciyi numberFromInfiniteList znachennya infinity ye neskinchennim promizhkom ale poki dijsne znachennya abo bilsh konkretno znachennya z konkretnim indeksom ne stane potribnim spisok ne obchislyuyetsya a pri obchislenni obchislyuyetsya tilki potribna chastina tobto do vkazanogo indeksu Inshi vikoristannya V operacijnih sistemah z vikonnim interfejsom vivedennya informaciyi na ekran keruyetsya podiyami vivedennya Vnaslidok cogo operacijna sistema unikaye nepotribnih obchislen dlya onovlennya ekranu Inshim prikladom linivosti v suchasnih komp yuternih sistemah ye kopiyuvannya pri zapisi pri yakomu pam yat vidilyayetsya lishe koli zberezhene znachennya zminyuyetsya Linivist mozhe buti korisnoyu dlya visokoyi produktivnosti Prikladom ye funkciya mmap v Unix yaka robit kerovane potreboyu zavantazhennya fajlu v pam yat tobto tilki ti chastini yaki realno vikoristovuyutsya zavantazhuyutsya i pam yat na nepotribni bloki ne vidilyayetsya MATLAB realizuye kopiyuvannya pri modifikaciyi za yakim masivi yaki kompilyuyutsya i zminyuyut svoye roztashuvannya u pam yati lishe koli yih zmist zminivsya Ale takij pidhid mozhe prizvesti do pomilki out of memory yaksho zminiti element pislya kopiyuvannya zamist zmini pid chas kopiyuvannya RealizaciyaDeyaki movi programuvannya vidkladayut obchislennya viraziv za umovchannyam inshi nadayut specialni funkciyi abo sintaksis dlya yih vidkladennya Napriklad obchislennya argumentiv funkciyi za umovchannyam vidkladayetsya v movah Miranda i Haskell V bagatoh inshih movah obchislennya mozhut buti zatrimani v yavnomu viglyadi z vikoristannyam specialnogo sintaksisu v Scheme delay i force v OCaml lazy i Lazy force Ob yekt yakij predstavlyaye taki vidkladeni obchislennya nazivayetsya linive majbutnye Perl 6 vikoristovuye linive obchislennya spiskiv tozh mozhna stvoryuvati neskinchenni spiski ta peredavati yih do funkcij ale na vidminu vid Haskell ta Miranda Perl 6 za umovchannyam ne vikoristovuye linivi obchislennya dlya arifmetichnih viraziv ta funkcij Linivist ta retelnistKontrolyuvannya retelnosti v linivih movah U movah z linivim programuvannyam takih yak Haskell za umovchannyam virazi obchislyuyutsya lishe koli yih znachennya stane potribne v deyakih vipadkah mozhlivo zrobiti kod bilsh retelnim abo navpaki zrobiti obchislennya bilsh linivimi pislya togo yak voni buli zrobleni retelnimi Ce mozhna zrobiti ne vikoristovuyuchi yavni komand yaki forsuyut obchislennya sho robit kod bilsh retelnim abo unikayuchi takih komand sho robit kod bilsh linivim Pid strogimi obchislennyami zazvichaj rozumiyut retelnist ale tehnichno ce rizni koncepciyi Perevagami linivih obchislen ye Mozhlivist viznachiti potik keruvannya yak abstrakciyi zamist primitiviv Mozhlivist viznachati potencijno neskinchenni strukturi danih Ce dozvolyaye realizovuvati deyaki algoritmi bilsh pryamolinijno Pokrashennya produktivnosti za rahunok unikannya nepotribnih obchislen i nevikonuvanih gilok v umovnih virazah Odnak v deyakih kompilyatorah realizovana optimizaciya yaka nazivayetsya analiz strogosti yaka v deyakih vipadkah dozvolyaye kompilyatoru viznachiti chi bude zminna zavzhdi vikoristovuvatis V takih vipadkah vibir programista vikoristovuvati linivi obchislennya chi ni staye nepotribnim tomu sho analiz strogosti bude forsuvati strogi obchislennya Simulyaciya linivosti v retelnih movah Python V Python 2 x funkciya range stvoryuye spisok cilih chisel Uves spisok zberigayetsya u pam yati pislya togo yak prisvoyennya bude vikonane Os priklad retelnogo abo negajnogo obchislennya gt gt gt r range 10 gt gt gt print r 0 1 2 3 4 5 6 7 8 9 gt gt gt print r 3 3 U Python 3 x funkciya range povertaye specialnij ob yekt yakij obchislyuye elementi spisku na vimogu Elementi cogo ob yektu generuyutsya tilki todi koli voni stayut potribni tobto koli v prikladi obchislyuyetsya print r 3 tozh ce bude prikladom linivih abo vidkladenih obchislen gt gt gt r range 10 gt gt gt print r range 0 10 gt gt gt print r 3 3 Cya zmina do linivih obchislen zberigaye chas vikonannya dlya velikih promizhkiv yaki nikoli ne budut povnistyu vikoristovuvatisya U Python 2 x ye funkciya xrange yaka povertaye ob yekt sho stvoryuye chisla u zadanomu promizhku na vimogu Perevagoyu funkciyi xrange ye te sho takij ob yekt bude zajmati odnu j tu samu kilkist pam yati Zastosuvannya gt gt gt r xrange 10 gt gt gt print r xrange 10 gt gt gt lst x for x in r gt gt gt print lst 0 1 2 3 4 5 6 7 8 9 Z versiyi 2 2 i dali Python rozshiriv linivi obchislennya realizaciyeyu iteratoriv linivi poslidovnosti na vidminu vid kortezhiv chi spiskiv Napriklad Python 2 gt gt gt numbers range 10 gt gt gt iterator iter numbers gt gt gt print numbers 0 1 2 3 4 5 6 7 8 9 gt gt gt print iterator lt listiterator object at 0xf7e8dd4c gt gt gt gt print iterator next 0 Cej priklad pokazuye yak spiski obchislyuyutsya pri vikliku ale u vipadku z iteratorom pershij element 0 drukuyetsya koli taka potreba vinikaye NET Framework U frejmvorku NET linivi obchislennya mozhlivi z vikoristannyam klasu span class n System span span class p span span class n Lazy span span class o lt span span class n T span span class o gt span U F ye specializovani kolekciyi taki yak span class n Microsoft span span class p span span class n FSharp span span class p span span class n Collections span span class p span span class n Seq span u yakih ye vbudovana pidtrimka dlya linivih obchislen let fibonacci Seq unfold fun x y gt Some x y x y 0I 1I fibonacci gt Seq nth 1000 U C i VB NET vikoristovuyetsya klas span class n System span span class p span span class n Lazy span span class o lt span span class n T span span class o gt span public int Sum int a 0 int b 0 Lazy lt int gt x new Lazy lt int gt gt a b a 3 b 5 return x Value returns 8 Za umovi sho programist oberezhnij tilki obchislyuyutsya tilki znachennya sho potribni dlya kincevogo virazu Prote pevni obchislennya mozhut privesti do togo sho programa namagatimetsya obchisliti neskinchenne chislo elementiv napriklad sproba otrimannya dovzhini spisku abo obchisliti sumu elementiv prizvede do togo sho programa zavershit robotu cherez nedostachu pam yati Bilsh praktichnij priklad recursive calculation of the n th fibonacci number public int Fib int n return n 1 1 n 2 1 Fib n 1 Fib n 2 public void Main Console WriteLine Which Fibonacci number do you want to calculate int n Int32 Parse Console ReadLine Lazy lt int gt fib new Lazy lt int gt gt Fib n function is prepared but not executed bool execute if n gt 100 Console WriteLine This can take some time Do you really want to calculate this large number y n execute Console ReadLine y else execute true if execute Console WriteLine fib Value number is only calculated if needed Inshij sposib vikoristannya klyuchovogo slova span class k yield span Umovni konstrukciyi eager evaluation public IEnumerable lt int gt Fibonacci int x IList lt int gt fibs new List lt int gt int prev 1 int next 1 for int i 0 i lt x i int sum prev next prev next next sum fibs Add sum return fibs lazy evaluation public IEnumerable lt int gt LazyFibonacci int x int prev 1 int next 1 for int i 0 i lt x i int sum prev next prev next next sum yield return sum obchislyuyetsya viraz a todi i tilki todi koli a ye istinoyu obchislyuyetsya viraz b v inshomu vipadku obchislyuyetsya viraz s Tobto odin z viraziv b abo s ne bude obchislenij PrimitkiPosilannyaCya stattya ne mistit posilan na dzherela Vi mozhete dopomogti polipshiti cyu stattyu dodavshi posilannya na nadijni avtoritetni dzherela Material bez dzherel mozhe buti piddano sumnivu ta vilucheno 13 bereznya 2021