Острівний гольф №1: кругообіг


43

Це перший у серії викликів Island Golf. Наступний виклик

З огляду на острів ASCII-art, виведіть оптимальний шлях для його обходу.

Вхідні дані

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

...........
...##......
..#####....
..#######..
.#########.
...#######.
...#####.#.
....####...
...........

Завжди буде хоча б одна плитка землі. Усі суходольні плитки будуть суміжними (тобто є лише один острів). Водяні плитки також будуть суміжними (тобто озер немає). Зовнішня межа сітки - це водопровідна плитка. Земляні плитки не будуть з'єднані по діагоналі: тобто ви ніколи не побачите чогось подібного

....
.#..
..#.
....

Вихідні дані

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

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

.ooooo.....
o..##.oo...
o.#####.o..
o.#######o.
o#########o
ooo#######o
..o#####.#o
..oo####..o
....oooooo.

Довжина обведення обчислюється наступним чином: Для кожної пари сусідніх плиток на шляху, якщо вони з'єднані горизонтально або вертикально, додайте 1; якщо вони з'єднані по діагоналі, додайте √2. Довжина вищевказаного шляху становить 22 + 7√2 (≈ 31,9).

Найкоротший кругосвітній є КРУГОСВІТКА з найкоротшою можливої довжиною. Ваша програма повинна вивести будь-який шлях, який задовольняє цій умові. Для більшості островів буде кілька можливих рішень. Ось одне рішення для вищевказаного острова, довжиною 10 + 13√2 (≈ 28,4):

...oo......
..o##oo....
.o#####oo..
.o#######o.
o#########o
.o.#######o
..o#####.#o
...o####.o.
....ooooo..

Деталі

Ваше рішення може бути повноцінною програмою або функцією . Будь-який із методів введення та виведення за замовчуванням є прийнятним.

