Жадібний проти Небажаний проти Посибних Кванторів


357

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

Зокрема, у наступному прикладі:

Enter your regex: .*foo  // greedy quantifier
Enter input string to search: xfooxxxxxxfoo
I found the text "xfooxxxxxxfoo" starting at index 0 and ending at index 13.

Enter your regex: .*?foo  // reluctant quantifier
Enter input string to search: xfooxxxxxxfoo
I found the text "xfoo" starting at index 0 and ending at index 4.
I found the text "xxxxxxfoo" starting at index 4 and ending at index 13.

Enter your regex: .*+foo // possessive quantifier
Enter input string to search: xfooxxxxxxfoo
No match found.

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

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

Крім того, якщо хтось може пояснити дещо іншим фразуванням наступний параграф, це буде дуже вдячно:

Перший приклад використовує жадібний квантор. *, Щоб знайти "що завгодно", нульовий або більше разів, а потім літери "f" "o" "o". Оскільки кількісний показник жадібний,. * Частина виразу спочатку з'їдає весь вхідний рядок. На даний момент загальний вираз не може досягти успіху, оскільки три останні букви ("f" "o" "o") вже були вжиті ( ким? ). Тож матч повільно відступає ( справа-наліво? ) Одну букву за часом, поки не буде відрегульовано крайне праворуч виникнення "foo" ( що це означає? ), І в цей момент збіг проходить, і пошук закінчується.

Однак другий приклад - це неохоче, тому він починається спочатку споживаючи ( ким? ) "Нічого". Оскільки "foo" не з'являється на початку рядка, він змушений проковтнути ( хто проковтує?) Першу букву ("x"), яка запускає першу відповідність у 0 і 4. Наш тестовий джгут продовжує процес до введення рядка вводу. Він знаходить ще один матч у 4 та 13.

У третьому прикладі не вдається знайти збіг, оскільки кількісний показник є присвійним. У цьому випадку весь рядок вводу споживається. * +, ( Як? ), Не залишаючи нічого іншого, щоб задовольнити "foo" в кінці виразу. Використовуйте присвійний кількісний показник для ситуацій, коли ви хочете захопити все що-небудь, не відступаючи ніколи ( що означає відступ? ); він буде перевершувати еквівалентний жадібний кількісний показник у випадках, коли відповідність знайдена не відразу.


22
Максимальні Квантор подобаються *, +і ?є жадібними. Мінімально квантори подобається *?, +?і ??є ледачими. Присвійні Квантор подобаються *+, ++і ?+є липкими.
tchrist

6
Це запитання було додано до поширених запитань про регулярне вираження стека в розділі "Квантори> Більше про відмінності ...".
aliteralmind

Цікаві: Підручники Java ™ - Відмінності серед жадібних, неохочих та навантажувальних кванторів - Прокрутіть униз, щоб переглянути розділ.
Хлопець Кодер

Відповіді:


495

Я пострілю.

Жадібні кванторного перші матчі якомога більше. Отже, .*відповідає всій рядку. Потім матч намагається зіставити fнаступне, але символів не залишилося. Таким чином, він "повертається назад", роблячи жадібний кількісний коефіцієнт на один менший символ (залишаючи "o" в кінці рядка незрівнянним). Це все ще не відповідає fрегексу, тому він відновлює ще один крок, роблячи жадібний кількісний показник ще одним символом менше (залишаючи "oo" в кінці рядка незрівнянним). Це все ще не відповідає fрівню в регулярному виразі, тому він відновлює ще один крок (залишаючи "foo" в кінці рядка незрівнянним). Тепер матч, нарешті, відповідає fрегексу,ooтеж збігаються. Успіху!

