Советы по игре в гольф в С


138

Какие общие советы у вас есть для игры в гольф в Си? Я ищу идеи, которые могут быть применены к кодовым проблемам гольфа в целом, которые, по крайней мере, несколько специфичны для C (например, «удалить комментарии» - это не ответ). Пожалуйста, оставьте один совет за ответ. Также, пожалуйста, укажите, относится ли ваш совет к C89 и / или C99 и работает ли он только на определенных компиляторах.


8
Я думаю, что самый большой совет в одном предложении: Прочитайте коды выигрышей, представленные в IOCCC.
вс

Ответы:


107

Используйте побитовый XOR для проверки неравенства между целыми числами:

if(a^b)вместо того, чтобы if(a!=b)сохранить 1 символ.


73
a-bдает вам тот же эффект.
Угорен

22
Точно так же вы можете использовать a*bвместо a&&b(имеет другой приоритет, может быть или не быть плохим). Если вы знаете / = -b (например, они не подписаны), то a||b==a+b
walpen

3
еще лучше объединить это с оператором Элвиса ?:(вместо if): например, просто сделать что-то, если не так: a^b?_diff_:;
Olivier Dulac

1
@OlivierDulac Есть ли компилятор, который принимает пустую троицу, если ложная ветвь?
Джонатан Фрех

1
@OlivierDulac Вы можете проверить. Из того, что я знаю, у GCC есть ?:оператор, который просто эквивалентенa ? a : b
Chromium

75
  • mainСписок аргументов Abuse для объявления одной или нескольких целочисленных переменных:

    main(a){for(;++a<28;)putchar(95+a);}
    

    (ответ на Алфавит на языках программирования )

    Это решение также использует тот факт, что a(aka argc) начинается как 1, при условии, что программа вызывается без аргументов.

  • Используйте глобальные переменные для инициализации вещей до нуля:

    t[52],i;main(c){for(;i<52;)(c=getchar())<11?i+=26:t[i+c-97]++;
    for(i=27;--i&&t[i-1]==t[i+25];);puts(i?"false":"true");}
    

    (ответ на Anagram Code Golf! )


62

Оператор запятой можно использовать для выполнения нескольких выражений в одном блоке, избегая при этом фигурных скобок:

main(){                                                                                     

int i = 0;                                                                                  
int j = 1;                                                                                  
if(1)                                                                                       
    i=j,j+=1,printf("%d %d\n",i,j); // multiple statements are all executed                                                  
else                                                                                        
    printf("failed\n");                                                                     

}

Выходы: 1 2


Не работает, если одно из утверждений break.
Максим Михайлов

9
@MaxLawnboy, потому что breakэто утверждение, и этот ответ говорит о выражениях.
NieDzejkob

59

Избегайте катастрофических объявлений типа аргумента функции

Если вы объявляете функцию, в которой все пять аргументов являются ints, то жизнь хороша. ты можешь просто написать

