Classificação aleatória cega

18

Aqui está um padrão bastante comum para ordenar algoritmos:

def sort(l):
    while not is_sorted(l):
         choose indices i, j
         assert i < j
         if l[i] > l[j]:
             l[i], l[j] = l[j], l[i]

Esses algoritmos funcionam bem porque os índices ie jsão escolhidos com cuidado, com base no estado da lista l.

No entanto, e se não pudéssemos ver l, e apenas tivéssemos que escolher cegamente? Quão rápido poderíamos classificar a lista então?


Seu desafio é escrever uma função que produza um par aleatório de índices, considerando apenas o comprimento de l. Especificamente, você deve gerar dois índices,, i, jcom 0 <= i < j < len(l). Sua função deve funcionar em qualquer comprimento da lista, mas será pontuada em uma lista de comprimento 100.

Sua pontuação é o número médio de opções de índice necessárias para classificar uma lista aleatoriamente aleatória e uniforme de acordo com o padrão acima, onde os índices são escolhidos de acordo com a sua função.

Eu pontuarei as submissões, considerando o número médio de opções de índice em mais de 1000 tentativas, em uma lista aleatoriamente aleatória e aleatória, de tamanho 100, sem entradas repetidas.

Reservo-me o direito de executar menos testes se a inscrição for claramente não competitiva ou não terminar, e executarei mais testes para diferenciar os principais concorrentes e encontrar um único vencedor. Se vários envios principais permanecerem dentro da margem de erro no limite de meus recursos computacionais, declararei o envio anterior como vencedor, até que outros recursos computacionais possam ser utilizados.


Aqui está um exemplo de programa de pontuação, em Python:

import random
def is_sorted(l):
    for x in range(len(l)-1):
        if l[x] > l[x+1]:
            return False
    return True

def score(length, index_chooser):
    steps = 0
    l = list(range(length))
    random.shuffle(l)

    while not is_sorted(l):
        i, j = index_chooser(length)
        assert (i < j)
        if l[i] > l[j]:
            l[i], l[j] = l[j], l[i]
        steps += 1
    return steps

Sua função pode não manter nenhum estado mutável, interagir com variáveis ​​globais, afetar a lista l, etc. A única entrada da sua função deve ser o comprimento da lista le deve gerar um par ordenado de números inteiros no intervalo [0, len(l)-1](ou apropriado para o idioma). indexação de lista). Fique à vontade para perguntar se algo é permitido nos comentários.

Os envios podem estar em qualquer idioma de uso gratuito. Inclua um equipamento de pontuação, se ainda não tiver sido publicado no seu idioma. Você pode postar uma pontuação provisória, mas vou deixar um comentário com a pontuação oficial.

A pontuação é o número médio de etapas para uma lista classificada em uma lista aleatoriamente aleatória e aleatória, de comprimento 100. Boa sorte.

isaacg
fonte
2
@JoKing Na verdade - a sua apresentação é uma distribuição
isaacg
2
Por que você não permite um estado mutável? Permitir isso significa que os envios podem ajustar melhor seus algoritmos, em vez de esperar que os itens certos sejam escolhidos.
Nathan Merrill
3
@NathanMerrill Se o estado mutável fosse permitido, o vencedor seria apenas uma rede de classificação que já é um problema bem estudado.
Anders Kaseorg
3
@ NathanMerrill Se você quiser postar essa pergunta, fique à vontade. Não é essa pergunta, no entanto.
Isaacg 17/1018
3
@NathanMerrill Oh, claro. O desafio "Projetar a melhor rede de classificação", embora seja uma questão interessante, tem sido muito estudado no mundo da pesquisa em CS. Como resultado, as melhores submissões provavelmente consistiriam apenas em implementações de trabalhos de pesquisa, como o tipo bitônico de Batcher. A pergunta que fiz aqui é original até onde eu sei e, portanto, deveria ter mais espaço para inovação.
Isaacg 17/10

Respostas:

10

Python, pontuação = 4508

def half_life_3(length):
    h = int(random.uniform(1, (length / 2) ** -3 ** -0.5) ** -3 ** 0.5)
    i = random.randrange(length - h)
    return i, i + h

Half-Life 3 confirmado.

Python, pontuação = 11009

def bubble(length):
    i = random.randrange(length - 1)
    return i, i + 1

Aparentemente, uma classificação aleatória de bolhas não é muito pior do que uma classificação normal de bolhas.

Distribuições ideais para pequenos comprimentos

Não há como isso estender para o comprimento 100, mas é interessante olhar de qualquer maneira. Calculei distribuições ótimas para casos pequenos (comprimento ≤ 7) usando descida gradiente e muita álgebra matricial. A coluna k mostra a probabilidade de cada troca na distância k .

length=1
score=0.0000

length=2
1.0000
score=0.5000

length=3
0.5000 0.0000
0.5000
score=2.8333

length=4
0.2957 0.0368 0.0000 
0.3351 0.0368 
0.2957 
score=7.5106

length=5
0.2019 0.0396 0.0000 0.0000 
0.2279 0.0613 0.0000 
0.2279 0.0396 
0.2019 
score=14.4544

length=6
0.1499 0.0362 0.0000 0.0000 0.0000 
0.1679 0.0558 0.0082 0.0000 
0.1721 0.0558 0.0000 
0.1679 0.0362 
0.1499 
score=23.4838

