Конкурс скрытых кодов: не очень быстрая сортировка [закрыто]


28

Задание

Напишите программу на языке по вашему выбору, которая читает строки ввода от стандартного ввода до EOF, а затем записывает их в стандартный вывод в ASCIIbetical порядке, аналогично программе sortкомандной строки. Короткий, не закулисный пример в Python:

import sys

for line in sorted(sys.stdin):
    print(line.rstrip('\n'))

Закулисная часть

Как и в OS War , ваша цель - доказать, что ваша любимая платформа «лучше», так как ваша программа намеренно работает намного медленнее на конкурирующей платформе. Ради этого конкурса «платформа» состоит из любой комбинации:

  • процессор
    • Архитектура (x86, Alpha, ARM, MIPS, PowerPC и т. Д.)
    • Битность (64-битные и 32-битные против 16-битных)
    • Биг - против байтов
  • Операционная система
    • Windows против Linux против Mac OS и т. Д.
    • Разные версии одной и той же операционной системы
  • Языковая реализация
    • Различные поставщики компиляторов / интерпретаторов (например, MSVC ++ против GCC)
    • Разные версии одного и того же компилятора / интерпретатора

Хотя вы могли бы удовлетворить требования, написав такой код:

#ifndef _WIN32
    Sleep(1000);
#endif

Такой ответ не должен быть одобрен. Цель состоит в том, чтобы быть тонким. В идеале ваш код должен выглядеть так, как будто он вообще не зависит от платформы. Если же есть какие - либо #ifdefзаявления (или условия , основанные на os.nameили System.Environment.OSVersionили любой другой ), они должны иметь правдоподобное обоснование (на основе лжи, конечно).

Включить в свой ответ

  • Код
  • Ваши «любимые» и «любимые» платформы.
  • Вход для тестирования вашей программы.
  • Время работы на каждой платформе для одного и того же ввода.
  • Описание того, почему программа работает так медленно на нежелательной платформе.

