Le cas échéant, quel est le problème avec ce brassage de l'algorithme et comment puis-je savoir?
Comme arrière-plan, je suis conscient de la De Fisher-Yates parfait shuffle. C'est un grand shuffle avec ses O(n) la complexité et de la garantie de l'homogénéité et je serais un idiot de ne pas l'utiliser ... dans un environnement qui permet en place des mises à jour de tableaux (dans la plupart, si pas tous, impératif environnements de programmation).
Malheureusement, la programmation fonctionnelle du monde ne vous donne pas accès à mutable état.
En raison de Fisher-Yates, cependant, il n'y a pas beaucoup de littérature que je peux trouver sur la façon de concevoir un réarrangement de l'algorithme. Quelques lieux qui adresse à tous les faire brièvement avant de dire, en effet, "alors, voici de Fisher-Yates qui est le brassage que vous devez savoir". J'ai dû, en fin de compte, de venir avec ma propre solution.
La solution je suis venu avec les œuvres de ce genre de lecture aléatoire de toutes les listes de données:
- Si la liste est vide, retourner l'ensemble vide.
- Si la liste contient un seul élément, le retour de ce seul élément.
- Si la liste est non-vide, de la partition de la liste avec un générateur de nombre aléatoire et d'appliquer l'algorithme récursivement à chaque partition, en rassemblant les résultats.
En Erlang code il ressemble à quelque chose comme ceci:
shuffle([]) -> [];
shuffle([L]) -> [L];
shuffle(L) ->
{Left, Right} = lists:partition(fun(_) ->
random:uniform() < 0.5
end, L),
shuffle(Left) ++ shuffle(Right).
(Si cela ressemble à un dérangé tri rapide pour vous, eh bien, qu'est ce que c'est, en gros.)
Alors, voici mon problème: la même situation qui rend la recherche de brassage des algorithmes qui ne sont pas de Fisher-Yates difficile de trouver des outils pour analyser un réarrangement de l'algorithme tout aussi difficile. Il y a beaucoup de littérature que je peux trouver sur l'analyse de PRNGs pour l'uniformité, de la périodicité, etc. mais pas beaucoup d'informations là-bas sur la façon d'analyser un shuffle. (En effet, certaines des informations que je trouve sur l'analyse de mélange est tout simplement faux -- facilement trompés par de simples techniques.)
Donc ma question est: comment dois-je analyser mon brassage de l'algorithme (en supposant que le random:uniform()
appeler, il est à la tâche de générer winrar nombres aléatoires avec de bonnes caractéristiques)? Ce mathématiques sont les outils à ma disposition pour juger si oui ou non, de dire, de 100 000 passages de la shuffler sur une liste d'entiers allant de 1..100 m'a donné, de manière plausible, bon brassage des résultats? J'ai fait quelques tests de mon propre (en comparant les augmentations diminutions dans le mélange, par exemple), mais j'aimerais savoir un peu plus.
Et si il n'y a aucune indication dans la lecture aléatoire de l'algorithme lui-même qui serait apprécié aussi.
- Les réponses à cette question peut aider: stackoverflow.com/questions/1685339/... Elle avait également être la peine de prendre un coup d'oeil à Knuth l'analyse de Fisher-Yates (voir l'article de wikipédia vous lié pour une citation).
- Je recommande que vous prenez ce MathOverflow. Inductive preuve qu'il fonctionne comme prévu semble faire bouillir le calcul de la somme d'une ligne. (Mais je suis à peu près certain que c'est correct mais pas garanti arrêter à un moment donné).
- doublep > je pense aussi que cet algorithme fonctionne. Voir mon post pour une explication détaillée.
- J'ai pensé infini ralentissement a été considéré comme très mauvais, dans les algorithmes de tri? Aussi, ne serait pas
lists:split
,lists:droplast
etlists:append
la mise en œuvre de l'algorithme trivial?
Vous devez vous connecter pour publier un commentaire.
Remarque générale
Mon approche personnelle au sujet de l'exactitude de la probabilité à l'aide d'algorithmes: si vous savez comment prouver c'est correct, alors il est probablement correct; si vous ne le faites pas, il est certainement erroné.
Autrement dit, il est généralement inutile d'essayer d'analyser chaque algorithme que vous pourriez en venir à: vous avez à garder à la recherche d'un algorithme jusqu'à ce que vous trouviez celui que vous peut prouver corriger.
L'analyse d'un algorithme aléatoire par le calcul de la distribution
Je connais un moyen de "automatiquement" analyser un shuffle (ou plus généralement un hasard-à l'aide de l'algorithme) qui est plus forte que la simple "jeter beaucoup de tests et de vérifier l'uniformité". Vous pouvez mécaniquement calculer la distribution associée à chaque entrée de votre algorithme.
L'idée générale est qu'un hasard-à l'aide de l'algorithme explore une partie d'un monde de possibilités. Chaque fois que votre algorithme qui demande un élément aléatoire dans un ensemble ({
true
,false
} lors de la rotation d'une pièce de monnaie), il y a deux résultats possibles pour votre algorithme, et l'un d'eux est choisi. Vous pouvez changer votre algorithme de sorte que, au lieu de retourner un des résultats possibles, il explore tous solutions en parallèle et renvoie tous les résultats possibles avec les distributions.En général, qui aurait besoin d'une réécriture de son algorithme en profondeur. Si votre langue prend en charge délimité par des continuations, vous n'avez pas à vous; vous pouvez mettre en œuvre "une exploration de tous les résultats possibles" à l'intérieur de la fonction demandant un élément aléatoire (l'idée est que le générateur aléatoire, au lieu de retourner un résultat, la capture de la poursuite associé à votre programme et l'exécuter avec tous les différents résultats). Pour un exemple de cette approche, voir oleg est HANSEI.
Un intermédiaire, et probablement moins arcanes, la solution est de représenter ce "monde de possibles" comme une monade, et d'utiliser un langage comme Haskell avec des installations pour monadique de programmation. Voici un exemple d'implémentation d'une variant1 de votre algorithme, en Haskell, à l'aide de la probabilité monade de l' probabilité package :
Vous pouvez l'exécuter pour une entrée donnée, et obtenir la sortie de la distribution :
Vous pouvez voir que cet algorithme est uniforme avec des entrées de taille 2, mais non uniforme sur les entrées de taille 3.
La différence avec le test est une approche que nous pouvons avoir de certitude absolue en un nombre fini d'étapes : il peut être assez grande, comme il s'élève à une exploration exhaustive de l'univers de possibles, mais généralement plus petit que 2^N, comme il y a des factorisations des résultats similaires), mais si elle renvoie une distribution non uniforme que nous savons pour sûr que l'algorithme est faux. Bien sûr, si elle retourne une distribution uniforme pour
[1..N]
et1 <= N <= 100
, vous ne connaissez que votre algorithme est uniforme jusqu'à des listes de taille 100; il peut encore être mauvais.1: cet algorithme est une variante de votre Erlang est mise en œuvre, en raison de la spécificité de pivot de la manipulation. Si je n'utilise pas de pivot, comme dans votre cas, la taille de saisie n'est pas diminuer à chaque étape de plus : l'algorithme considère également le cas lorsque toutes les entrées sont dans la liste de gauche (ou à droite de la liste), et de se perdre dans une boucle infinie. C'est une faiblesse de la probabilité de l'errance de mise en œuvre (si un algorithme a une probabilité de 0 de non-résiliation, la répartition de calcul peut toujours s'écarter), que je ne sais pas encore comment les corriger.
De tri mélange
Ici est un algorithme simple qui j'ai confiance, j'ai pu prouver correct:
Vous pouvez omettre l'étape 2 si vous connaissez la probabilité de collision (deux nombres aléatoires cueillis sont égaux) est suffisamment faible, mais, sans elle, l'aléatoire n'est pas parfaitement uniforme.
Si vous chercher vos clés dans [1..N], où N est la longueur de votre collection, vous aurez beaucoup de collisions (Problème d'anniversaire). Si vous choisissez votre clé comme un entier de 32 bits, la probabilité d'un conflit est faible dans la pratique, mais toujours sous réserve de l'anniversaire problème.
Si vous utilisez l'infini (paresseusement évalué) des chaînes de bits que les clés, plutôt que de longueur finie clés, la probabilité de collision devient 0, et la vérification de la distinction n'est plus nécessaire.
Ici est un shuffle mise en œuvre en OCaml, à l'aide de paresseux nombres réels comme infini des chaînes de bits:
Il y a d'autres approches de la "pure brassage". Une belle est apfelmus de mergesort solution basée sur.
Algorithmique considérations: la complexité de l'algorithme précédent dépend de la probabilité que toutes les clés sont distinctes. Si vous choisissez comme des nombres entiers de 32 bits, vous avez une ~4 milliards de probabilité qu'une touche particulière entre en collision avec une autre clé. Tri par ces touches est O(n log n), à condition de choisir un nombre aléatoire est O(1).
Si tu infini des chaînes de bits, vous ne jamais avoir à redémarrer la cueillette, mais la complexité est alors liée à "la façon dont beaucoup d'éléments de la les flux sont évalués sur la base de la moyenne". Je conjecture qu'il est O(log n) en moyenne (donc toujours en O(n log n) au total), mais n'ont aucune preuve.
... et je pense que votre algorithme fonctionne
Après plus de réflexion, je pense (comme douplep), que votre mise en œuvre est correcte. Ici c'est une simple explication.
Chaque élément de votre liste est testé par plusieurs
random:uniform() < 0.5
tests. À un élément, vous pouvez associer la liste des résultats de ces tests, comme une liste de booléens ou {0
,1
}. Au début de l'algorithme, vous ne connaissez pas la liste associée à l'une de ces nombre. Après la premièrepartition
appel, vous savez que le premier élément de chaque liste, etc. Lors de votre algorithme retourne la liste des tests sont parfaitement connues et les éléments sont triés selon ces listes triées dans l'ordre lexicographique, ou considérées comme des représentations binaires des nombres réels).Donc, votre algorithme est équivalent à trier par infini bitstring clés. L'action de partitionnement de la liste, qui rappelle de quicksort de la partition sur un pivot de l'élément, est en fait une manière de séparer, pour une position donnée dans la bitstring, les éléments d'évaluation
0
à partir des éléments d'évaluation1
.Le tri est uniforme, parce que les chaînes de bits sont tous différents. En effet, les deux éléments avec les nombres réels de l'égalité jusqu'à la
n
-ème bit sont sur le même côté de la partition qui se produisent pendant une récursifshuffle
appel de profondeurn
. L'algorithme se termine seulement quand toutes les listes issues des partitions sont vides ou des singletons : tous les éléments ont été séparés par au moins un essai, et ont donc un net binaire décimal.Probabiliste de la résiliation
D'un point subtil sur votre algorithme (ou ma équivalent de tri de la méthode), c'est que la résiliation condition est probabiliste. De Fisher-Yates se termine toujours après avoir connu un certain nombre d'étapes (le nombre d'éléments dans le tableau). Avec votre algorithme, la résiliation dépend de la sortie du générateur de nombre aléatoire.
Il y a des sorties possibles qui permettraient de faire de votre algorithme divergent, pas fin. Par exemple, si le générateur de nombre aléatoire toujours la sortie
0
, chaquepartition
appel renvoie la liste d'entrée inchangé, sur lequel vous récursive appelez le shuffle : vous aurez en boucle indéfiniment.Cependant, ce n'est pas un problème si vous êtes certain que votre générateur de nombre aléatoire est juste : il ne triche pas, et toujours de retour indépendantes uniformément distribués résultats. Dans ce cas, la probabilité que le test
random:uniform() < 0.5
retourne toujourstrue
(oufalse
) est exactement 0 :true
est de 2^{ -N}true
est la probabilité de l'intersection infinie, pour tout N, de l'événement que le premier N appels de retour0
; c'est l'infimum limite1 de la 2^{ -N}, qui est 01: pour les détails mathématiques, voir http://en.wikipedia.org/wiki/Measure_(mathematics)#Measures_of_infinite_intersections_of_measurable_sets
Plus généralement, l'algorithme ne prend pas fin si et seulement si certains éléments sont associés à la même boolean flux. Cela signifie qu'au moins deux éléments ont la même boolean flux. Mais la probabilité pour que deux aléatoire boolean flux sont égaux à 0 : la probabilité que les chiffres à la position K sont égaux est de 1/2, donc la probabilité que les N premiers chiffres sont égaux à 2^{ -N}, et la même analyse s'applique.
Donc, vous savez que votre algorithme se termine avec la probabilité 1. C'est un peu plus faible garantie que le de Fisher-Yates algorithme, qui de toujours mettre fin à. En particulier, vous êtes vulnérable à une attaque d'un mal adversaire contrôle de votre générateur de nombre aléatoire.
Avec plus de la théorie des probabilités, vous pouvez également calculer la distribution de temps de fonctionnement de votre algorithme pour une entrée donnée de la longueur. C'est au-delà de mes compétences techniques, mais je suppose que c'est une bonne chose : je suppose que vous avez seulement besoin de regarder en O(log N) premiers chiffres, en moyenne, pour vérifier que tous les N paresseux, les flux sont différents, et que la probabilité beaucoup plus élevée de l'exécution de la diminution des temps de façon exponentielle.
[min_int..max_int]
est pas suffisant pour faire de la probabilité d'un conflit proche de 0, en raison de l'anniversaire problème que vous avez mentionné: avec 32-bit ints, vous avez déjà atteindre 0,5 chance de conflit avec une liste de seulement ~de 77 000 articles.[min_int..max_int]
: vous avez raison et il n'a pas d'échelle de grandes séquences. J'ai aussi inclus une mise en œuvre du nombre réel de tri. Je suis d'accord que de Fisher-Yates est plus simple, mais je ne suis pas sûr Oleg proposition de l'est.Votre algorithme est une tri-fonction de lecture aléatoire, tel que discuté dans l'article de Wikipedia.
Une manière générale, la complexité de calcul de tri à base de mélange est le même que le sous-jacent algorithme de tri (par exemple, O(n journal n) moyenne O(n2) le pire des cas pour un tri rapide-fonction de lecture aléatoire), et que la répartition n'est pas parfaitement uniforme, il devrait approche uniforme assez proche pour la plupart des fins pratiques.
Oleg Kiselyov prévoit l'article suivant /discussion:
qui couvre les limitations de sorte à base de mélange dans le détail, et offre également deux adaptations du procédé Fischer–Yates stratégie: un naïf O(n2), et une binaire-tree-based O(n journal n) une.
Ce n'est pas vrai: bien que purement fonctionnelle de la programmation évite effets secondaires, il prend en charge l'accès à mutable état, avec les premiers effets de classe, sans nécessiter d'effets secondaires.
Dans ce cas, vous pouvez utiliser Haskell mutable tableaux pour mettre en œuvre la mutation de Fisher–Yates algorithme tel que décrit dans ce tutoriel:
Additif
La fondation particulière de votre shuffle de tri est en fait une infinie-clés tri radix: comme gasche, chacun partition correspond à un groupement de chiffres.
Le principal inconvénient de cette est le même que toute autre infini-clé de tri aléatoire: il n'y a pas de résiliation de la garantie. Bien que la probabilité de résiliation augmente à mesure que la comparaison des produits, il n'y a jamais de limite: le pire des cas, la complexité est O(∞).
Je faisais des choses similaires à ce il ya un moment, et, en particulier, vous pourriez être intéressé en Clojure de vecteurs, qui sont fonctionnels et immuable, mais toujours avec O(1) random access/caractéristiques de mise à jour. Ces deux gist avoir plusieurs implémentations de "prendre la N des éléments au hasard à partir de ce M la taille de la liste"; au moins l'un d'eux se transforme en une fonctionnelle de la mise en œuvre de Fisher-Yates si vous le permettez N=M.
https://gist.github.com/805546
https://gist.github.com/805747
Basé sur Comment faire pour tester le caractère aléatoire (cas du point de Brassage) , je propose:
Shuffle (de taille moyenne) des tableaux composé d'un nombre égal de zéros et de uns. Répéter et de les enchaîner jusqu'à s'ennuyer. Les utiliser comme entrée pour les inconditionnels de tests. Si vous avez une bonne lecture aléatoire, alors vous devriez être la génération aléatoire de séquences de zéros et de uns (avec la réserve que l'excédent cumulé de zéros (ou ceux) est égale à zéro à la limite de la taille moyenne des tableaux, qui vous espère que les tests de détection, mais le plus grand "moyen" est le moins ils sont susceptibles de le faire).
Noter qu'un test peut rejeter votre shuffle pour trois raisons:
Vous aurez à résoudre ce qui est le cas si un test rejette.
Diverses adaptations de la inconditionnels de tests (pour résoudre certains numéros, j'ai utilisé le source de la inconditionnels de la page). Le principe du mécanisme d'adaptation est de faire de l'algorithme de shuffle agir comme une source d'aléatoire uniformément distribué bits.
et ainsi de suite...
Vous pouvez également tirer parti de dieharder et/ou ent pour procéder à des tests adaptés.