Небажанням або «не жадібний» Квантор перші матчі якомога менше. Тож .*спочатку нічого не відповідає, а весь рядок залишається незрівнянним. Тоді матч намагається зіставити fнаступне, але незрівнянна частина рядка починається з "х", так що це не працює. Тож матч відступає, змушуючи не жадібний кількісний коефіцієнт відповідати ще одному символу (тепер він відповідає "x", залишаючи "fooxxxxxxfoo" незрівнянним). Потім він намагається співставити те f, що вдається, і те, oі наступне oв регулярному вираженні теж. Успіху!

У вашому прикладі він запускає процес із залишку незрівнянної частини рядка "xxxxxxfoo", слідуючи за тим же процесом.

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


15
+1 Хороша відповідь. Я хотів би лише додати: Перейдіть, читайте « Освоєння регулярних виразів» (3-е видання)
ridgerunner

@Anomie трохи запізнився, але, з поважною частиною, я думаю, ти мав на увазі Отже, це починається з .*+ (зауважте "+")
RD

3
що саме робить присвійний кількісний показник? якщо це не відповідає цьому? (Я маю на увазі, в чому сенс, якщо ви не можете мати символів після нього)
перегляньте

4
@relipse: Ви б використовували його в ситуації, коли знаєте, що зворотний трек не допоможе, ймовірно, не з .*+цим відповідає всім. Наприклад, якщо у вас є візерунок [xyz]*foo, немає жодного способу, щоб зворотне відстеження значень x, y і z, які порівнюються [xyz]*бітом, ніколи не дозволить збігнути наступний fooбіт, тож ви можете прискорити роботу, зробивши його приємним.
Аномія

4
@moodboom, ніколи не буває випадків (математичний факт), коли власні квантори визначають відповідність, яка не буде вироблятися простими жадібними кванторами. Бувають випадкові випадки, коли вони створюють невідповідність, коли жадібні квантори визначають відповідність. Для ВСІХ інших випадків (коли жадібні та прихильні дають однакові результати), прихильні квантори дають підвищення продуктивності.
Wildcard

49

Це лише моя вихідна практика - візуалізувати сцену -

Візуальне зображення


3
За винятком того, що я думаю, що останній випадок, присвійний, не повинен мати n пропусків - просто захопіть всю нитку відразу.
ставитесь до своїх модників добре

@phyzome Я думаю, зараз це нормально?
SIslam

1
Дякую за наочне пояснення :)
Ларс Моллекен

У EXPRESSION .*?foo(), чи не повинні [f] [o] [o]прямокутники бути жовтими в 5th pass?
тонікс

1
@tonix так! Жовте забарвлення потрібно виконати для відповідної частини в виразі .*?fooта .*+foo.
SIslam

24

Я раніше не чув точних термінів «відрижка» або «відступ»; фраза, яка замінить їх, є "зворотним відстеженням", але "відрижка" видається такою ж хорошою фразою, як і будь-яка для "вмісту, який був попередньо прийнятий до того, як зворотний трек відкинув його знову".

Важливе, що слід розуміти про більшість двигунів регексу, - це те, що вони зволікають назад : вони орієнтовно приймуть потенційну, часткову відповідність, намагаючись узгодити весь вміст регексу. Якщо з першої спроби регулярного вибору не вдасться повністю узгодити, двигун регулярного вирівнювання буде відслідковувати один із своїх збігів. Він буде намагатися узгодження *, +, ?, чергування або {n,m}повторення по- різному, і спробуйте ще раз. (І так, цей процес може зайняти багато часу.)

Перший приклад використовує жадібний квантор. *, Щоб знайти "що завгодно", нульовий або більше разів, а потім літери "f" "o" "o". Оскільки кількісний показник жадібний,. * Частина виразу спочатку з'їдає весь вхідний рядок. На даний момент загальний вираз не може досягти успіху, оскільки три останні букви ("f" "o" "o") вже були вжиті ( ким? ).

Останні три букви, f, oі oвже споживаються початкової .*частини правила. Однак для наступного елемента в регулярному виразі fу вхідному рядку нічого не залишилося. Двигун буде змушений відслідковувати свою первинну .*відповідність і намагатися відповідати символу, який не є останнім. (Це може бути розумним і зворотним до всіх, але останніх-трьох, тому що в ньому є три дослівні терміни, але я не знаю деталей щодо впровадження на цьому рівні.)

