Skip to content

💡 Тестовое задание на позицию Programming intern в компании Wargaming Санкт-Петербург

License

Notifications You must be signed in to change notification settings

i582/wargaming-test-task

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

35 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Тестовое задание на позицию Programming intern в компании Wargaming Санкт-Петербург

Задание 1

Условие задания

На языке Python или С/С++, написать алгоритм (функцию) определения четности целого числа, который будет аналогичен нижеприведенному по функциональности, но отличен по своей сути.

Объяснить плюсы и минусы обеих реализаций.

Python example:

def isEven(value):
   return value % 2 == 0

C/C++ example:

bool isEven(int value)
{
   return value % 2 == 0;
}

С++:

​ 1. Исходный код: ссылка

Плюсы и минусы

Сравниваем варианты:

  1. bool isEven(int value)
    {
       return value % 2 == 0;
    }
  2. bool isEven2(int value)
    {
       return (value & 1) == 0;
    }

Давайте обратимся к результирующему коду на ассемблере для обеих функций, для начала используем GCC 9.3 на сайте godbolt:

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:

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 функции:

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                             ; isEven

tv66 = -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. _tail = _tail->next;
  2. _tail = next_index(_tail);
    
    ...
        
    size_t next_index(size_t ind) const
    {
        ++ind;
        return ind >= Size ? 0 : ind;
    }

Если рассматривать реализацию под микроскопом, то в 1 варианте, у нас присваивание и обращение к свойству структуры. Во втором варианте — префиксный инкремент + сравнение, в итоге получаем 2 операции.

Таким образом можно сказать, что вариант с списком более быстрый, а вариант с массивом занимает меньше места.

Код:

  1. Реализация через индексы на статическом массиве: ссылка;
  2. Реализация с помощью связных списков на статическом массиве: ссылка.

Updated (16:01 11.04.20):

  1. Реализация через индексы на динамическом массиве: ссылка;

Добавлена реализация через динамический массив с использованием индексов. Основным отличием от реализации на статическом массиве является возможность, используя метод resize, изменить размер очереди.

Я понял, что все что я описал выше несколько неверно, потому что относится только к реализациям на статическом массиве. Если мы рассматриваем очереди с изменяемым размером (тут точнее сказать с увеличивающимся размером, так как и в статическом массиве можно сделать изменение размера в меньшую сторону) то реализация с помощью списков будет лучше, потому что при каждом изменении массива не надо будет делать перемещение элементов, что делает сложность resize равной O(1), а не O(n), в случае массива. Но, однако же, в списке все еще требуется больше памяти.

Python:

  1. Реализация через индексы: ссылка;
  2. Реализация с помощью связных списков: ссылка.

Задание 3

Условие задания

На языке Python или С/С++, написать функцию, которая быстрее всего (по процессорным тикам) отсортирует данный ей массив чисел.

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

Объяснить почему вы считаете, что функция соответствует заданным критериям.

Выбрать один единственный метод из того множества сортировок, которые у нас есть невозможно. Каждый метод имеет свои плюсы и минусы. Если вспомнить первую приходящую на ум эффективную сортировку, а то есть быструю сортировку, то с ней не все так гладко она может уходить в n^2 в худшем случае. Также она имеет проблему с тем что она не стабильная, что может повлиять на скорость если сортируются комплексные объекты. Также у нее проблема, если мы сортируем связный список, а не массив. Например, сортировке слиянием в тоже время без разницы, список это или массив. Также плюсом этой сортировки является гарантированное n*logn время, а также возможность ее распараллеливания, например, в отличии от сортировкой кучей, которая также имеет потолок в n*logn. Но тут стоит вспомнить, что сортировка слиянием требует n памяти, в отличии от быстрой (память для рекурсии logn) и кучей (1). На маленьком количестве данных (около 20) не стоит забывать и о простых методах сортировки (вставками, выбора), так как на таком количестве n^2 не сильно отличается от n*logn. Конечно, надо также вспомнить и о такой сортировке, как Timsort, которая является сортировкой по-умолчанию в Python. Эта сортировка ведет себя почти на уровне быстрой, но, в тоже время, на частично упорядоченных массивах показывает неплохой прирост. В С++ std::sort не имеет стандарта реализации, так в GCC это быстрая с возможным переходом в кучу. Конечно, существует еще множество сортировок, но я хотел лишь привести к тому, что нельзя без информации о данных привести наилучший результат. Именно поэтому я не стану приводить реализации какого-либо алгоритма, но вполне cмогу привести, если будут известны входные данные.

Резюмируя, можно сказать: "Предоставьте мне точку опоры пример данных для сортировки и я переверну весь мир скажу какой алгоритм использовать" :D

About

💡 Тестовое задание на позицию Programming intern в компании Wargaming Санкт-Петербург

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published