f(a,b,c,d,e){

Но предположим, что это dдолжно быть charили даже int*. Тогда ты облажался! Если одному параметру предшествует тип, все они должны быть:

f(int a,int b,int c,int*d,int e){

Но ждать! Есть способ обойти этот катастрофический взрыв бесполезных персонажей. Это выглядит так:

f(a,b,c,d,e) int *d; {

Это даже экономит стандартное mainобъявление, если вам нужно использовать аргументы командной строки:

main(c,v)char**v;{

на два байта короче

main(int c,char**v){

Я был удивлен, обнаружив это, так как я до сих пор не сталкивался с этим на PPCG.


6
Почему на земле это работает ??
Натаниэль

30
По-видимому, это называется стиль K & R и предшествует ANSI C на десятилетие.
Деннис

Обратите внимание, что совместное использование функций K & R и более новых (скажем, '99) функций может оказаться невозможным. Зависит от вашего компилятора.
dmckee

5
@dmckee прав. C99 не допускает неявное int, поэтому вы должны использовать, -std=gnu99и теперь вы не переносимы. В clc-talk вы даже не пишете код «С» как таковой, а «Gnu99-C». Здесь мы в основном игнорируем это, но хорошо упомянуть об этом, если вы публикуете код, специфичный для компилятора. Иногда люди на самом деле сделать для загрузки и выполнения этих программ у нас. :)
luser droog

@luserdroog: Вы можете использовать -std=c89gcc или clang, чтобы скомпилировать ваш код в соответствии с тем более старым стандартом, который допускает неявное int только с предупреждением.
Питер Кордес

37

Вместо> = и <= вы можете просто использовать целочисленное деление (/), когда сравниваемые значения выше нуля, что сохраняет один символ. Например:

putchar(c/32&&126/c?c:46); //Prints the character, but if it is unprintable print "."

Что, конечно, все еще можно сжать, используя, например, просто> и ^ (умный способ избежать написания && или || в некоторых случаях).

putchar(c>31^c>126?c:46);

Трюк с целочисленным делением, например, полезен для определения, является ли число меньше 100, так как это сохраняет символ:

a<100 vs 99/a

Это также хорошо в тех случаях, когда требуется более высокий приоритет.


Вы можете написатьputchar(c>31&c<127?c:46);
Джин Икс

37

Некоторые компиляторы, такие как GCC, позволяют опускать базовые #includeтипы s, param и return для main.

Ниже приведена действительная программа C89 и C99, которая компилирует (с предупреждениями) GCC:

main(i) { printf("%d", i); }

Обратите внимание, что отсутствует #includefor stdio.h, отсутствует тип возвращаемого значения for mainи отсутствует объявление типа for i.


17
Технически это не соответствует стандартам, так как main принимает ноль или два параметра, а не один. Не то, чтобы кто-то заботился о коде гольф.
Конрад Боровски

Вызов printf()(или любая переменная функция) без прототипа вызывает неопределенное поведение . GCC не компилирует стандарт C по умолчанию. Если вы вызовете gcc в режиме C89 ( gcc -ansi -pedantic) или C99 ( gcc -std=c99 -pedantic), вы получите довольно много жалоб, по крайней мере, в последнем случае.
Ниссе Энгстрем

@ NisseEngström: соглашения о вызовах в основных реализациях C позволяют безопасно вызывать функции с переменными числами без прототипов. Так что большинство реализаций C определяют поведение.
Питер Кордес

29

Тройной условный оператор ?:может часто использоваться в качестве подставки в простых if- elseзаявления на значительной экономии средств.

В отличие от эквивалента в c ++, оператор формально не выдает lvalue , но некоторые компиляторы (в частности, gcc) позволят вам с этим справиться, что является хорошим бонусом.


Дополнение: Если вам нужен только if, но не else, тогда троичный символ все еще может быть полезен.
Кейси

9
&&и ||может также использоваться: if(x==3)f()становится с вашим предложением x==3?f():0, и может быть улучшено до x==3&&f(). Но будьте осторожны с приоритетом оператора - если f()его заменить на y=1, то &&решение требует дополнительного набора скобок.
Угорен

1
Я никогда не осознавал, что gcc ?:дает lvalue. Могу ли я использовать это в производственном коде? LOL
Джефф Берджес

4
@ugoren: x==3&&f()можно дальше играть в x^3||f()
гольф

@fgrieu, да, хотя это не совсем тема здесь ( этот ответ предполагает это).
Угорен

27

http://graphics.stanford.edu/~seander/bithacks.html

Биты хорошие.

~-x = x - 1
-~x = x + 1

Но с разными приоритетами, и не меняйте х, как ++ и -. Также вы можете использовать это в действительно определенных случаях: ~ 9 короче, чем -10.

if(!(x&y)) x | y == x ^ y == x + y
if(!(~x&y)) x ^ y == x - y

Это более эзотерично, но у меня была возможность использовать его. Если вас не волнует короткое замыкание

x*y == x && y
if(x!=-y) x+y == x || y

Также:

if(x>0 && y>0) x/y == x>=y   

5
Последний совет ( (x/y) == (x>=y)) действительно полезен.
Угорен

24

Используйте лямбды (непортативно)

Вместо

f(int*a,int*b){return*a>*b?1:-1;}
...
qsort(a,b,4,f);

или (только gcc)

qsort(a,b,4,({int L(int*a,int*b){a=*a>*b?1:-1;}L;}));

или (llvm с поддержкой блоков)

qsort_b(a,b,4,^(const void*a,const void*b){return*(int*)a>*(int*)b?1:-1;});

попробуй что-то вроде

qsort(a,b,4,"\x8b\7+\6\xc3");

... где строка в кавычках содержит инструкции на машинном языке вашей функции "лямбда" (соответствует всем требованиям ABI платформы).

Это работает в средах, в которых строковые константы помечены как исполняемые. По умолчанию это верно для Linux и OSX, но не для Windows.

Один глупый способ научиться писать свои собственные «лямбда-функции» - это написать функцию в C, скомпилировать ее, проверить что-то вроде этого objdump -Dи скопировать соответствующий шестнадцатеричный код в строку. Например,

int f(int*a, int*b){return *a-*b;}

... при компиляции с gcc -Os -cцелью Linux x86_64 генерирует что-то вроде

0:   8b 07                   mov    (%rdi),%eax
2:   2b 06                   sub    (%rsi),%eax
4:   c3                      retq

GNU CC goto:

Вы можете вызывать эти «лямбда-функции» напрямую, но если код, который вы вызываете, не принимает параметры и не собирается возвращаться, вы можете использовать gotoдля сохранения несколько байтов. Так что вместо

((int(*)())L"ﻫ")();

или (если в вашей среде нет арабских символов)

((int(*)())L"\xfeeb")();

Пытаться

goto*&L"ﻫ";

или же

goto*&L"\xfeeb";

В этом примере eb feэто машинный язык x86 для чего-то подобного for(;;);и простой пример чего-то, что не принимает параметров и не собирается возвращать :-)

Оказывается, вы можете gotoнаписать код, который возвращается к вызывающему родителю.

#include<stdio.h>
int f(int a){
 if(!a)return 1;
 goto*&L"\xc3c031"; // return 0;
 return 2; // never gets here
}
int main(){
 printf("f(0)=%d f(1)=%d\n",f(0),f(1));
}

Приведенный выше пример (может компилироваться и запускаться в Linux с gcc -O) чувствителен к макету стека.

РЕДАКТИРОВАТЬ: В зависимости от вашей цепочки инструментов, вам, возможно, придется использовать -zexecstackфлаг компиляции.

Если это не сразу видно, этот ответ был написан в основном для lols. Я не беру на себя ответственности за лучшее или худшее гольф или неблагоприятные психологические последствия от чтения этого.


2
Я только что написал сценарий для чтения частей функции C из стандартного и печати лямбда-выражения. Возможно, стоит упомянуть в своем ответе, может быть, вам будет приятно посмотреть, так как вы научили меня делать это в первую очередь.
MD XF

23

Используйте курсоры вместо указателей. Захватите brk()в начале и используйте его как указатель базы .

char*m=brk();

Затем создайте #define для доступа к памяти.

#define M [m]

Mстановится постфиксом, *применяемым к целым числам. (Старый трюк [x] == x [a].)

Но это еще не все! Тогда у вас могут быть аргументы и возвраты указателей в функциях, которые короче макросов (особенно если вы сокращаете «возврат»):

f(x){return x M;} //implicit ints, but they work like pointers
#define f(x) (x M)

Чтобы сделать курсор из указателя, вы вычитаете базовый указатель, получая ptrdiff_t, который усекается в int, потери - это ваш бизнес.

char *p = sbrk(sizeof(whatever)) - m;
strcpy(m+p, "hello world");

Эта техника используется в моем ответе на « Напиши интерпретатор для нетипизированного лямбда-исчисления» .


21

Определите параметры вместо переменных.

f(x){int y=x+1;...}

f(x,y){y=x+1;...}

Вам не нужно фактически передавать второй параметр.

Также вы можете использовать приоритет оператора для сохранения скобок.
Например, (x+y)*2может стать x+y<<1.


Или просто x+y*2, сохраняя еще один символ.
Брэден Бест

4
@ B1KMusic, x+y*2не то же самое, из-за приоритета оператора.
Угорен

Хорошо, лол. Это было бы х + (у * 2). Я был зациклен на x+y<<1примере, предполагая, что он оценивается как x+(y<<1), и предложил *2вместо этого. Я не знал, что операции с бит-смещением оценивались как, например,(x+y)<<2
Braden Best

20

Так как обычно EOF == -1, используйте побитовый оператор NOT для проверки EOF: while(~(c=getchar()))или while(c=getchar()+1)и изменяйте значение c в каждом месте


1
Я недостаточно хорошо знаю C, но не while(1+c=getchar())сработает?
ɐɔı'uʎs

6
@ operatorı No.uʎs Нет. Оператор сложения +имеет более высокий приоритет, чем оператор присваивания =, поэтому 1+c=getchar()эквивалентен (1+c)=getchar(), который не компилируется, потому что (1+c)не является lvalue.
ace_HongKongIndependence

19

Тернарный оператор ?:необычен тем, что состоит из двух отдельных частей. Из-за этого он представляет собой небольшую лазейку для стандартных правил приоритета операторов. Это может быть полезно для избежания скобок.

Возьмите следующий пример:

if (t()) a = b, b = 0;  /* 15 chars */

Обычная игра в гольф подход заключается в замене ifс &&, но из-за низкого приоритета оператора запятая, вам требуется дополнительная пара скобок:

t() && (a = b, b = 0);  /* still 15 chars */

Средняя часть троичного оператора не нуждается в скобках:

t() ? a = b, b = 0 : 0;  /* 14 chars */

Подобные комментарии относятся к подпискам массива.


7
В этом примере b-=a=bеще короче. ?:Трюк еще полезно, -=потому что также имеет низкое предпочтение.
Угорен

Хорошая точка зрения; мой пример был излишне сложным.
хлебница

Другое дело, что иногда вы хотите , чтобы перевернуть условие: для x>0||(y=3), x>0?0:(y=3)бесполезно, но x<1?y=3:0делает работу.
Угорен

и clang, и gcc допускают пустой истинный регистр в троице. Если опущено, его значение является значением условия. Например,x>5?:y=1
Крис Уздавинис

19

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

#define R return

Это очень распространенный случай использования, если ваш код включает в себя более пары функций. Другие длинноватые ключевые слова , такие как while, double, switch, и caseтакже являются кандидатами; а также все, что является сомнительным в вашем коде.

Я обычно резервирую заглавные буквы для этой цели.


1
Короче замена будет -DR=return. Обратите внимание, что если вы включаете определенные символы, может возникнуть необходимость в одинарных или двойных кавычках вокруг определения -DP='puts("hello")'.

15

Если ваша программа читает или пишет по одному на каждом шаге, всегда старайтесь использовать функцию чтения и записи вместо getchar () и putchar () .

Пример ( поменять стандартный ввод и поместить на стандартный вывод )

main(_){write(read(0,&_,1)&&main());}

Упражнение: Используйте эту технику, чтобы получить хороший результат здесь .


Что вы подразумеваете под каждым шагом ?
Кейси

Кейси: Я полагаю, они имеют в виду, что программа что-то читает, работает с ней и записывает вывод. Так сказать, в потоковом режиме. В отличие от подхода, при котором весь ввод должен быть прочитан и обработан одновременно.
Джои

Джои права, я имел в виду то же самое, извините, я не проверял свой почтовый ящик до сегодняшнего дня.
Quixotic

8
Эта манипуляция со стеком великолепна.
Андреа Биондо

14

Обратные петли

Если можешь, попробуй заменить

for(int i=0;i<n;i++){...}

с участием

for(int i=n;i--;){...}

13

Если вам когда-либо понадобится вывести один символ новой строки ( \n), не используйте putchar(10), используйте puts("").


12

Используйте возвращаемые значения для нуля вещи. Если вы вызываете какую-то функцию, и эта функция возвращает ноль при нормальных условиях, то вы можете поместить ее в место, где ожидается ноль. Аналогично, если вы знаете, что функция вернет ненулевое значение с добавлением взрыва. В конце концов, в любом случае вы не выполняете правильную обработку ошибок в кодовом гольфе, верно?

Примеры:

close(fd);foo=0;   →  foo=close(fd);    /* saves two bytes */
putchar(c);bar=0;  →  bar=!putchar(c);  /* saves one byte  */

12

Назначьте вместо возврата.

Это не совсем стандартный C, но он работает с каждым известным мне компилятором и процессором:

int sqr(int a){return a*a;}

имеет такой же эффект как:

int sqr(int a){a*=a;}

Потому что первый аргумент сохраняется в том же регистре процессора, что и возвращаемое значение.

Примечание. Как отмечается в одном комментарии, это неопределенное поведение, которое не гарантируется для каждой операции. И любая оптимизация компилятора просто пропустит это.

X-макросы

Еще одна полезная функция: X-Macros могут помочь вам, когда у вас есть список переменных, и вам нужно выполнить некоторые операции, которые включают все из них:

https://en.wikipedia.org/wiki/X_Macro


3
Я процитировал это, и я был исправлен, Это просто неправда. Он будет работать только с умножениями и делениями, а также когда оптимизации отключены. Это потому, что обе операции помещают свои результаты в EAX, который является общим регистром для возврата. Параметры хранятся либо в стеке, либо в ecx или edx. Попробуй сам.
Gaspa79

3
Вы правы, это неопределенное поведение, оно также зависит от компилятора и архитектуры, я обычно проверяю gcc на x86 и armv7, прежде чем публиковать любой ответ, используя этот трюк. И, конечно, если вы включите оптимизацию, любой умный компилятор просто удалит ненужное умножение.
ГБ

3
Я видел эту работу с GCC, но не с другими
Альберт Реншоу

1
@ Gaspa79: gcc -O0всегда выбирает для вычисления выражений в регистре возвращаемого значения. Я посмотрел, по крайней мере, x86, ARM и MIPS (на gcc.godbolt.org ), и gcc, похоже, изо всех сил пытается это сделать -O0. Но помните , если вы воспользоваться этим, язык программирования вы в есть gcc -O0, не C , и вы должны маркировать свой ответ соответственно, а не C . Он не работает на любом уровне оптимизации, кроме -O0режима отладки, и не работает с Clang IIRC.
Питер Кордес

11
  1. Используйте *aвместо a[0]доступа к первому элементу массива.

  2. Реляционные операторы ( !=, >и т. Д.) Дают 0или 1. Используйте это с арифметическими операторами, чтобы задать различные смещения в зависимости от того, является ли условие истинным или ложным: a[1+2*(i<3)]получит доступ, a[1]если i >= 3и в a[3]противном случае.


11
a[i<3?3:1]на два символа короче a[1+2*(i<3)].
Рето Коради

10

Вы можете заглянуть в архив IOCCC (международный конкурс обфусцированного кода C).

Один заметный трюк заключается в том, чтобы #define макросы, чье расширение имеет несбалансированные скобки / скобки, например

#define P printf(

16
Несоответствующие скобки сами по себе не имеют значения. Смысл в том, чтобы определить как можно больше повторяющихся шаблонов. Вы можете пойти дальше, с #define P;printf(.
Угорен

Как это сократить количество байтов? Может быть, привести пример?
Cyoce

2
@Cyoce Смотрите, например, этот ответ .
Джонатан Фрех

8

for(int i=0;i<n;i++){a(i);b(i);} короче можно сделать несколькими способами:

for(int i=0;i<n;){a(i);b(i++);} -1 для перемещения ++до последнего iв цикле

for(int i=0;i<n;b(i++))a(i); -3 больше для перемещения всех операторов, кроме одного, в верхнюю часть и из основного цикла, удаления скобок


Использование оператора запятой - это еще один способ избежать скобок в некоторых случаях.
Питер Кордес

8

Работай!

Если вы можете свести свою проблему к простым функциям с такой же сигнатурой и определенным как одиночные выражения, то вы можете добиться большего успеха #define r returnи вычленить практически весь шаблон для определения функции.

#define D(f,...)f(x){return __VA_ARGS__;}
D(f,x+2)
D(g,4*x-4)
D(main,g(4))

Результатом программы является ее значение состояния, возвращаемое ОС или управляющей оболочке или IDE.

Использование __VA_ARGS__позволяет вам использовать оператор запятой для введения точек последовательности в эти выражения-функции . Если это не нужно, макрос может быть короче.

#define D(f,b)f(x){return b;}

7
  1. используйте, scanf("%*d ");чтобы прочитать фиктивный ввод. (в случае, если ввод не имеет смысла в дальнейшей программе), он короче, чем scanf("%d",&t);где вы также должны объявить переменную t.

  2. Хранение символов в массиве int намного лучше, чем в массиве символов. пример.

    s[],t;main(c){for(scanf("%*d ");~(c=getchar());s[t++]=c)putchar(s[t]);}


2
На самом деле, я использую %*dне только в Гольфе, потому что это также полезно в ситуациях, когда, например, хочется пропустить новую scanf("%[^\n]%*c",str);
строку

6

Выведите символ, затем возврат каретки вместо:

printf("%c\n",c);

или же

putchar(c);putchar('\n'); // or its ascii value, whatever!

просто объявите c как int и:

puts(&c);

9
Вероятно, стоит отметить, что это зависит от архитектуры с прямым порядком байтов. Если с является int с прямым порядком байтов, то вы просто получите возврат каретки. (С другой стороны, если c - это символ, вы можете получить случайный мусор после возврата каретки.)
breadbox

@breadbox Да, вы совершенно правы; Я только что отредактировал: последний отрывок должен использовать c как int (который часто легко объявить как таковой).
Моала

Есть ли на puts(&c)самом деле работает? Это не обязательно будет завершено нулем.
Esolanging Fruit

1
@EsolangingFruit На младшем байте с 32-разрядными целыми числами int 0 ≤ c <256 сохраняется как последовательность байтов c 0 0 0 . При интерпретации адреса c as char *мы видим одноэлементную строку: символ c , за которым следует нулевой байт.
Денис

6

Использование asprintf()спасает вас от явного размещения, а также измерения длины строки ака char*! Возможно, это не слишком полезно для игры в код, но облегчает повседневную работу с массивами символов. Есть еще несколько хороших советов в 21 веке .

Пример использования:

#define _GNU_SOURCE
#include <stdio.h>

int main(int argc, char** argv) {
  char* foo;
  asprintf(&foo, "%s", argv[1]);
  printf("%s",foo);
}

6

import если вам нужно

Как отмечалось в самом первом ответе , некоторые компиляторы (в частности, GCC и clang) позволяют вам не использовать #includes для стандартных функций библиотеки.

Даже если вы не можете просто удалить #include, могут быть другие способы избежать этого , но это не всегда практично или особенно в гольфе.

В остальных случаях вы можете использовать #import<header file>вместо того, #include<header file>чтобы сохранить байт. Это расширение GNU, и оно считается устаревшим, но оно работает по крайней мере в gcc 4.8, gcc 5.1 и clang 3.7.


6

Попробуй cpow()вместоcos()

Вместо

double y=cos(M_PI*2*x);

попробуй что-то вроде

double y=cpow(-1,x*2);

При этом используется формула Эйлера , немного сложный анализ и наблюдение, что присвоение комплекса двойному дает действительную часть (осторожно с вызовами функций с переменными числами и другими тонкостями).

cos2πx+jsin2πx=ej2πx=ejπ2x=(1)2x

Этот тип трюк может быть использован для уменьшения

double y=cpow(-1,x/2);

в

double y=cpow(1i,x);

(1)x2=j2x2=jx

LATEX


5

Вот несколько советов, которые я использовал в своих интересах. Я бесстыдно украл их у других, так что поверь никому, кроме меня:

Объединить назначение с вызовами функций

Вместо этого:

r = /* Some random expression */
printf("%d", r);

Сделай это:

printf("%d", r = /* Some random expression */);

Инициализируйте несколько переменных вместе (когда это возможно)

Вместо этого:

for(i=0,j=0;...;...){ /* ... */ }

Сделай это:

for(i=j=0;...;...){ /* ... */ }

Свернуть ноль / ненулевые значения

Это аккуратный трюк, который я подобрал у кого-то здесь (не помню, кто, извините). Если у вас есть целочисленное значение, и вам нужно свернуть его до 1 или 0, вы можете использовать !!это легко. Это иногда выгодно для других альтернатив, как ?:.

Возьмите эту ситуацию:

n=2*n+isupper(s[j])?1:0; /* 24 */

Вы могли бы вместо этого сделать это:

n=n*2+!!isupper(s[j]); /* 22 */

Другой пример:

r=R+(memcmp(b+6,"---",3)?R:0); /* 30 */

Может быть переписан как:

r=R+R*!!memcmp(b+6,"---",3)); /* 29 */

1
может бытьR*-~!!mxxxx
l4m2

5

Знание основных логических равенств может сэкономить пару байтов. Например, вместо того, чтобы if (!(a&&b)){}пытаться вместо этого использовать закон Деморгана if (!a||!b){}. То же самое относится к побитовым функциям: вместо ~(a|b)do ~a&~b.


Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.