Тож матч повільно відступає ( справа наліво? ) По одній букві за часом, поки не буде відрегульовано найправіше виникнення "foo" ( що це означає? ), При якому

Це означає , fooщо в попередньому порядку були в тому числі при суміщенні .*. Оскільки ця спроба зазнала невдачі, движок регулярного випробовування намагається прийняти один менший символ .*. Якби був успішний матчем перед.* тим в цьому прикладі, то двигун буде , ймовірно , спробує скоротити .*матч (справа наліво, як ви вказали, тому що жодній Класифікатор), та якщо він не зміг матч цілі входи, то це могло б бути змушені повторно оцінити те , що вона відповідає , перш ніж.* в моєму гіпотетичному прикладі.

вказує, що збіг вдається, і пошук закінчується.

Однак другий приклад - це неохоче, тому він починається спочатку споживаючи ( ким? ) "Нічого". Тому що "foo"

Початкові нічого не споживаються .?*, що споживає найкоротшу можливу кількість всього, що дозволяє решті регексу збігатися.

не з’являється на початку струни, вона змушена ковтати ( хто ковтає?)

Знову .?*споживає першого символу, після зворотного відстеження на початковій невідповідності всього регексу з найкоротшим можливим збігом. (У цьому випадку двигун регулярного випромінювання розширює відповідність .*?зліва направо, тому що .*?неохоче.)

перша буква ("х"), яка запускає першу відповідність на 0 і 4. Наш тестовий джгут продовжує процес, поки вхідний рядок не вичерпається. Він знаходить ще один матч у 4 та 13.

У третьому прикладі не вдається знайти збіг, оскільки кількісний показник є присвійним. У цьому випадку весь рядок вводу витрачається на. * +, ( Як? )

A .*+споживає якомога більше, і не відхиляє пошук нових збігів, коли регулярний вираз в цілому не зможе знайти відповідність. Оскільки присвійна форма не виконує відкати, ви , ймовірно , не будете бачити багато застосувань з .*+, а класами символів або аналогічними обмеженнями: account: [[:digit:]]*+ phone: [[:digit:]]*+.

Це може різко прискорити збіг регулярних виразів, тому що ви говорите двигуну регулярного виразу, що він ніколи не повинен відхиляти потенційні збіги, якщо введення не відповідає. (Якби вам довелося вручну написати всі відповідні коди, це було б схоже на те, щоб ніколи не використовувати putc(3)«відштовхування» вхідного символу. Це було б дуже схоже на наївний код, який можна написати при першій спробі. За винятком того, що двигуни регулярного вирівнювання є набагато краще, ніж один символ push-back, вони можуть перемотати всю спинку до нуля і спробувати знову. :)

Але більше, ніж потенційні прискорення швидкості, це також може дозволяти вам писати регулярні вирази, які точно відповідають тому, що вам потрібно. У мене виникають проблеми з легким прикладом :), але написання регулярного вираження за допомогою присвійних проти жадібних кількісних показників може дати вам різні збіги, і те чи інше може бути більш підходящим.

не залишаючи нічого, щоб задовольнити "foo" в кінці виразу. Використовуйте присвійний кількісний показник для ситуацій, коли ви хочете захопити все що-небудь, не відступаючи ніколи ( що означає відступ? ); вона буде перевершувати

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

еквівалентний жадібний кількісний показник у випадках, коли відповідність виявляється не відразу.


