Cuộc thi mã ngầm: Sắp xếp không quá nhanh [đã đóng]


28

Nhiệm vụ

Viết chương trình, bằng ngôn ngữ bạn chọn, đọc các dòng đầu vào từ đầu vào tiêu chuẩn cho đến EOF, sau đó ghi chúng vào đầu ra tiêu chuẩn theo thứ tự ASCII, tương tự như sortchương trình dòng lệnh. Một ví dụ ngắn, không khéo léo trong Python là:

import sys

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

Phần lót tay

Tương tự như Cuộc chiến hệ điều hành , mục tiêu của bạn là chứng minh rằng nền tảng ưa thích của bạn là tốt hơn, bằng cách chương trình của bạn cố tình chạy chậm hơn nhiều trên nền tảng cạnh tranh. Vì lợi ích của cuộc thi này, một nền tảng của Google bao gồm bất kỳ sự kết hợp nào của:

  • Bộ xử lý
    • Kiến trúc (x86, Alpha, ARM, MIPS, PowerPC, v.v.)
    • Nhân chứng (64 bit so với 32 bit so với 16 bit)
    • Lớn so với ít endian
  • Hệ điều hành
    • Windows so với Linux so với Mac OS, v.v.
    • Các phiên bản khác nhau của cùng một hệ điều hành
  • Ngôn ngữ thực hiện
    • Các nhà cung cấp trình biên dịch / trình thông dịch khác nhau (ví dụ: MSVC ++ so với GCC)
    • Các phiên bản khác nhau của cùng một trình biên dịch / trình thông dịch

Mặc dù bạn có thể đáp ứng các yêu cầu bằng cách viết mã như:

#ifndef _WIN32
    Sleep(1000);
#endif

Một câu trả lời như vậy không nên được nâng cao. Mục tiêu là phải tinh tế. Lý tưởng nhất là mã của bạn trông giống như hoàn toàn không phụ thuộc vào nền tảng. Nếu bạn làm có bất kỳ #ifdefbáo cáo (hoặc điều kiện dựa trên os.namehoặc System.Environment.OSVersionhoặc bất kỳ), họ cần phải có một sự biện minh hợp lý (dựa trên một lời nói dối, tất nhiên).

Bao gồm trong câu trả lời của bạn

  • Mật mã
  • Nền tảng yêu thích của bạn và các nền tảng khác của bạn.
  • Một đầu vào để kiểm tra chương trình của bạn.
  • Thời gian chạy trên mỗi nền tảng, cho cùng một đầu vào.
  • Một mô tả về lý do tại sao chương trình chạy rất chậm trên nền tảng không thuận lợi.

4
Điều này khó hơn tôi nghĩ. Các câu trả lời duy nhất tôi có thể đưa ra là rất dài và hơi rõ ràng, hoặc rất ngắn và cực kỳ rõ ràng :-(
squossish ossifrage

2
Tôi đang bỏ phiếu để đóng câu hỏi này dưới dạng ngoài chủ đề vì các thử thách ngầm không còn thuộc chủ đề trên trang web này. meta.codegolf.stackexchange.com/a/8326/20469
mèo

Câu trả lời:


29

C

Thông minh

CleverSort là một thuật toán sắp xếp chuỗi hai bước tiên tiến (tức là được thiết kế quá mức và tối ưu hóa).

Trong bước 1, nó bắt đầu bằng cách sắp xếp trước các dòng đầu vào bằng cách sử dụng sắp xếp cơ số và hai byte đầu tiên của mỗi dòng. Radix sort là không so sánh và hoạt động rất tốt cho chuỗi.

Trong bước 2, nó sử dụng sắp xếp chèn vào danh sách các chuỗi được sắp xếp trước. Vì danh sách gần như được sắp xếp sau bước 1, sắp xếp chèn khá hiệu quả cho tác vụ này.

#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]);
}

Nền tảng

Chúng ta đều biết rằng các máy móc lớn về cuối có hiệu quả hơn nhiều so với các máy tính nhỏ của họ. Để đo điểm chuẩn, chúng tôi sẽ biên dịch CleverSort với tối ưu hóa được bật và tạo ngẫu nhiên một danh sách khổng lồ (chỉ hơn 100.000 chuỗi) các dòng 4 byte:

$ 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

Điểm chuẩn lớn

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

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

Không quá xấu.

Bechmark nhỏ

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

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

Boo, Endian bé nhỏ! Boo!

Sự miêu tả

Sắp xếp chèn thực sự khá hiệu quả đối với các danh sách gần như được sắp xếp, nhưng nó không hiệu quả khủng khiếp đối với các danh sách được sắp xếp ngẫu nhiên.

Phần ngầm của CleverSort là macro FIRSTSHORT :

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

Trên các máy lớn, việc đặt một chuỗi gồm hai số nguyên 8 bit theo từ vựng hoặc chuyển đổi chúng thành số nguyên 16 bit và đặt hàng chúng sau đó sẽ mang lại kết quả tương tự.

Đương nhiên, điều này cũng có thể xảy ra với các máy endian nhỏ, nhưng macro nên có

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

hoạt động như mong đợi trên tất cả các nền tảng.

"Điểm chuẩn lớn" ở trên thực sự là kết quả của việc sử dụng macro phù hợp.

Với macro sai và một máy cuối nhỏ, danh sách được sắp xếp trước theo ký tự thứ hai của mỗi dòng, dẫn đến một thứ tự ngẫu nhiên theo quan điểm từ điển. Sắp xếp chèn hành xử rất kém trong trường hợp này.


16

Python 2 so với Python 3

Rõ ràng, Python 3 nhanh hơn vài bậc so với Python 2. Hãy lấy thực hiện thuật toán Shellsort này làm ví dụ:

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

Điểm chuẩn

Chuẩn bị một đầu vào thử nghiệm. Điều này được lấy từ câu trả lời của Dennis nhưng với ít từ hơn - Python 2 rất chậm ...

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

Con trăn 2

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

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

Con trăn 3

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

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

Mã ngầm ở đâu?

Tôi cho rằng một số độc giả có thể muốn tự mình săn lùng kẻ lừa đảo, vì vậy tôi ẩn câu trả lời bằng thẻ spoiler.

Bí quyết là sự phân chia số nguyên trong tính toán của max_sequence_element. Trong Python 2 1/2ước tính bằng 0 và do đó biểu thức luôn bằng không. Tuy nhiên, hành vi của toán tử đã thay đổi thành phép chia dấu phẩy động trong Python 3. Vì biến này kiểm soát độ dài của chuỗi khoảng cách, là tham số quan trọng của Shellsort, Python 2 kết thúc bằng một chuỗi chỉ chứa số một trong khi Python 3 sử dụng đúng trình tự. Điều này dẫn đến thời gian chạy bậc hai cho Python 2.

Phần thưởng 1:

Bạn có thể sửa mã bằng cách thêm một dấu chấm sau 1hoặc 2trong phép tính.

Phần thưởng 2:

Ít nhất trên máy của tôi Python 2 nhanh hơn Python 3 một chút khi chạy mã cố định ...


Chơi tốt! Thời gian Nitpix: flagtrông chỉ ghi, bạn không thể xóa nó? Ngoài ra, rcó vẻ thừa nếu bạn làm if lst[i+h] < lst[i]: .... Mặt khác, nếu bạn giữ rtại sao phải trao đổi? Bạn không thể làm gì lst[i+h] = lst[i]? Có phải tất cả những điều này là một sự phân tâm có chủ ý?
Jonas Kölker
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.