Concurso de código secreto: classificação não tão rápida [fechada]

28

A tarefa

Escreva um programa, no idioma de sua escolha, que leia as linhas de entrada da entrada padrão até o EOF e, em seguida, grave-as na saída padrão na ordem ASCIIbetics, semelhante ao sortprograma de linha de comando. Um exemplo curto e não dissimulado em Python é:

import sys

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

A parte secreta

Semelhante ao The OS War , seu objetivo é provar que sua plataforma preferida é "melhor", fazendo com que seu programa seja executado deliberadamente muito mais lentamente em uma plataforma concorrente. Para o efeito deste concurso, uma "plataforma" consiste em qualquer combinação de:

  • Processador
    • Arquitetura (x86, Alpha, ARM, MIPS, PowerPC, etc.)
    • Bitness (64 bits vs. 32 bits vs. 16 bits)
    • Big-end-little-endian
  • Sistema operacional
    • Windows vs. Linux vs. Mac OS, etc.
    • Versões diferentes do mesmo sistema operacional
  • Implementação de linguagem
    • Fornecedores diferentes de compilador / intérprete (por exemplo, MSVC ++ vs. GCC)
    • Versões diferentes do mesmo compilador / intérprete

Embora você possa atender aos requisitos escrevendo código como:

#ifndef _WIN32
    Sleep(1000);
#endif

Essa resposta não deve ser votada. O objetivo é ser sutil. Idealmente, seu código deve parecer que não depende da plataforma. Se você não tem nenhum #ifdefdeclarações (ou condições baseadas em os.nameou System.Environment.OSVersionou qualquer outro), eles devem ter uma justificação plausível (baseada em uma mentira, é claro).

Incluir na sua resposta

  • O código
  • Suas plataformas "favoritas" e "desfavorecidas".
  • Uma entrada com a qual testar seu programa.
  • O tempo de execução em cada plataforma, para a mesma entrada.
  • Uma descrição do motivo pelo qual o programa é executado tão lentamente na plataforma desfavorável.
dan04
fonte
4
Isso é mais difícil do que eu pensava. As únicas respostas que podem surgir com ou são muito longos e um pouco óbvio, ou muito curta e extremamente óbvio :-(
ossifrage escrúpulos
2
Estou votando para encerrar esta questão como fora de tópico, porque os desafios secretos não estão mais no tópico neste site. meta.codegolf.stackexchange.com/a/8326/20469
cat

Respostas:

29

C

CleverSort

O CleverSort é um algoritmo de classificação de duas etapas de última geração (exageradamente manipulado e subótimo).

Na etapa 1, ele começa pré-classificando as linhas de entrada usando classificação de raiz e os dois primeiros bytes de cada linha. A classificação Radix não é comparativa e funciona muito bem para strings.

Na etapa 2, ele usa classificação de inserção na lista pré-classificada de seqüências de caracteres. Como a lista está quase classificada após a etapa 1, a classificação por inserção é bastante eficiente para esta tarefa.

Código

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

Plataformas

Todos sabemos que as máquinas big-endian são muito mais eficientes do que suas contrapartes little-endian. Para comparação, compilaremos o CleverSort com otimizações ativadas e criaremos aleatoriamente uma enorme lista (pouco mais de 100.000 strings) de linhas de 4 bytes:

$ 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

Referência big-endian

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

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

Não é muito pobre.

Bechmark little-endian

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

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

Boo, pequeno Endian! Vaia!

Descrição

A classificação por inserção é realmente bastante eficiente para listas quase ordenadas, mas é terrivelmente ineficiente para listas ordenadas aleatoriamente.

A parte secreta do CleverSort é a macro FIRSTSHORT :

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

Em máquinas big endian, ordenar uma sequência de dois números inteiros de 8 bits lexicograficamente ou convertê-los em números inteiros de 16 bits e ordená-los posteriormente produz os mesmos resultados.

Naturalmente, isso também é possível em máquinas little-endian, mas a macro deveria ter sido

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

que funciona como esperado em todas as plataformas.

O "benchmark big endian" acima é realmente o resultado do uso da macro apropriada.

Com a macro errada e uma máquina little-endian, a lista é pré-classificada pelo segundo caractere de cada linha, resultando em uma ordem aleatória do ponto de vista lexicográfico. O tipo de inserção se comporta muito mal nesse caso.

Dennis
fonte
16

Python 2 x Python 3

Obviamente, o Python 3 é várias ordens de magnitude mais rápido que o Python 2. Vamos tomar esta implementação do algoritmo Shellsort como um exemplo:

Código

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

Referência

Prepare uma entrada de teste. Isso é retirado da resposta de Dennis, mas com menos palavras - o Python 2 é muito lento ...

$ 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

Onde está o código secreto?

Suponho que alguns leitores possam querer caçar o malandro, então escondo a resposta com uma etiqueta de spoiler.

O truque é a divisão inteira no cálculo da max_sequence_element. No Python 2 1/2, o valor é zero e, portanto, a expressão é sempre zero. No entanto, o comportamento do operador mudou para a divisão de ponto flutuante no Python 3. Como essa variável controla o comprimento da sequência de gap, que é um parâmetro crítico do Shellsort, o Python 2 acaba usando uma sequência que contém apenas o número um enquanto o Python 3 usa a sequência correta. Isso resulta em um tempo de execução quadrático para o Python 2.

Bônus 1:

Você pode corrigir o código simplesmente adicionando um ponto após o 1ou 2no cálculo.

Bônus 2:

Pelo menos na minha máquina, o Python 2 é um pouco mais rápido que o Python 3 ao executar o código fixo ...

René
fonte
Bem jogado! Nitpix time: flagparece somente para gravação, não foi possível removê-lo? Além disso, rparece supérfluo se você o fizer if lst[i+h] < lst[i]: .... Por outro lado, se você continuar, rpor que fazer a troca? Você não poderia simplesmente fazer lst[i+h] = lst[i]? Tudo isso é uma distração intencional?
Jonas Kölker