Eu estava ajudando alguém com seu código JavaScript e meus olhos foram atraídos por uma seção que se parecia com isso:
function randOrd(){
return (Math.round(Math.random())-0.5);
}
coords.sort(randOrd);
alert(coords);
Meu primeiro pensamento foi: ei, isso não pode funcionar! Mas então eu fiz algumas experiências e descobri que, de fato, pelo menos parece fornecer resultados bem aleatórios.
Depois, fiz uma pesquisa na web e, quase no topo, encontrei um artigo do qual esse código era mais copiado de maneira mais precisa. Parecia um site bastante respeitável e autor ...
Mas meu pressentimento me diz que isso deve estar errado. Especialmente porque o algoritmo de classificação não é especificado pelo padrão ECMA. Penso que diferentes algoritmos de classificação resultarão em diferentes embaralhamento não uniforme. Alguns algoritmos de classificação podem provavelmente até repetir infinitamente ...
Mas o que você acha?
E como outra pergunta ... como eu iria agora medir o quão aleatórios são os resultados dessa técnica de embaralhamento?
atualização: fiz algumas medições e publiquei os resultados abaixo como uma das respostas.
fonte
Respostas:
Nunca foi minha maneira favorita de embaralhar, em parte porque é específica da implementação, como você diz. Em particular, pareço lembrar que a biblioteca padrão de classificação de Java ou .NET (não tenho certeza qual) pode frequentemente detectar se você acaba com uma comparação inconsistente entre alguns elementos (por exemplo, você primeiro declara
A < B
eB < C
, mas depoisC < A
).Também acaba como um shuffle mais complexo (em termos de tempo de execução) do que você realmente precisa.
Eu prefiro o algoritmo de reprodução aleatória que efetivamente particiona a coleção em "aleatório" (no início da coleção, inicialmente vazio) e "não embaralhado" (o restante da coleção). Em cada etapa do algoritmo, escolha um elemento aleatório não embaralhado (que poderia ser o primeiro) e troque-o pelo primeiro elemento não embaralhado - depois trate-o como embaralhado (ou seja, mova mentalmente a partição para incluí-lo).
Este é O (n) e requer apenas chamadas n-1 para o gerador de números aleatórios, o que é bom. Também produz um embaralhamento genuíno - qualquer elemento tem 1 / n de chance de terminar em cada espaço, independentemente da sua posição original (assumindo um RNG razoável). A versão classificada aproxima- se de uma distribuição uniforme (supondo que o gerador de números aleatórios não escolha o mesmo valor duas vezes, o que é altamente improvável se estiver retornando dobras aleatórias), mas acho mais fácil argumentar sobre a versão aleatória :)
Essa abordagem é chamada de embaralhamento de Fisher-Yates .
Considero uma boa prática codificar esse shuffle uma vez e reutilizá-lo em todos os lugares que você precisar para embaralhar itens. Então você não precisa se preocupar com implementações de classificação em termos de confiabilidade ou complexidade. São apenas algumas linhas de código (que não tentarei em JavaScript!)
O artigo da Wikipedia sobre embaralhamento (e em particular a seção de algoritmos de embaralhamento) fala sobre a classificação de uma projeção aleatória - vale a pena ler a seção sobre implementações ruins de embaralhamento em geral, para que você saiba o que evitar.
fonte
2^x
estados para cada índice de matriz, ou seja, haverá 2 ^ (xn) de estados totais, que devem ser um pouco maiores que 2 ^ c - veja minha resposta editada para mais detalhesDepois que Jon já cobriu a teoria , aqui está uma implementação:
O algoritmo é
O(n)
, enquanto a classificação deve serO(n log n)
. Dependendo da sobrecarga de execução do código JS em comparação com asort()
função nativa , isso pode levar a uma diferença notável no desempenho, que deve aumentar com o tamanho da matriz.Nos comentários à resposta de bobobobo , afirmei que o algoritmo em questão pode não produzir probabilidades distribuídas uniformemente (dependendo da implementação de
sort()
).Meu argumento segue estas linhas: Um algoritmo de classificação requer um certo número
c
de comparações, por exemplo,c = n(n-1)/2
para Bubblesort. Nossa função de comparação aleatória torna o resultado de cada comparação igualmente provável, ou seja, há resultados2^c
igualmente prováveis . Agora, cada resultado deve corresponder a uma dasn!
permutações das entradas da matriz, o que impossibilita uma distribuição uniforme no caso geral. (Isso é uma simplificação, pois o número real de comparações necessárias depende da matriz de entrada, mas a asserção ainda deve ser mantida.)Como Jon apontou, isso por si só não é motivo para preferir o uso de Fisher-Yates
sort()
, pois o gerador de números aleatórios também mapeará um número finito de valores pseudo-aleatórios para asn!
permutações. Mas os resultados de Fisher-Yates ainda devem ser melhores:Math.random()
produz um número pseudo-aleatório no intervalo[0;1[
. Como o JS usa valores de ponto flutuante de precisão dupla, isso corresponde aos2^x
valores possíveis em que52 ≤ x ≤ 63
(estou com preguiça de encontrar o número real). Uma distribuição de probabilidade gerada usandoMath.random()
parará de se comportar bem se o número de eventos atômicos for da mesma ordem de magnitude.Ao usar Fisher-Yates, o parâmetro relevante é o tamanho da matriz, que nunca deve ser abordada
2^52
devido a limitações práticas.Ao classificar com uma função de comparação aleatória, a função basicamente se importa apenas se o valor de retorno for positivo ou negativo, portanto isso nunca será um problema. Mas existe uma similar: como a função de comparação é bem comportada, os
2^c
possíveis resultados são, como afirmado, igualmente prováveis. Sec ~ n log n
então ,2^c ~ n^(a·n)
ondea = const
, o que torna pelo menos possível que2^c
seja da mesma magnitude que (ou até menor que)n!
e, assim, leve a uma distribuição desigual, mesmo que o algoritmo de classificação seja mapeado uniformemente nas permutações. Se isso tem algum impacto prático está além de mim.O verdadeiro problema é que não é garantido que os algoritmos de classificação sejam mapeados uniformemente nas permutações. É fácil ver que o Mergesort faz o que é simétrico, mas raciocinar sobre algo como Bubblesort ou, mais importante, Quicksort ou Heapsort, não é.
Conclusão: Enquanto
sort()
usar o Mergesort, você deverá estar razoavelmente seguro, exceto nos casos de canto (pelo menos, espero que2^c ≤ n!
seja um caso de canto), se não, todas as apostas serão desativadas.fonte
Fiz algumas medições de quão aleatórios são os resultados desse tipo aleatório ...
Minha técnica era pegar uma pequena matriz [1,2,3,4] e criar todas (4! = 24) permutações dela. Então eu aplicaria a função de reprodução aleatória à matriz um grande número de vezes e contaria quantas vezes cada permutação é gerada. Um bom algoritmo de embaralhamento distribuiria os resultados de maneira uniforme em todas as permutações, enquanto um ruim não criaria esse resultado uniforme.
Usando o código abaixo, testei no Firefox, Opera, Chrome, IE6 / 7/8.
Surpreendentemente para mim, o tipo aleatório e o embaralhamento real criaram distribuições igualmente uniformes. Portanto, parece que (como muitos sugeriram) os principais navegadores estão usando a classificação por mesclagem. É claro que isso não significa que não pode haver um navegador por aí, isso é diferente, mas eu diria que significa que esse método de classificação aleatória é confiável o suficiente para ser usado na prática.EDIT: Este teste realmente não mediu corretamente a aleatoriedade ou a falta dela. Veja a outra resposta que eu postei.
Mas no lado da performance, a função shuffle dada por Cristoph foi uma clara vencedora. Mesmo para pequenas matrizes de quatro elementos, o embaralhamento real era duas vezes mais rápido que o aleatório!
fonte
Curiosamente, a Microsoft usou a mesma técnica em sua página de seleção aleatória do navegador.
Eles usaram uma função de comparação ligeiramente diferente:
Parece quase o mesmo para mim, mas acabou não sendo tão aleatório ...
Então, eu fiz alguns testes novamente com a mesma metodologia usada no artigo vinculado e, de fato - resultou que o método de classificação aleatória produziu resultados defeituosos. Novo código de teste aqui:
fonte
sort()
deve retornar um número maior que, menor que ou igual a zero, dependendo da comparação dea
eb
. ( developer.mozilla.org/pt-BR/docs/Web/JavaScript/Reference/… )Coloquei uma página de teste simples no meu site, mostrando o viés do seu navegador atual em relação a outros navegadores populares, usando métodos diferentes para embaralhar. Ele mostra o terrível viés de apenas usar
Math.random()-0.5
, outro shuffle 'aleatório' que não é tendencioso e o método de Fisher-Yates mencionado acima.Você pode ver que em alguns navegadores há uma chance de 50% de que certos elementos não mudem de lugar durante o 'shuffle'!
Nota: você pode tornar a implementação do shuffle Fisher-Yates por @Christoph um pouco mais rápida para o Safari alterando o código para:
Resultados do teste: http://jsperf.com/optimized-fisher-yates
fonte
Eu acho que é bom para casos em que você não é exigente quanto à distribuição e deseja que o código-fonte seja pequeno.
Em JavaScript (onde a fonte é transmitida constantemente), pequeno faz a diferença nos custos de largura de banda.
fonte
arr = arr.map(function(n){return [Math.random(),n]}).sort().map(function(n){return n[1]});
, o que tem a vantagem de não demorar muito demais e ser realmente distribuído adequadamente. Também existem variantes de shuffle Knuth / FY muito compactadas.arr = arr.map(function(n){return [Math.random(),n];}).sort().map(function(n){return n[1];});
.É um truque, certamente. Na prática, um algoritmo de loop infinito não é provável. Se você estiver classificando objetos, poderá percorrer a matriz de cordas e fazer algo como:
(e depois faça um loop neles novamente para remover o sortValue)
Ainda é um truque. Se você quiser fazê-lo bem, terá que fazê-lo da maneira mais difícil :)
fonte
Faz quatro anos, mas eu gostaria de salientar que o método comparador aleatório não será distribuído corretamente, independentemente do algoritmo de classificação que você usar.
Prova:
n
elementos, existem exatamenten!
permutações (ou seja, possíveis embaralhamento).Os únicos tamanhos que poderiam ser corretamente distribuídos são n = 0,1,2.
Como exercício, tente desenhar a árvore de decisão de diferentes algoritmos de classificação para n = 3.
Há uma lacuna na prova: se um algoritmo de classificação depender da consistência do comparador e tiver um tempo de execução ilimitado com um comparador inconsistente, ele poderá ter uma soma infinita de probabilidades, que poderá adicionar até 1/6, mesmo que todo denominador na soma é uma potência de 2. Tente encontrar um.
Além disso, se um comparador tiver uma chance fixa de fornecer uma das respostas (por exemplo
(Math.random() < P)*2 - 1
, para constanteP
), a prova acima será válida. Se o comparador alterar suas chances com base nas respostas anteriores, talvez seja possível gerar resultados justos. Encontrar um comparador para um dado algoritmo de classificação pode ser um trabalho de pesquisa.fonte
Se você estiver usando o D3, há uma função de reprodução aleatória incorporada (usando Fisher-Yates):
E aqui está Mike entrando em detalhes sobre isso:
http://bost.ocks.org/mike/shuffle/
fonte
Aqui está uma abordagem que usa uma única matriz:
A lógica básica é:
Código:
fonte
Você pode usar a
Array.sort()
função para embaralhar uma matriz - Sim.Os resultados são aleatórios o suficiente - Não.
Considere o seguinte snippet de código:
Saída de amostra:
Idealmente, as contagens devem ser distribuídas uniformemente (para o exemplo acima, todas as contagens devem estar em torno de 20). Mas eles não são. Aparentemente, a distribuição depende de qual algoritmo de classificação é implementado pelo navegador e de como itera os itens da matriz para classificação.
Mais informações são fornecidas neste artigo:
Array.sort () não deve ser usado para embaralhar uma matriz
fonte
Não há nada de errado com isso.
A função que você passa para .sort () geralmente se parece com
Seu trabalho no sortingFunc é retornar:
A função de classificação acima coloca as coisas em ordem.
Se você retornar + e + aleatoriamente como você tem, receberá uma ordem aleatória.
Como no MySQL:
fonte
shuffle()
só tem de ser escrito uma vez, por isso não é realmente um problema: basta colocar o trecho em seu cofre código e desenterrá-la sempre que precisar