2
Я підозрюю, що ніколи не трапляється випадок, коли присвійний квантор визначатиме те, що жадібний кількісний показник не буде. Я вважаю, що це підтверджує наступне: жадібний кількісний коефіцієнт завжди відповідає максимальній сумі, а потім повертається назад, якщо він не може знайти відповідність. Посильний кількісний показник відповідає максимальній сумі, а потім виходить, якщо він не може знайти відповідність. Таким чином, може статися щось, що жадібний кількісний коефіцієнт відповідає, який не має, але не зворотний, оскільки вони обидва шукають "дерево" в одній послідовності, присвійний квантор просто відмовляється легше. ;)
Wildcard

2
Підтверджено: "Саме для цього є атомне групування та присвійні квантори: ефективність, забороняючи зворотне відстеження". З регулярної-expressions.info Отже, твердження у цій відповіді "Але більше, ніж потенційні прискорення швидкості, це також може дати вам написати регекси, які відповідають саме тому, що вам потрібно відповідати". насправді не зовсім точний.
Wildcard

1
@Wildcard, дякую за коментарі; це може пояснити, чому у мене виникли проблеми придумати приклад. Хе-хе.
sarnold

19

http://swtch.com/~rsc/regexp/regexp1.html

Я не впевнений, що це найкраще пояснення в Інтернеті, але він досить добре написаний і докладно деталізований, і я продовжую повертатися до нього. Ви можете перевірити це.

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

Цей процес використовується рекурсивно: у своїй спробі зіставити рядок із заданим регулярним виразом, двигун розділить регулярний вираз на частини і застосує алгоритм до кожного фрагмента окремо.

Різниця між жадібними, неохоченими та нав'язливими кванторами виникає, коли двигун робить вибір, з якою частиною струни слід спробувати протиставити, і як змінити цей вибір, якщо він не працює з першого разу. Правила такі:

  • Жадібний кількісний показник спонукає двигун почати з усієї строки (або, принаймні, усієї, яка вже не була відповідна попереднім частинам регулярного виразу), і перевірити, чи відповідає вона регулярним виразом. Якщо так, чудово; двигун може продовжуватися з рештою регулярного вибору. Якщо ні, то він спробує ще раз, але обрізаючи один символ (останній) із розділу рядка, що перевіряється. Якщо це не працює, він обрізає інший символ тощо. Отже, жадібний кількісний коефіцієнт перевіряє можливі збіги, щоб від тривалої до найкоротшої.

  • Небажаний кількісний показник спонукає двигун почати з найкоротшого можливого шматка струни. Якщо вона збігається, двигун може продовжуватися; якщо ні, то він додає один символ до розділу рядка, що перевіряється, і намагається це, і так далі, поки він не знайде збіг або не буде використана вся рядок. Тож неохоче квантор перевіряє можливі відповідники для того, щоб від найкоротшого до найдовшого.

  • Потенційний квантор є подібним до жадібного квантора з першої спроби: він спонукає двигун запуститись, перевіривши всю нитку. Різниця полягає в тому, що якщо він не працює, присвійний кількісний коефіцієнт повідомляє, що збіг тут і там не вдався. Двигун не змінює ділянку струни, яку переглядають, і більше не робить спроб.

Ось чому у вашому прикладі не відповідає присвоєний коефіцієнт кількісного показника: .*+перевіряється цілий рядок, який відповідає йому, але потім двигун продовжує шукати додаткові символи fooпісля цього - але, звичайно, він їх не знаходить, тому що ви вже в кінці рядка. Якби це жадібний кількісний коефіцієнт, він би відкликав і намагався скласти .*єдину відповідність до наступного символу, потім до третього до останнього символу, потім до четвертого до останнього символу, що вдається, тому що лише тоді там fooзліва після того, .*як "з'їли" більш ранню частину рядка.


1
Це відмінне джерело. Я люблю діаграми стану машини. :)
Regex Rookie

@Regex Rookie: радий, що вам це подобається :) Проте, переглянувши цей сайт, я думаю, що я повинен зрозуміти, що його мета - сприяти альтернативній реалізації механізму регулярних виразів. Алгоритм зворотного відстеження I (частково) та інші відповіді описують повільний шлях; це абсолютно окремий алгоритм від ідеї NFA / DFA, описаної на веб-сторінці. Зворотний трек просто зрозуміти, тому саме так зазвичай пояснюються новачки.
David Z

