不熟练的代码竞赛:排序不太快[关闭]


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.nameSystem.Environment.OSVersion或其他),他们应该有一个合理的理由(基于谎言,当然)。

包括在您的答案中

  • 代码
  • 您的“收藏夹”和“收藏夹”平台。
  • 用于测试程序的输入。
  • 对于相同的输入,每个平台上的运行时间。
  • 关于程序为何在最不喜欢的平台上运行如此缓慢的描述。

4
这比我想的要难。我能想出的唯一答案要么很长而且很明显,要么很短而且非常明显:-(
sosamrage ossifrage

2
我投票结束这个问题是不合时宜的,因为在这个网站上,不熟练的挑战已不再是正题。meta.codegolf.stackexchange.com/a/8326/20469

Answers:


29

C

聪明排序

CleverSort是一种先进的(即,过度设计和次优)两步字符串排序算法。

在步骤1中,首先使用基数排序和每行的前两个字节对输入行进行预排序。基数排序是非比较的,对字符串非常有效。

在步骤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])

在big-endian机器上,按字典顺序对两个8位整数的字符串进行排序或将其转换为16位整数,然后对它们进行排序将产生相同的结果。

当然,这在小字节序机器上也是可能的,但是宏应该已经

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

可以在所有平台上正常工作。

上面的“ big-endian基准”实际上是使用适当宏的结果。

如果使用了错误的宏和低位字节序的机器,则该列表将按每行的第二个字符进行预排序,从而从词典编排的角度进行随机排序。在这种情况下,插入排序的行为非常差。


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))

基准测试

准备测试输入。这取自Dennis的答案,但用的单词少-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]吗?所有这一切都是故意的吗?
JonasKölker2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.