4
Это сложнее, чем я думал. Единственные ответы, которые я могу придумать, это либо очень длинные и немного очевидные, либо очень короткие и чрезвычайно очевидные :-(
брезгливое оссифраж

2
Я голосую за то, чтобы закрыть этот вопрос как не по теме, потому что скрытые проблемы больше не обсуждаются на этом сайте. meta.codegolf.stackexchange.com/a/8326/20469
кошка,

Ответы:


29

С

CleverSort

CleverSort - это современный (то есть чрезмерно спроектированный и неоптимальный) алгоритм двухэтапной сортировки строк.

На шаге 1 он начинается с предварительной сортировки входных строк с использованием сортировки по основанию и первых двух байтов каждой строки. Radix sort не сравнительный и очень хорошо работает со строками.

На шаге 2 он использует сортировку вставкой в предварительно отсортированном списке строк. Так как список почти отсортирован после шага 1, сортировка вставки довольно эффективна для этой задачи.

Код

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// Convert first two bytes of Nth line into integer

#define FIRSTSHORT(N) *((uint16_t *) input[N])

int main()
{
    char **input = 0, **output, *ptemp;
    int first_index[65536], i, j, lines = 0, occurrences[65536];
    size_t temp;

    // Read lines from STDIN

    while(1)
    {
        if(lines % 1000 == 0)
            input = realloc(input, 1000 * (lines / 1000 + 1) * sizeof(char*));

        if(getline(&input[lines], &temp, stdin) != -1)
            lines++;
        else
            break;
    }

    output = malloc(lines * sizeof(char*));

    // Radix sort

    memset(occurrences, 0, 65536 * sizeof(int));

    for(i = 0; i < lines; i++) occurrences[FIRSTSHORT(i)]++;

    first_index[0] = 0;

    for(i = 0; i < 65536 - 1; i++)
        first_index[i + 1] = first_index[i] + occurrences[i];

    memset(occurrences, 0, 65536 * sizeof(int));

    for(i = 0; i < lines; i++)
    {
        temp = FIRSTSHORT(i), output[first_index[temp] + occurrences[temp]++] = input[i];
    }

    // Insertion sort

    for(i = 1; i < lines; i++)
    {
        j = i;

        while(j > 0 && strcmp(output[j - 1], output[j]) > 0)
            ptemp = output[j - 1], output[j - 1] = output[j], output[j] = ptemp, j--;
    }

    // Write sorted lines to STDOUT

    for(i = 0; i < lines; i++)
        printf("%s", output[i]);
}

платформы

Мы все знаем, что машины с прямым порядком байтов намного эффективнее их аналогов с прямым порядком байтов. Для бенчмаркинга мы скомпилируем CleverSort с включенной оптимизацией и случайным образом создадим огромный список (чуть более 100 000 строк) из 4-байтовых строк:

$ gcc -o cleversort -Ofast cleversort.c
$ head -c 300000 /dev/zero | openssl enc -aes-256-cbc -k '' | base64 -w 4 > input
$ wc -l input
100011 input

Big-endian тест

$ time ./cleversort < input > /dev/null

real    0m0.185s
user    0m0.181s
sys     0m0.003s

Не слишком потрепанный.

Младшая последовательность

$ time ./cleversort < input > /dev/null

real    0m27.598s
user    0m27.559s
sys     0m0.003s

Бу, маленький Эндиан! Бу!

Описание

Сортировка вставками действительно довольно эффективна для почти отсортированных списков, но ужасно неэффективна для случайно отсортированных.

Подчеркнутой частью CleverSort является макрос FIRSTSHORT :

#define FIRSTSHORT(N) *((uint16_t *) input[N])

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

Естественно, это возможно и на машинах с прямым порядком байтов, но макрос должен был

#define FIRSTSHORT(N) (input[N][0] | (input[N][1] >> 8))

который работает, как и ожидалось, на всех платформах.

Вышеуказанный «эталонный порядок байтов» является результатом использования правильного макроса.

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


16

Python 2 против Python 3

Очевидно, что Python 3 на несколько порядков быстрее, чем Python 2. Давайте возьмем эту реализацию алгоритма Shellsort в качестве примера:

Код

import sys
from math import log

def shellsort(lst):

    ciura_sequence = [1, 4, 10, 23, 57, 132, 301, 701]  # best known gap sequence (Ciura, 2001)

    # check if we have to extend the sequence using the formula h_k = int(2.25h_k-1)
    max_sequence_element = 1/2*len(lst)
    if ciura_sequence[-1] <= max_sequence_element:
        n_additional_elements = int((log(max_sequence_element)-log(701)) / log(2.25))
        ciura_sequence += [int(701*2.25**k) for k in range(1,n_additional_elements+1)]
    else:
        # shorten the sequence if necessary
        while ciura_sequence[-1] >= max_sequence_element and len(ciura_sequence)>1:
            ciura_sequence.pop()

    # reverse the sequence so we start sorting using the largest gap
    ciura_sequence.reverse()

    # shellsort from http://sortvis.org/algorithms/shellsort.html
    for h in ciura_sequence:
        for j in range(h, len(lst)):
            i = j - h
            r = lst[j]
            flag = 0
            while i > -1:
                if r < lst[i]:
                    flag = 1
                    lst[i+h], lst[i] = lst[i], lst[i+h]
                    i -= h
                else:
                    break
            lst[i+h] = r

    return lst

# read from stdin, sort and print
input_list = [line.strip() for line in sys.stdin]
for line in shellsort(input_list):
    print(line)

assert(input_list==sorted(input_list))

эталонный тест

Подготовьте тестовый ввод. Это взято из ответа Дениса, но с меньшим количеством слов - Python 2 ооочень медленный ...

$ head -c 100000 /dev/zero | openssl enc -aes-256-cbc -k '' | base64 -w 4 > input

Python 2

$ time python2 underhanded2.py < input > /dev/null 

real    1m55.267s
user    1m55.020s
sys     0m0.284s

Python 3

$ time python3 underhanded2.py < input > /dev/null 

real    0m0.426s
user    0m0.420s
sys     0m0.006s

Где секретный код?

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

Хитрость заключается в целочисленном делении в расчете max_sequence_element. В Python 2 1/2вычисляется до нуля и, следовательно, выражение всегда равно нулю. Однако поведение оператора изменилось на деление с плавающей точкой в ​​Python 3. Поскольку эта переменная контролирует длину последовательности пробелов, которая является критическим параметром Shellsort, Python 2 заканчивается использованием последовательности, которая содержит только номер один, в то время как Python 3 использует правильную последовательность. Это приводит к квадратичному времени выполнения для Python 2.

Бонус 1:

Вы можете исправить код, просто добавив точку после 1или 2в расчете.

Бонус 2:

По крайней мере, на моей машине Python 2 работает немного быстрее, чем Python 3, когда работает фиксированный код ...


Отлично сработано! Время Nitpix: flagвыглядит только для записи, не могли бы вы удалить его? Кроме того, rкажется излишним, если вы делаете if lst[i+h] < lst[i]: .... С другой стороны, если вы продолжаете, rзачем делать своп? Не могли бы вы просто сделать lst[i+h] = lst[i]? Является ли все это намеренным отвлечением?
Йонас Кёлкер
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.