Вхід і вихід можуть бути рядком з рядком або списком рядків. Якщо ваша мова має тип символів, відмінний від одно символьних рядків, ви можете замінити "список символів" на "рядок" у попередньому реченні. Якщо ваша мова потребує введення висоти та / або ширини сітки, ви можете це зробити. Ваш висновок може (необов'язково) мати один зворотний новий рядок. Як було сказано вище, ви можете використовувати будь-які три різних символи замість #.o(будь-ласка, вкажіть у поданні, які символи ви використовуєте).

Тестові справи

A. Острови з унікальними найкоротшими округами:

...
.#.
...

.o.
o#o
.o.

......
.####.
......

.oooo.
o####o
.oooo.

......
......
..##..
...#..
......
......

......
..oo..
.o##o.
..o#o.
...o..
......

.......
.#####.
...#...
...#...
.#####.
.......

.ooooo.
o#####o
o..#..o
o..#..o
o#####o
.ooooo.

.......
...#...
...#...
.#####.
...#...
...#...
.......

...o...
..o#o..
.o.#.o.
o#####o
.o.#.o.
..o#o..
...o...

.......
.#####.
.##..#.
..#..#.
.......

.ooooo.
o#####o
o##..#o
.o#..#o
..oooo.

Б. Приклад острова з кількома можливими рішеннями:

........
....##..
...####.
..###...
.#####..
.#####..
..##....
........

Можливі виходи:

....oo..
...o##o.
..o####o
.o###.o.
o#####o.
o#####o.
.o##oo..
..oo....

....oo..
...o##o.
..o####o
.o###.o.
o#####o.
o#####o.
.o##.o..
..ooo...

....oo..
...o##o.
..o####o
.o###..o
o#####.o
o#####o.
.o##oo..
..oo....

....oo..
...o##o.
..o####o
.o###..o
o#####.o
o#####o.
.o##.o..
..ooo...

C. Великий тестовий випадок як суть


Це : виграє найкоротший код на кожній мові.


1
Найкоротший кругообіг для 3-го тестового випадку - це «хліб» у «Гра життя» Конвея!
Товариш SparklePony

Відповіді:


18

Mathematica (версія 9), 165 байт

Приємна, коротка ConvexHullMeshфункція, яку використовував Грег Мартін, була введена лише у версії 10 Mathematica, тому я подумав, що спробую зробити без неї, використовуючи свою стародавню версію Mathematica 9. Хоча мені вдалося отримати її трохи коротше! Це функція , яка приймає і повертає список рядків (з ., #і , oяк і символи).

""<>#&/@("o"MorphologicalTransform[MorphologicalComponents[#,Method->"ConvexHull"],Max[#(1-#[[2,2]])CrossMatrix@1]&]+"#"#/.{0->"."})&[Characters@#/.{"."->0,"#"->1}]&

Пояснення:

  • По-перше, Characters@# /. {"."->0, "#"->1}перетворює вхід в матрицю 0s і 1s.
  • "o"MorphologicalTransform[MorphologicalComponents[#,Method->"ConvexHull"],Max[#(1-#[[2,2]])CrossMatrix@1]&]+"#"#потім використовує потужні (але надзвичайно важкі для байтів…) можливості обробки зображень для того, щоб спочатку заповнити опуклий корпус острова (який ви отримаєте, якщо натягнути навколо нього шматок струни), а потім взяти його межу. Потім ми множимо цю матрицю на рядок, "o"щоб отримати матрицю 0s і "o"s (завдяки вражаючої пристосованості Mathematica щодо типів), і додаємо це в "#"рази матрицю острова.
  • Нарешті, ""<>#& /@ (... /. {0->"."})перетворює цю матрицю "o"s, "#"s і 0s в матрицю "o"s, "#"s і "."s і з'єднує кожен рядок у рядок.

Коли ми тестуємо це на прикладі B , отримуємо вихід

{"....oo..",
 "...o##o.",
 "..o####o",
 ".o###..o",
 "o#####o.",
 "o#####o.",
 ".o##oo..",
 "..oo...."}

[Редагувати, завдяки Грегу Мартіну:] Якщо нам дозволяють використовувати масиви символів замість списків рядків, ми можемо скоротити це до 144 байт:

"o"MorphologicalTransform[MorphologicalComponents[#,Method->"ConvexHull"],Max[#(1-#[[2,2]])CrossMatrix@1]&]+"#"#/.{0->"."}&[#/.{"."->0,"#"->1}]&

1
Чудово зроблено! Я ніколи про це не знав MorphologicalComponents[#, Method->"ConvexHull"] :) Ви можете зберегти ще більше байтів, припустивши, що вхід вже розділений на символи, а також повернувши 2D масив символів.
Грег Мартін

@GregMartin, я не знав про таке використання MorphologicalComponentsжодного дня!
Не дерево

Початківець Mathematica тут: як я повинен викликати цю функцію? Я спробував f[{"...",".#.","..."}]і помилився.
DLosc

@DLosc, функція - це вся справа, а не лише f. (Ну, строго кажучи, це речі після крапки з комою.) Щоб викликати функцію, введіть всю річ у вікно Mathematica, а потім - [ваше введення, і ], таким чином, воно повинно виглядати приблизно так f@m_:=m(1-m[[2,2]]) . . . #/.{"."->0,"#"->1}]&[{"...", ".#.", "..."}](скорочене для місця).
Не дерево

@DLosc Ну, це тому, що код зламаний. Я думаю, що я це вже виправив! (Я поняття не маю, що там сталося; вибачте ...)
Не дерево

11

(Але перейдіть на розгляд рішення Нотатрі , краще!)

Математика, 168 байт

(x_~n~y_:=Min[Norm[x-#]&/@y];i=#;p=i~Position~#&;t=p["#"|"."]~Select~#&;(i~Part~##="o")&@@@t[#~n~t[ConvexHullMesh[Join[s=p@"#",#+{.1,.1}&/@s]]~RegionMember~#&]==1&];i)&

Чиста функція, що приймає 2D масив символів як вхідний і повертає 2D масив символів. Простіша для читання версія:

1  (x_~n~y_ := Min[Norm[x - #] & /@ y];
2  i = #; p = i~Position~# &; 
3  t = p["#" | "."]~Select~# &;
4  (i~Part~## = "o") & @@@ 
5    t[#~n~
6      t[ConvexHullMesh[
7        Join[s = p@"#", # + {.1, .1} & /@ s]]
8      ~RegionMember~# &] == 1 &];
9  i) &

Рядок 1 визначає функцію, nяка виробляє (найменшу) відстань між точкою xв площині та набором yінших точок. Рядок 2 ініціалізує змінну iна вхід, як щоб потім усунути неясність, що виникає, і мати змогу модифікувати її для отримання можливого результату; рядок 2 також визначає функцію, pяка повертає координати всіх подій його вводу в i.

У рядку 3 p["#" | "."]позначається кожна окрема координата з вхідної карти (оскільки всі її символи є "#"або "."), і tце функція, яка вибирає лише ті координати, які задовольняють ще не визначене властивість. У рядку 4 i~Part~## = "o"збирається змінити купу записів iперсонажа "o"; ці символи будуть обрані з набору можливих координат відповідно до матеріалів на рядках 5-8. І рядок 9 просто повертає відповідь після її обчислення.

Гаразд, інфраструктура зроблена, тепер до власних обчислень. ConvexHullMesh- це вбудована система Mathematica для обчислення опуклого корпусу набору точок (найменший опуклий багатокутник, що містить ці точки). Морально кажучи, це повинно "заповнити" бухти та фіорди острова (що є s = p@"#"), щоб виключити їх із нашого циркумнавігації. Існує невелика проблема з тим, ConvexHullMeshколи цей набір точок знаходиться в рядку (спасибі, тестовий випадок №2), який ми вирішуємо, додавши sдо себе в рядку злегка зміщену версію . Цей вихід є багатокутником, тому рядки 7 -9 (t[...~RegionMember~# &]) створює список точок з цілими координатами в цьому многокутнику. Нарешті, рядок 5 та кінець рядка 9 обчислюють усі точки, які знаходяться на відстані рівно 1 (отже, не 0) від цього набору цілих точок; цей набір стає обхідним шляхом.

Нижче наводиться вихід для великого тестового випадку за посиланням ОП. Зауважте у верхньому лівому куті, незвичний вибір, коли рухатись на захід проти південного заходу, натякає на те, що це затінення невидимою лінією схилу -2/3 між двома півостровами (зазначений відрізок лінії є частиною межі опуклого корпусу).

........................
.............o..........
...........oo#ooooooo...
..........o#.#.##...#o..
........oo.#.#.###.##o..
.......o..########.##o..
.....oo...############o.
...oo#....############o.
..o#.###.##############o
.o##.##################o
.o####################o.
.o.##################.o.
.o##################..o.
.o..################..o.
o###################..o.
o#####################o.
o.##################.o..
o####################o..
o#...##############.o...
o##...#############o....
o#.....###....#oooo.....
.oooooo#ooooooo.........
.......o................

Чи представляє Mathematica зазвичай рядки як 1D масиви символів? Якщо ні, то вам потрібно буде взяти / повернути 1D масив рядків. (Також з нетерпінням чекаю пояснення! Я не думаю, що мені вдасться це запустити, не маючи Mathematica, правда?)
DLosc

Mathematica має рядковий тип даних, але, схоже, масив символів також дійсний для цілей цього веб-сайту (тобто я дізнався про це, коли запускався в PPCG, але я забуваю законності, чому). Так, на жаль, Mathematica є невільним і, таким чином, не доступний багатьом людям :(
Грег Мартін

1
@GregMartin Я завжди пробую рішення Mathematica на sandbox.open.wolframcloud.com
ов

Поточний консенсус говорить, що списки односимвольних рядків не можна використовувати замість рядка. Наскільки я можу сказати, "символи" в Mathematica - це лише односимвольні рядки, як у Python. Ситуація різна в такій мові, як Java, яка має окремий charтип; у цьому випадку charзамість рядка може бути використаний масив.
DLosc

1
Ось як я її прочитав: Основна відповідь, яка була схвалена, була опублікована в 2014 році. Відповідь, яку я пов’язував, була розміщена в 2016 році, як спроба з'ясувати неоднозначність у попередній відповіді. Тому я читаю негативну оцінку за новою відповіддю, коли люди говорять: «Ні, ми не хочемо, щоб старіші відповіді означали, що списки однорядних рядків у порядку». Але незалежно від мета, у цьому питанні я забороняю списки однозначних рядків (і я уточнив формулювання, щоб це відобразити).
DLosc

10

Python 3, 779 байт (відступ з вкладками)

Це вся програма. Він зчитує вхід зі stdin та друкує його до stdout. Стдін повинен закінчитися EOF. Приклад запуску з великим вкладом: https://ideone.com/XIfYY0

import itertools,sys
L=list
C=itertools.count
d=L(map(L,filter(None,sys.stdin.read().split('\n'))))
X=len(d[0])
Y=len(d)
R=range
def P(r):return all(d[y][x]=='.'for x,y in r)
def S(f):
    for n in C(0):
        if P(f(n)):l=n
        else:break
    for n in C(l+1):
        if P(f(n)):return l,n
def f(V,a,*b):return L(eval('lambda '+a+':('+i+')',V)for i in b)
V=locals()
def D(n):
    y=min(n,Y-1);x=n-y
    while y>=0and x<X:yield(x,y);x+=1;y-=1
def E(n):
    x=max(0,n-Y);y=x+Y-n
    while y<Y and x<X:yield(x,y);x+=1;y+=1
F=f(V,'p','(p,y)for y in R(0,Y)','(x,p)for x in R(0,X)')+[D,E]
r=f(V,'x,y','x','y','x+y','x-y+Y')
B=L(map(S,F))
for x in R(0,X):
    for y in R(0,Y):
        z=L(zip(r,B))
        if all(g(x,y)in R(a,b+1)for g,(a,b)in z)and any(g(x,y)in e for g,e in z):d[y][x]='o'
print('\n'.join(''.join(x)for x in d))

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


1
Вам не потрібно використовувати sys.stdinяк вхід. input(), отримання мультилінійного зробить роботу і коштує менше байтів
Dead Possum

2
Може бути в змозі замінити R(0,x)зR(x)
ceilingcat

+1 за те, що не використовується вбудований.
Роберт Фрейзер

1
Приємно! Ще кілька порад щодо гольфу: збережіть по 5 байт за допомогою лямбда для визначення Pта f; L(generator expression)=> [generator expression]; F, rі, Bздається, використовуються лише один раз за штуку, і тому вони можуть бути накресленими.
DLosc

8

JavaScript (ES6), 369 343 байт

f=s=>(a=s.split`
`.map(s=>[...s]),m=Array(8),a.map((b,i)=>b.map((c,j)=>c>'#'||[i-j,i,j+i,j,j-i,-i,-i-j,-j].map((d,k)=>d>m[k]||(m[k]=d-1)))),[o,p,q,r,t,u,v,w]=m,g=(i,j,k,l,...p)=>i-k|j-l?a[i][j]=g(i+(k>i)-(k<i),j+(l>j)-(l<j),k,l,...p):1/p[0]?g(k,l,...p):'o',g(p,p-o,p,q-p,q-r,r,r-t,r,-u,t-u,-u,u-v,w-v,-w,o-w,-w,p,p-o),a.map(b=>b.join``).join`
`)

Пояснення: Рядок розділений на символьний масив (мені незрозуміло, чи прийнятний вхід масиву символів). Потім масив перебирається і розташовуються позиції всіх земельних площ. Обмежує лінія , задані рівняння x - y = o, x = p, x + y = q, y = r, y - x = t, -x = u, -x - y = v, -y = wвизначається таким чином, щоб максимально можливий параметр , де всі землі знаходяться за межами лінії. Це впливає на огорожу острова у восьмикутник. Координати кутів восьмикутника легко обчислюються за параметрами, а комірки на його краю заповнюються. Потім масив об'єднується назад у рядок. Причина восьмикутника достатня в наступному:

   /o#     /o#     /o#
 |/o #   |/o #   |/ o#
 *o###   * o #   *  o#
/|o #   /|o #   /| o#
 |o#     |o#     |o#

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


Що робить ´ ... p´?
Роберт Фрейзер

@RobertFraser Технічна назва - це руйнування масиву. Однак у цьому випадку він просто виступає в якості rest of argumentsпараметра.
Ніл

@Neil Власне, технічна назва є параметром відпочинку . Той самий синтаксис використовується для оператора розповсюдження . (Ви використовуєте обидва, як ...pу різних місцях.) Деструктуризація - це щось інше (хоча оператор розповсюдження може бути використаний у деструктуризації).
Брайан МакКучон

@BrianMcCutchon Ви маєте рацію, я мав на увазі оператора розповсюдження, але деструктурування працює в списках аргументів все одно.
Ніл

6

Python 3,5, 224, 263, 234 218 байт

Позбавившись ще 16 байт, позбувшись вкладеної функції та зробивши її однолінійною.

def h(s,k=0,i=0):w=s.find('\n')+1;x=s.find('X')-w;k=k or x;d=[1,w+1,w,w-1,-1,-w-1,-w,-w+1]*2;u=s[:k]+'o'+s[k+1:];return['X'>s[k]and i<8and(h(u,k+d[i+2],i+2)or h(u,k+d[i+1],i+1)or h(u,k+d[i],i))or'',s][s[k]>'X'and k==x]

Поле 29 байт:

def f(s):
 w=s.find('\n')+1;x=s.find('X')-w;d=[1,w+1,w,w-1,-1,-w-1,-w,-w+1]*2
 def h(s,k,i):u=s[:k]+'o'+s[k+1:];return['X'>s[k]and i<8and(h(u,k+d[i+2],i+2)or h(u,k+d[i+1],i+1)or h(u,k+d[i],i))or'',s][s[k]>'X'and k==x]
 return h(s,x,0)

Введення - це один рядок, що використовує "~" для океану, "X" для суші, а "o" для межі. (Використання "X" зберігає байт для '>' замість '==')

Менш гольф-версія з коментарями:

def f(s):
    w=s.find('\n')+1                         # width of one row
    x=s.find('X')-w                          # starting point
    d=[1,w+1,w,w-1,-1,-w-1,-w,-w+1]*2        # delta to add to current index to move in 
                                             # the 8 directions: E, SE, S, SW, W, NW, 
                                             # N, NE. Make it long to avoid
                                             # lots of modulo operations in 
                                             #    the recursive calls

    def h(s,k,i):                            # s is the island string, k the current
                                             # position, i the direction index
        if s[k]>'X'and k==x:                 # if back at the begining,
            return s                         #   return the map

        elif 'X'>s[k] and i<8:               # if there is water here, and haven't
                                             #  looped around,
            u=s[:k]+'o'+s[k+1:]              #  make a new map with an 'o' in the 
                                             #  current spot

            r = h(u,k+d[i+2],i+2)            # try a 90 degree right turn
            if r: return r

            r = h(u,k+d[i+1],i+1)            # try a 45 degree turn
            if r: return r

            r= h(u,k+d[i],i)                 # try straight ahead
            if r: return r

        return ''                            # this path failed

    return h(s,x,0)

@DLosc виправлено. (чи слід видалити стару відповідь?)
RootTwo

Приємно! (Так, ви повинні видалити стару відповідь - якщо хтось хоче її побачити, він може переглянути історію редагування публікації.)
DLosc

5

C # 7 - 414 369 327 байт

Редагувати : перемкнулося на 1D-циклічне обчислення, обчислення iта jна льоту

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

using C=System.Console;class P{static void Main(){var D=C.In.ReadToEnd().Replace("\r","");int W=D.IndexOf('\n')+1,H=D.Length,z=H,k,q,c;int P()=>z%W*(k%3-1)+z/W*(k/3-1)+H;var B=new int[9];for(;z-->0;)for(k=9;k-->0&D[z]%7<1;)if(B[k]<=P())B[k]=P()+1;for(;++z<H;C.Write(q>9?'o':D[z]))for(q=k=9;k-->0;)q*=(c=P()-B[k])>0?0:c<0?1:2;}}

Спробуйте в Інтернеті

Повна програма, приймає вхід до стандартного в, виводить його на стандартне використання #, використання .та o. Для кожної комірки він обчислює "профіль" (це відстань понад 8 напрямків (для зручності здається, що для зручності обчислити дев'яту, але це завжди 0) і записує максимум кожної з них. Потім виписується вся карта знову, і замінює будь-яку комірку, яка знаходиться як на межі, так і поза межами жодної, на "o". Коментований код нижче пояснює, як це все працює.

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

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

Приклад використання та виведення:

type t7.txt | IslandGolf1.exe

.........ooooooooooo....
........o....#......o...
.......o...#.#.##...#o..
......o....#.#.###.##.o.
.....o....########.##..o
....o.....############.o
...o.#....############.o
..o#.###.##############o
.o##.##################o
o.####################.o
o..##################..o
o.##################...o
o...################...o
o###################...o
o#####################.o
o.##################..o.
o####################o..
o#...##############.o...
o##...#############o....
o#.....###....#...o.....
.o.....#.........o......
..ooooooooooooooo.......

Форматований та коментований код:

using C=System.Console;

class P
{
    static void Main()
    {
        // \n 10
        // # 35
        // . 46
        // o 111


        var D=C.In.ReadToEnd().Replace("\r",""); // map

        int W=D.IndexOf('\n')+1, // width
            H=D.Length, // length
            z=H, // position in map (decomposed into i and j by and for P)
            k, // bound index
            q, // bound distance, and later cell condition (0 -> outside, 8 -> inside, >8 -> on boudary)
            c; // (free), comparison store

        // 'indexes' into a profile for the point z at index k
        // effectively {i=z%W,j=z/W,-i,-j,i+j,j-i,-i-j,i-j,0}[k] (inside order is a bit different) (0 const is always treated as 'inside bounds')
        // each non-zero-const entry describes the distance in one of the 8 directions: we want to maximise these to find the 'outer bounds'
        // the non-zero-const bounds describe 8 lines, together an octogen
        int P()=>z%W*(k%3-1)+z/W*(k/3-1)+H; // new C#7 local method syntax (if you don't have C#7, you can test this code with the line below instead)
        //k=0;System.Func<int>P=()=>z%W*(k%3-1)+z/W*(k/3-1)+H; // old lambda syntax (must pre-assign k to make static checker happy)

        var B=new int[9]; // our current bounds, each is initially null (must only call P() when on a #)
        // B[k] starts off a 0, P() has a +H term, and W+(H/W)<H for W >= 3, so B[k] is assigned the first time we compare it (H-i-j always > 0)

        for(;z-->0;) // for each cell
            for(k=9;k-->0& // for each bound
                D[z]%7<1;) // if this cell is #
                if(B[k]<=P())B[k]=P()+1; // update bound if necessary (add one so that we define the bound _outside_ the hashes)
        // z=-1
        for(;++z<H; // for each cell
                C.Write(q>9?'o':D[z])) // print the cell (if q > 9, then we are on the bounds, otherwise, spit out whatever we were before)
            // check we are not 'outside' any of the bounds, and that we are 'on' atleast one of them
            for(q=k=9;k-->0;) // for each bound
                q*=(c=P()-B[k])>0?0: // outside bound (q=0)    (??0 is cheaper than (int) or .Value)
                    c<0?1: // inside (preserve q)
                    2; // on bound (if q != 0, then q becomes > 9)
    }
}

найбільший восьмикутник? чи найменший?
Sarge Borsch

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