https://github.com/i582/wargaming-test-task
:bulb: Тестовое задание на позицию Programming intern в компании Wargaming Санкт-Петербург
https://github.com/i582/wargaming-test-task
Last synced: 23 days ago
JSON representation
:bulb: Тестовое задание на позицию Programming intern в компании Wargaming Санкт-Петербург
- Host: GitHub
- URL: https://github.com/i582/wargaming-test-task
- Owner: i582
- License: mit
- Created: 2020-04-04T13:29:36.000Z (about 5 years ago)
- Default Branch: master
- Last Pushed: 2020-07-06T08:02:01.000Z (almost 5 years ago)
- Last Synced: 2023-03-06T21:56:06.253Z (about 2 years ago)
- Language: C++
- Homepage:
- Size: 77.1 KB
- Stars: 1
- Watchers: 1
- Forks: 2
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# Тестовое задание на позицию Programming intern в компании Wargaming Санкт-Петербург
## Задание 1
Условие задания
На языке Python или С/С++, написать алгоритм (функцию) определения четности целого числа, который будет аналогичен нижеприведенному по функциональности, но отличен по своей сути.
Объяснить плюсы и минусы обеих реализаций.
Python example:
```python
def isEven(value):
return value % 2 == 0
```
C/C++ example:
```cpp
bool isEven(int value)
{
return value % 2 == 0;
}
```С++:
1. Исходный код: [ссылка](https://github.com/i582/wargaming-test-task/blob/master/cpp/src/first/is_even.h)
**Плюсы и минусы**
Сравниваем варианты:
1. ```cpp
bool isEven(int value)
{
return value % 2 == 0;
}
```2. ```cpp
bool isEven2(int value)
{
return (value & 1) == 0;
}
```Давайте обратимся к результирующему коду на ассемблере для обеих функций, для начала используем GCC 9.3 на сайте [godbolt](https://godbolt.org/):
```asm
isEven(int):
push rbp
mov rbp, rsp
mov DWORD PTR [rbp-4], edi
mov eax, DWORD PTR [rbp-4]
and eax, 1
test eax, eax
sete al
pop rbp
ret
isEven2(int):
push rbp
mov rbp, rsp
mov DWORD PTR [rbp-4], edi
mov eax, DWORD PTR [rbp-4]
and eax, 1
test eax, eax
sete al
pop rbp
ret
```Здесь мы видим, что функции абсолютно одинаковы в ассемблере, так что если говорить о GCC, то эти реализации не имеют различий. Но не одним GCC едины, так что посмотрим на код от Clang 10.0.0:
```asm
isEven(int): # @isEven(int)
push rbp
mov rbp, rsp
mov dword ptr [rbp - 4], edi
mov eax, dword ptr [rbp - 4]
cdq
mov ecx, 2
idiv ecx
cmp edx, 0
sete sil
and sil, 1
movzx ecx, sil
mov eax, ecx
pop rbp
ret
isEven2(int): # @isEven2(int)
push rbp
mov rbp, rsp
mov dword ptr [rbp - 4], edi
mov eax, dword ptr [rbp - 4]
and eax, 1
cmp eax, 0
sete cl
and cl, 1
movzx eax, cl
pop rbp
ret
```И вот здесь уже есть разница. Clang не делает оптимизацию при взятии остатка от 2, а использует `idiv` , и вот в этом эта реализация проигрывает, так как побитовое И более быстрая операция, нежели деление. Кстати, GCC также использует деление, когда вместо 2 подставляется переменная со значением 2 (ну оно и логично, как по-другому по-простому).
Если посмотреть, что там у msvc, то немного страшно смотреть на реализацию 1 функции:
```assembly
tv66 = -4 ; size = 4
_value$ = 8 ; size = 4
bool isEven(int) PROC ; isEven
push ebp
mov ebp, esp
push ecx
mov eax, DWORD PTR _value$[ebp]
and eax, -2147483647 ; 80000001H
jns SHORT $LN5@isEven
dec eax
or eax, -2 ; fffffffeH
inc eax
$LN5@isEven:
test eax, eax
jne SHORT $LN3@isEven
mov DWORD PTR tv66[ebp], 1
jmp SHORT $LN4@isEven
$LN3@isEven:
mov DWORD PTR tv66[ebp], 0
$LN4@isEven:
mov al, BYTE PTR tv66[ebp]
mov esp, ebp
pop ebp
ret 0
bool isEven(int) ENDP ; isEventv66 = -4 ; size = 4
_value$ = 8 ; size = 4
bool isEven2(int) PROC ; isEven2
push ebp
mov ebp, esp
push ecx
mov eax, DWORD PTR _value$[ebp]
and eax, 1
jne SHORT $LN3@isEven2
mov DWORD PTR tv66[ebp], 1
jmp SHORT $LN4@isEven2
$LN3@isEven2:
mov DWORD PTR tv66[ebp], 0
$LN4@isEven2:
mov al, BYTE PTR tv66[ebp]
mov esp, ebp
pop ebp
ret 0
bool isEven2(int) ENDP ; isEven2
```Но в целом, если посмотреть на количество операций, то 2 вариант выигрывает по скорости.
Таким образом, наверное, можно сказать, что 2 вариант будет работать лучше на Clang и MSVC, но все это до тех пора пока не будет включена оптимизация :)
## Задание 2
Условие задания
На языках Python(2.7) и/или С++, написать минимум по 2 класса реализовывающих циклический буфер FIFO.
Объяснить плюсы и минусы каждой реализации.
**С++:**
Оба варианта построены на статических массивах, что позволяет избежать каких-либо проблем с памятью, так как нет динамических аллокаций, что в данном случае еще и увеличивает скорость работы. Оба варианта, так как это кольцевая очередь, имеют сложность вставки и удаления за O(1).
Если рассмотреть различия каждого из варианта, то для списковой реализации необходимо дополнительно 4 байта на указатель (8 на 64 битных системах). Это минус данной реализации. Далее различие в сдвиге при добавлении/удалении:
1. ```cpp
_tail = _tail->next;
```2. ```cpp
_tail = next_index(_tail);
...
size_t next_index(size_t ind) const
{
++ind;
return ind >= Size ? 0 : ind;
}
```Если рассматривать реализацию под микроскопом, то в 1 варианте, у нас присваивание и обращение к свойству структуры. Во втором варианте — префиксный инкремент + сравнение, в итоге получаем 2 операции.
Таким образом можно сказать, что вариант с списком более быстрый, а вариант с массивом занимает меньше места.
Код:
1. Реализация через индексы на статическом массиве: [ссылка](https://github.com/i582/wargaming-test-task/blob/master/cpp/src/second/cyclic_queue.h);
2. Реализация с помощью связных списков на статическом массиве: [ссылка](https://github.com/i582/wargaming-test-task/blob/master/cpp/src/second/cyclic_queue_list.h).**Updated (16:01 11.04.20):**
3. Реализация через индексы на динамическом массиве: [ссылка](https://github.com/i582/wargaming-test-task/blob/master/cpp/src/second/cyclic_queue_dynamic_array.h);
Добавлена реализация через динамический массив с использованием индексов. Основным отличием от реализации на статическом массиве является возможность, используя метод `resize`, изменить размер очереди.
Я понял, что все что я описал выше несколько неверно, потому что относится только к реализациям на статическом массиве. Если мы рассматриваем очереди с изменяемым размером (тут точнее сказать с увеличивающимся размером, так как и в статическом массиве можно сделать изменение размера в меньшую сторону) то реализация с помощью списков будет лучше, потому что при каждом изменении массива не надо будет делать перемещение элементов, что делает сложность `resize` равной O(1), а не O(n), в случае массива. Но, однако же, в списке все еще требуется больше памяти.
**Python:**
1. Реализация через индексы: [ссылка](https://github.com/i582/wargaming-test-task/blob/master/python/second/CyclesQueue/arrayImpl.py);
2. Реализация с помощью связных списков: [ссылка](https://github.com/i582/wargaming-test-task/blob/master/python/second/CyclesQueue/listImpl.py).## Задание 3
Условие задания
На языке Python или С/С++, написать функцию, которая быстрее всего (по процессорным тикам) отсортирует данный ей массив чисел.
Массив может быть любого размера со случайным порядком чисел (в том числе и отсортированным).
Объяснить почему вы считаете, что функция соответствует заданным критериям.
Выбрать один единственный метод из того множества сортировок, которые у нас есть невозможно. Каждый метод имеет свои плюсы и минусы. Если вспомнить первую приходящую на ум эффективную сортировку, а то есть быструю сортировку, то с ней не все так гладко она может уходить в n^2 в худшем случае. Также она имеет проблему с тем что она не стабильная, что может повлиять на скорость если сортируются комплексные объекты. Также у нее проблема, если мы сортируем связный список, а не массив. Например, сортировке слиянием в тоже время без разницы, список это или массив. Также плюсом этой сортировки является гарантированное n\*logn время, а также возможность ее распараллеливания, например, в отличии от сортировкой кучей, которая также имеет потолок в n*logn. Но тут стоит вспомнить, что сортировка слиянием требует n памяти, в отличии от быстрой (память для рекурсии logn) и кучей (1). На маленьком количестве данных (около 20) не стоит забывать и о простых методах сортировки (вставками, выбора), так как на таком количестве n^2 не сильно отличается от n\*logn. Конечно, надо также вспомнить и о такой сортировке, как Timsort, которая является сортировкой по-умолчанию в Python. Эта сортировка ведет себя почти на уровне быстрой, но, в тоже время, на частично упорядоченных массивах показывает неплохой прирост. В С++ std::sort не имеет стандарта реализации, так в GCC это быстрая с возможным переходом в кучу. Конечно, существует еще множество сортировок, но я хотел лишь привести к тому, что нельзя без информации о данных привести наилучший результат. Именно поэтому я не стану приводить реализации какого-либо алгоритма, но вполне cмогу привести, если будут известны входные данные.
Резюмируя, можно сказать: *"Предоставьте мне ~~точку опоры~~ пример данных для сортировки и я ~~переверну весь мир~~ скажу какой алгоритм использовать"* :D