언더 핸드 코드 컨테스트 : 간단하지 않은 정렬 [닫기]


28

작업

선택한 언어로 EOF까지 표준 입력에서 입력 라인을 읽은 다음 sort명령 행 프로그램 과 유사하게 ASCII 출력 순서로 표준 출력에 기록하는 프로그램을 작성하십시오. 파이썬에서 짧고 비언어적 인 예는 다음과 같습니다.

import sys

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

언더 핸드 파트

OS 전쟁 과 유사하게 , 귀하의 목표는 귀하의 프로그램이 경쟁 플랫폼에서 의도적으로 훨씬 더 느리게 실행되도록하여 선호하는 플랫폼이“더 나은”것으로 입증하는 것입니다. 이 콘테스트를 위해 "플랫폼"은 다음과 같은 조합으로 구성됩니다.

  • 프로세서
    • 아키텍처 (x86, Alpha, ARM, MIPS, PowerPC 등)
    • 비트 (64 비트 vs. 32 비트 vs. 16 비트)
    • 빅 엔드와 리틀 엔디안
  • 운영 체제
    • Windows vs. Linux vs. Mac OS 등
    • 동일한 운영 체제의 다른 버전
  • 언어 구현
    • 다른 컴파일러 / 통역 공급 업체 (예 : MSVC ++ 및 GCC)
    • 동일한 컴파일러 / 인터프리터의 다른 버전

다음과 같은 코드를 작성하여 요구 사항을 충족시킬 수 있지만 :

#ifndef _WIN32
    Sleep(1000);
#endif

그러한 대답은 공언해서는 안된다. 목표는 미묘합니다. 이상적으로 코드는 플랫폼에 의존하지 않는 것처럼 보일 것입니다. 당신이 경우 않는 어떤이 #ifdef(에 기반 또는 조건 문 os.name이나 System.Environment.OSVersion또는 무엇이든), 그들은 (물론 거짓말, 기준) 그럴듯한 명분이 있어야합니다.

답변에 포함

  • 코드
  • "좋아하는"플랫폼과 "좋지 않은"플랫폼
  • 프로그램을 테스트하기위한 입력.
  • 동일한 입력에 대해 각 플랫폼에서 실행 시간입니다.
  • 바람직하지 않은 플랫폼에서 프로그램이 느리게 실행되는 이유에 대한 설명 .

4
이것은 내가 생각했던 것보다 어렵다. 내가 올 수있는 유일한 대답은 매우 길고 약간 분명하거나 매우 짧고 매우 분명합니다 :-(
squeamish ossifrage

2
나는이 문제를 주제로 다루지 않기로 결심했다. 왜냐하면이 사이트에서 미숙 한 도전은 더 이상 주제가 아니기 때문이다. meta.codegolf.stackexchange.com/a/8326/20469
cat

답변:


29

기음

영리한

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 바이트 라인의 10 만 개 이상의 문자열을 무작위로 생성합니다.

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

빅 엔디안 시스템에서는 사전 식으로 두 개의 8 비트 정수 문자열을 주문하거나 16 비트 정수로 변환 한 후 나중에 정렬하면 동일한 결과가 나타납니다.

당연히, 이것은 리틀 엔디안 머신에서도 가능하지만 매크로는

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

모든 플랫폼에서 예상대로 작동합니다.

위의 "빅 엔디안 벤치 마크"는 실제로 적절한 매크로를 사용한 결과입니다.

잘못된 매크로와 리틀 엔디안 기계를 사용하면 목록이 모든 줄 의 두 번째 문자로 미리 정렬되어 사전 사전 관점에서 임의 순서로 정렬됩니다. 이 경우 삽입 정렬이 매우 열악합니다.


16

파이썬 2 대 파이썬 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 답변에서 가져 왔지만 더 적은 단어로 파이썬 2가 너무 느립니다 ...

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

파이썬 2

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

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

파이썬 3

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

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

언더 코드 코드는 어디에 있습니까?

일부 독자는 속임수 자체를 사냥하고 싶을 수 있으므로 스포일러 태그로 답변을 숨 깁니다.

트릭은의 계산에서 정수 나누기입니다 max_sequence_element. Python 2에서는 1/20으로 평가되므로 표현식은 항상 0입니다. 그러나 연산자 3의 동작은 Python 3에서 부동 소수점 나누기로 변경되었습니다.이 변수는 Shellsort의 중요한 매개 변수 인 갭 시퀀스의 길이를 제어하므로 Python 2는 1을 포함하는 시퀀스를 사용합니다. 3은 올바른 순서를 사용합니다. 결과적으로 Python 2의 2 차 실행 시간이 발생합니다.

보너스 1 :

계산에서 1또는 뒤에 점을 추가하여 코드를 수정할 수 있습니다 2.

보너스 2 :

적어도 내 컴퓨터에서 Python 2는 고정 코드를 실행할 때 Python 3보다 약간 빠릅니다.


잘했다! Nitpix 시간 : flag쓰기 전용으로 보입니다 . 제거 할 수 없습니까? 또한 r할 경우 불필요한 것으로 보입니다 if lst[i+h] < lst[i]: .... 반면에, r왜 당신이 스왑 을 유지 합니까? 당신은 그냥 할 수 lst[i+h] = lst[i]없습니까? 이 모든 것이 의도적 인 산만합니까?
Jonas Kölker
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.