C
クレバーソート
CleverSortは、最先端の(つまり、過剰に設計された、最適ではない)2ステップ文字列ソートアルゴリズムです。
ステップ1では、基数ソートと各行の最初の2バイトを使用して入力行を事前にソートすることから開始します。基数ソートは非比較的であり、文字列に対して非常にうまく機能します。
ステップ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をコンパイルし、4バイト行の巨大なリスト(100,000を超える文字列)をランダムに作成します。
$ 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
ビッグエンディアンのベンチマーク
$ 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])
ビッグエンディアンのマシンでは、2つの8ビット整数の文字列を辞書式に順序付けするか、16ビット整数に変換し、その後に順序付けしても同じ結果が得られます。
当然、これはリトルエンディアンのマシンでも可能ですが、マクロは
#define FIRSTSHORT(N) (input[N][0] | (input[N][1] >> 8))
すべてのプラットフォームで期待どおりに機能します。
上記の「ビッグエンディアンベンチマーク」は、実際には適切なマクロを使用した結果です。
間違ったマクロとリトルエンディアンのマシンでは、リストは各行の2番目の文字で事前にソートされ、辞書編集の観点からランダムな順序になります。この場合、挿入ソートの動作は非常に悪くなります。