@Давид Заславський: Гарне пояснення. Ваші коментарі в дужках в "Жадібний кількісний показник спонукає двигун почати з усієї рядка (або, принаймні, всього, що ще не було узгоджено з попередніми частинами регулярного виразу)". Вони також застосовуються до неохочих і прихильних кванторів. Це робить ваше пояснення сумісним з тим, що відбувається, коли ми змінюємо наші приклади з "(. * Foo"; ". *? Foo"; і ". * + Foo") на ("foo. *"; "Foo. *? "; і" foo. * + ").
Джон Бентлі

Насправді, xfooxxxxxxfoo відповідає. * Foo у звичайному (значення інформатики) регулярному вираженні. NFA буде aa станом, коли він замикається між собою будь-яким персонажем, і тоді він може перейти до foo. DFA був би прямим перекладом цієї NFA. Це можна зробити у 8 штатах.
user4951

@JimThio Так, тому що це не присвійний кількісний показник.
David Z

12

Ось мій погляд, використовуючи позиції Cell та Index (Дивіться діаграму тут, щоб відрізняти клітинку від індексу).

Жадібний - максимально співставляйте жадібний квантор і весь регулярний вираз. Якщо немає відповідності, поверніться до жадного кількісного показника.

Рядок вводу: xfooxxxxxxfoo
Regex:. * Foo

Вищеописаний Регекс складається з двох частин:
(i) '. *' Та
(ii) 'foo'

Кожен із наведених нижче кроків проаналізує дві частини. Додаткові коментарі до матчу на "Pass" або "Fail" пояснюються в дужках.

Крок 1:
(i). * = Xfooxxxxxxfoo - PASS ('. *' - жадібний кількісний коефіцієнт і буде використовувати весь вхідний рядок)
(ii) foo = Не залишилося жодного символу для відповідності після індексу 13 - FAIL
Match не вдалося.

Крок 2:
(i). * = Xfooxxxxxxfo - PASS (зворотне відстеження на жадібний квантор '. *')
(Ii) foo = o -
Збій не вдався.

Крок 3:
(i). * = Xfooxxxxxxf - PASS (зворотне відстеження на жадібний квантор '. *')
(Ii) foo = oo - FAIL
Match не вдалося.

Крок 4:
(i). * = Xfooxxxxxx - PASS (зворотне відстеження на жадібний квантор '. *')
(Ii) foo = foo - ПАРТНІЙ
ЗВІТ МАТЧ

Результат: 1 відповідність
Я знайшов текст "xfooxxxxxxfoo", починаючи з індексу 0 і закінчуючи індексом 13.

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

Рядок вводу: xfooxxxxxxfoo
Regex:. *? Foo

Наведений вище вираз має дві частини:
(i) '. *?' та
(ii) "foo"

