С
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))
который работает, как и ожидалось, на всех платформах.
Вышеуказанный «эталонный порядок байтов» является результатом использования правильного макроса.
При неправильном макросе и порядке с прямым порядком байтов список предварительно сортируется по второму символу каждой строки, что приводит к случайному упорядочению с лексикографической точки зрения. Сортировка вставок в этом случае ведет себя очень плохо.