length=7
0.1168 0.0300 0.0041 0.0000 0.0000 0.0000 
0.1313 0.0443 0.0156 0.0000 0.0000 
0.1355 0.0450 0.0155 0.0000 
0.1355 0.0443 0.0041 
0.1313 0.0300 
0.1168 
score=34.4257
Anders Kaseorg
fonte
Sua pontuação: 11009
isaacg 17/10
2
Você pode explicar um pouco a sua meia-vida? O ponto é apenas desviar o número aleatório para a frente da lista?
Max
1
As distribuições ideais para comprimentos pequenos são muito interessantes - percebo que a inclinação em direção ao centro é útil, especialmente para distâncias maiores de troca.
Isaacg 17/1018
@ Max O problema todo é sobre influenciar os números aleatórios de maneiras úteis; dessa forma, foi útil. Observe que hé a distância entre os elementos trocados; não representa a frente nem as costas.
Anders Kaseorg 17/10
1
Sua pontuação de meia-vida: 4508 em 10000 amostras.
Isaacg 18/10/19
7

Pontuação: 4627

def rand_step(n):
	step_size = random.choice([1, 1, 4, 16])
	
	if step_size > n - 1:
		step_size = 1 
	
	start = random.randint(0, n - step_size - 1)
	return (start, start + step_size)

Experimente online!

Gera índices aleatórios cuja distância é escolhida uniformemente [1,1,4,16]. A idéia é ter uma mistura de swaps de uma etapa com swaps em escalas maiores.

Ajustei manualmente esses valores para listas de comprimento 100, e eles provavelmente estão longe de ser o ideal. Algumas pesquisas de máquinas provavelmente poderiam otimizar a distribuição por distâncias para a estratégia de par aleatório com distância escolhida.

xnor
fonte
1
Sua pontuação: 4627 em 10.000 amostras. Vou executá-lo novamente com mais amostras, se você estiver entre os líderes depois de alguns dias.
Isaacg 17/10/19
3

Pontuação: 28493

def x_and_y(l):
    x = random.choice(range(l))
    y = random.choice(range(l))
    while y == x and l != 1: y = random.choice(range(l))
    return sorted([x,y])

Experimente online!

Essa solução apenas seleciona valores distintos para xe yaleatoriamente do intervalo e os retorna na ordem de classificação. Tanto quanto eu posso dizer, isso tem um desempenho melhor do que escolher e xdepois escolher yentre os valores restantes.

Brincadeira
fonte
Sua pontuação: 28493
isaacg 17/10
3

Python, pontuação: 39525

def get_indices(l):
    x = random.choice(range(l-1))
    y = random.choice(range(x+1,l))
    return [x,y]

[0,l1)x
x[x+1,l)y

Experimente online.

Kevin Cruijssen
fonte
Sua pontuação: 39525
isaacg
2

Python, pontuação ≈ 5000

def exponentialDistance(n):
    epsilon = 0.25
    for dist in range(1, n):
        if random.random() < epsilon:
            break
    else:
        dist = 1
    low = random.randrange(0, n - dist)
    high = low + dist
    return low, high

Tentado com um monte de valores epsilon, 0,25 parece ser o melhor.

Pontuação ≈ 8881

def segmentedShuffle(n):
    segments = 20
    segmentLength = (n - 1) // segments + 1

    if random.random() < 0.75:
        a = b = 0
        while a == b or a >= n or b >= n:
            segment = random.randrange(segments)
            a = random.randrange(segmentLength) + segment * segmentLength
            b = random.randrange(segmentLength) + segment * segmentLength
        return sorted([a, b])

    highSegment = random.randrange(1, segments)
    return highSegment * segmentLength - 1, highSegment * segmentLength

Uma abordagem diferente. Não é tão bom e morre horrivelmente com o comprimento não divisível pelo número de segmentos, mas ainda assim divertido de construir.


fonte
Sua pontuação: Distância exponencial: 5055. Aleatório segmentado: 8901
isaacg
1

Pontuação: 4583

def rand_shell(l):
    steps = [1, 3, 5, 9, 17, 33, 65, 129]
    candidates = [(left, left + step)
            for (step, nstep) in zip(steps, steps[1:])
            for left in range(0, l - step)
            for i in range(nstep // step)
    ]
    return random.choice(candidates)

Experimente online!

Não faço ideia do porquê. Eu apenas tentei seqüências listadas na wikipedia artical para shellsort . E este parece funcionar melhor. Ele obtém pontuação semelhante à do xnor publicado .

tsh
fonte
Sua pontuação: 4583 em 10.000 amostras. Vou executá-lo novamente com mais amostras, se você estiver entre os líderes em alguns dias.
Isaacg 17/10/19
Além disso, estou executando um programa mais rápido que mostra a mesma distribuição, para que eu possa obter mais amostras.
Isaacg 17/1018
2
@isaacg Para melhorar o desempenho dos testes, candidatessair da função como variável global deve funcionar.
tsh
1
Obrigado, isso é muito mais rápido do que eu estava fazendo.
Isaacg 17/1018
1

Python 2 , 4871

import random
def index_chooser(length):
    e= random.choice([int(length/i) for i in range(4,length*3/4)])
    s =random.choice(range(length-e))
    return [s,s+e]
def score(length, index_chooser):
    steps = 0
    l = list(range(length))
    random.shuffle(l)
    while True:
        for x in range(length-1):
            if l[x] > l[x+1]:
                break
        else:
            return steps
        i, j = index_chooser(length)
        assert(i < j)
        if l[i] > l[j]:
            l[i], l[j] = l[j], l[i]
        steps += 1

print sum([score(100, index_chooser) for t in range(100)])

Experimente online!

l4m2
fonte
Sua pontuação: 4871 em 10000 amostras
isaacg 18/10/1918