Крок 1:.
*? = '' (порожнє) - PASS (
Максимально співставляйте з неохоченим кількісним показником '. *?'. Індекс 0, який має "'- це збіг.) foo = xfo - FAIL (клітинка 0,1,2 - тобто індекс між 0 і 3)
Матч не вдався.

Крок 2:.
*? = x - PASS (Додайте символи до неохоронного кількісного показника '. *?'. Осередок 0, що має "x" - це збіг.)
foo = foo - PASS
Report MATCH

Крок 3:.
*? = '' (порожній) - PASS (Максимально співставляйте з неохоченим кількісним показником '. *?'. Індекс 4, що має "'- це збіг.)
foo = xxx - FAIL (клітинка 4,5,6 - тобто індекс між 4 та 7)
Матч не вдався.

Крок 4:.
*? = x - PASS (Додавання символів до неохоронного кількісного показника '. *?'. Комірка 4.)
foo = xxx - FAIL (комірка 5,6,7 - тобто індекс між 5 і 8)
Збіг не вдався.

Крок 5:.
*? = xx - PASS (Додавання символів до неохоронного кількісного показника ". *?". Комірка 4 - через 5.)
foo = xxx - FAIL (комірка 6,7,8 - тобто індекс між 6 і 9)
.

Крок 6:.
*? = xxx - PASS (Додавання символів до неохоронного кількісного показника '. *?'. Комірка 4 до 6.)
foo = xxx - FAIL (комірка 7,8,9 - тобто індекс між 7 та 10)
Збіг не вдався.

Крок 7:.
*? = xxxx - PASS (Додавання символів до неохоронного кількісного показника '. *?'. Комірка 4 до 7.)
foo = xxf - FAIL (клітинка 8,9,10 - тобто індекс між 8 і 11)
.

Крок 8:.
*? = xxxxx - PASS (Додавання символів до неохоронного кількісного показника ". *?". Комірка 4 - через 8.)
foo = xfo - FAIL (комірка 9,10,11 - тобто індекс між 9 і 12)
.

Крок 9:.
*? = ХХХХХХ - PASS (Додати символи небажанням квантора Cell 4 через 9. '*.?.)
Foo = Foo - PASS (Cell 10,11,12 - тобто індекс між 10 і 13)
Доповідь MATCH

Крок 10:.
*? = '' (Пропуск) - PASS (Match якнайменше до небажанням квантора '*' Index 13 є порожнім.? ..)
Foo = символ не залишилося , щоб відповідати - FAIL (Там немає нічого після того, як індекс 13 , щоб відповідати)
Match не вдалося.

Результат: 2 збіги.
Я знайшов текст "xfoo", починаючи з індексу 0 і закінчуючи індексом 4.
Я знайшов текст "xxxxxxfoo", починаючи з індексу 4 і закінчуючи індексом 13.

Possessive - Максимально співставляйте присвійний квантер і співставляйте весь вираз. НЕ зволікайте.

Рядок вводу: xfooxxxxxxfoo
Regex:. * + Foo

Вищенаведений регулярний вираз має дві частини: '. * +' І 'foo'.

Крок 1:
. * + = Xfooxxxxxxfoo - PASS ( '*' Матч якомога більше , щоб присвійний квантор)
Foo = Немає символи залишилися не відповідати - FAIL (Nothing , щоб відповідати після індексу 13)
Матч був замалий.

Примітка. Зворотне відстеження заборонено.

Результат: 0 матчів


1

Жадібний: "відповідати найдовшій можливій послідовності символів"

Небажано: "відповідати найкоротшій можливій послідовності символів"

Позитивний: Це трохи дивно, оскільки НЕ (на відміну від жадібних та неохочих) намагаються знайти відповідність для всього виразного коду.

До речі: жодна реалізація схеми узгодження регексу ніколи не використовує зворотний трек. Всі відповідники з реального життя надзвичайно швидкі - майже не залежать від складності регулярного виразу!


Наскільки я знаю, більшість реалізацій загального користування зараз упаковані настільки багато функцій, що неможливо було не використовувати зворотний трекінг. Тож теоретично вони повинні бути надзвичайно (експоненціально) повільними для деяких випадків. Але для більшості випадків існують спеціальні оптимізації, вбудовані в узор відповідника.
Роберт

0

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

Коли активні послідовності задовольняють лише провідні умови схеми регулярного виразів, робиться спроба перевірити інші умови щодо карантину. Якщо ця перевірка буде успішною, відповідні символи в карантині перевіряються, а залишкові незрівняні символи залишаються недійсними та будуть використовуватися, коли процес розпочнеться заново в наступній ітерації.

Потік символів відбувається з активної послідовності в карантин. Отримана поведінка полягає в тому, що якомога більша частина вихідної послідовності включена у відповідність.

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

Посивне кількісне визначення не має карантину і включає все у фіксованій активній послідовності .

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.