do
CleverSort
CleverSort to najnowocześniejszy (tj. Nadmiernie skonstruowany i nieoptymalny) dwustopniowy algorytm sortowania ciągów.
W kroku 1 rozpoczyna się od wstępnego sortowania linii wejściowych za pomocą sortowania radix i pierwszych dwóch bajtów każdej linii. Sortowanie Radix jest nieporównywalne i działa bardzo dobrze dla łańcuchów.
W kroku 2 używa sortowania wstawiania na wstępnie posortowanej liście ciągów. Ponieważ lista jest prawie posortowana po kroku 1, sortowanie wstawiania jest dość wydajne w przypadku tego zadania.
Kod
#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]);
}
Platformy
Wszyscy wiemy, że maszyny typu big-endian są znacznie wydajniejsze niż ich odpowiedniki typu little-endian. Do testów porównawczych skompilujemy CleverSort z włączonymi optymalizacjami i losowo utworzymy ogromną listę (nieco ponad 100 000 ciągów) 4-bajtowych linii:
$ 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
Benchmark Big-Endian
$ time ./cleversort < input > /dev/null
real 0m0.185s
user 0m0.181s
sys 0m0.003s
Niezbyt brudny.
Znak Little-Endian
$ time ./cleversort < input > /dev/null
real 0m27.598s
user 0m27.559s
sys 0m0.003s
Boo, mały Endian! Gwizd!
Opis
Sortowanie za pomocą wstawiania jest naprawdę dość wydajne w przypadku prawie posortowanych list, ale jest okropnie nieskuteczne w przypadku losowo sortowanych list.
Podstępną częścią CleverSort jest makro FIRSTSHORT :
#define FIRSTSHORT(N) *((uint16_t *) input[N])
Na maszynach big-endian uporządkowanie leksykograficzne ciągu dwóch 8-bitowych liczb całkowitych lub ich konwersja na 16-bitowe liczby całkowite i uporządkowanie ich później daje takie same wyniki.
Oczywiście jest to możliwe również na maszynach z małym endianem, ale makro powinno być
#define FIRSTSHORT(N) (input[N][0] | (input[N][1] >> 8))
który działa zgodnie z oczekiwaniami na wszystkich platformach.
Powyższy „test porównawczy big-endian” jest w rzeczywistości wynikiem użycia odpowiedniego makra.
Przy złym makrze i maszynie typu endian lista jest wstępnie sortowana według drugiego znaku każdej linii, co powoduje losowe uporządkowanie z leksykograficznego punktu widzenia. W tym przypadku sortowanie wstawiane zachowuje się bardzo słabo.