Par exemple, j'ai des listes:
a[0] = [1, 1, 1, 0, 0]
a[1] = [1, 1, 0, 0, 1]
a[2] = [0, 1, 1, 1, 0]
# and so on
Ils semblent différents, mais si l'on suppose que le début et la fin sont connectés, alors ils sont circulairement identiques.
Le problème est que chaque liste que j'ai a une longueur de 55 et ne contient que trois uns et 52 zéros. Sans condition circulaire, il existe 26 235 (55 au choix 3) listes. Cependant, si la condition `` circulaire '' existe, il existe un grand nombre de listes circulairement identiques
Actuellement, je vérifie l'identité circulaire en suivant:
def is_dup(a, b):
for i in range(len(a)):
if a == list(numpy.roll(b, i)): # shift b circularly by i
return True
return False
Cette fonction nécessite 55 opérations de décalage cyclique dans le pire des cas. Et il y a 26 235 listes à comparer les unes avec les autres. En bref, j'ai besoin de 55 * 26235 * (26235 - 1) / 2 = 18 926 847 225 calculs. C'est à peu près 20 Giga!
Y a-t-il un bon moyen de le faire avec moins de calculs? Ou des types de données qui prennent en charge circulaire ?
Réponses:
Tout d'abord, cela peut être fait en
O(n)
termes de longueur de la liste. Vous pouvez remarquer que si vous dupliquez votre liste 2 fois ([1, 2, 3]
) sera[1, 2, 3, 1, 2, 3]
alors votre nouvelle liste contiendra certainement toutes les listes cycliques possibles.Il vous suffit donc de vérifier si la liste que vous recherchez se trouve à deux reprises dans votre liste de départ. En python, vous pouvez y parvenir de la manière suivante (en supposant que les longueurs sont les mêmes).
Quelques explications sur mon oneliner:
list * 2
combinera une liste avec elle-même,map(str, [1, 2])
convertira tous les nombres en chaîne et' '.join()
convertira le tableau['1', '2', '111']
en chaîne'1 2 111'
.Comme l'ont souligné certaines personnes dans les commentaires, oneliner peut potentiellement donner des faux positifs, donc pour couvrir tous les cas limites possibles:
PS1 quand on parle de complexité temporelle, il convient de noter que cela
O(n)
sera réalisé si la sous-chaîne peut être trouvée dans leO(n)
temps. Ce n'est pas toujours le cas et dépend de l'implémentation dans votre langage ( bien que potentiellement cela puisse être fait en temps linéaire KMP par exemple).PS2 pour les personnes qui ont peur du fonctionnement des cordes et pensent de ce fait que la réponse n'est pas bonne. Ce qui est important, c'est la complexité et la rapidité. Cet algorithme fonctionne potentiellement dans le
O(n)
temps et dans l'O(n)
espace, ce qui le rend bien meilleur que tout autreO(n^2)
domaine. Pour voir cela par vous-même, vous pouvez exécuter un petit benchmark (crée une liste aléatoire fait apparaître le premier élément et l'ajoute à la fin, créant ainsi une liste cyclique. Vous êtes libre de faire vos propres manipulations)0,3 seconde sur ma machine. Pas vraiment longtemps. Maintenant, essayez de comparer cela avec des
O(n^2)
solutions. Pendant qu'il le compare, vous pouvez voyager des États-Unis à l'Australie (très probablement par un bateau de croisière)la source
Je ne connais pas suffisamment Python pour répondre à cela dans le langage demandé, mais en C / C ++, étant donné les paramètres de votre question, je convertirais les zéros et les uns en bits et les pousserais sur les bits les moins significatifs d'un uint64_t. Cela vous permettra de comparer les 55 bits d'un seul coup - 1 horloge.
Très rapide, et le tout rentrera dans des caches sur puce (209 880 octets). La prise en charge matérielle du décalage simultané des 55 membres de la liste vers la droite n'est disponible que dans les registres d'un processeur. Il en va de même pour la comparaison des 55 membres simultanément. Cela permet un mappage 1 pour 1 du problème vers une solution logicielle. (et en utilisant les registres SIMD / SSE 256 bits, jusqu'à 256 membres si nécessaire) En conséquence, le code est immédiatement évident pour le lecteur.
Vous pourrez peut-être l'implémenter en Python, je ne le connais tout simplement pas assez bien pour savoir si c'est possible ou quelles pourraient être les performances.
Après avoir dormi dessus, certaines choses sont devenues évidentes, et tout pour le mieux.
1.) Il est si facile de faire tourner la liste circulaire en utilisant des bits que l'astuce très intelligente de Dali n'est pas nécessaire. À l'intérieur d'un registre 64 bits, le décalage de bits standard accomplira la rotation très simplement, et dans une tentative de rendre tout cela plus convivial pour Python, en utilisant l'arithmétique au lieu d'opérations sur les bits.
2.) Le décalage de bits peut être accompli facilement en utilisant la division par 2.
3.) La vérification de la fin de la liste pour 0 ou 1 peut être facilement effectuée par modulo 2.
4.) "Déplacer" un 0 à la tête de la liste à partir de la queue peut être fait en divisant par 2. Ceci parce que si le zéro était réellement déplacé, cela rendrait le 55ème bit faux, ce qu'il est déjà en ne faisant absolument rien.
5.) "Déplacer" un 1 à la tête de la liste à partir de la queue peut être fait en divisant par 2 et en ajoutant 18 014 398 509 481 984 - qui est la valeur créée en marquant le 55ème bit vrai et tout le reste faux.
6.) Si une comparaison de l'ancre et de uint64_t composé est TRUE après une rotation donnée, interrompre et retourner TRUE.
Je convertirais tout le tableau de listes en un tableau de uint64_ts dès le départ pour éviter d'avoir à faire la conversion à plusieurs reprises.
Après avoir passé quelques heures à essayer d'optimiser le code, à étudier le langage d'assemblage, j'ai pu réduire de 20% le temps d'exécution. Je dois ajouter que le compilateur O / S et MSVC a également été mis à jour en milieu de journée hier. Pour quelque raison que ce soit, la qualité du code produit par le compilateur C s'est considérablement améliorée après la mise à jour (15/11/2014). Le temps d'exécution est maintenant d'environ 70 horloges, 17 nanosecondes pour composer et comparer un anneau d'ancrage avec les 55 tours d'un anneau de test et NxN de tous les anneaux contre tous les autres se fait en 12,5 secondes .
Ce code est si serré que tous les registres sauf 4 sont assis à ne rien faire 99% du temps. Le langage d'assemblage correspond presque ligne pour ligne au code C. Très facile à lire et à comprendre. Un grand projet d'assemblage si quelqu'un apprenait cela.
Le matériel est Hazwell i7, MSVC 64 bits, optimisations complètes.
la source
En lisant entre les lignes, on dirait que vous essayez d'énumérer un représentant de chaque classe d'équivalence circulaire de chaînes avec 3 uns et 52 zéros. Passons d'une représentation dense à une représentation clairsemée (ensemble de trois nombres dans
range(55)
). Dans cette représentation, le déplacement circulaire des
park
est donné par la compréhensionset((i + k) % 55 for i in s)
. Le représentant lexicographique minimum dans une classe contient toujours la position 0. Étant donné un ensemble de la forme{0, i, j}
avec0 < i < j
, les autres candidats au minimum dans la classe sont{0, j - i, 55 - i}
et{0, 55 - j, 55 + i - j}
. Par conséquent, nous avons besoin(i, j) <= min((j - i, 55 - i), (55 - j, 55 + i - j))
que l'original soit minimal. Voici un code d'énumération.la source
Répétez le premier tableau, puis utilisez l' algorithme Z (temps O (n)) pour trouver le deuxième tableau à l'intérieur du premier.
(Remarque: vous n'êtes pas obligé de copier physiquement le premier tableau. Vous pouvez simplement boucler pendant la correspondance.)
La bonne chose à propos de l'algorithme Z est qu'il est très simple par rapport à KMP, BM, etc.
Cependant, si vous vous sentez ambitieux, vous pouvez faire une correspondance de chaînes en temps linéaire et en espace constant -
strstr
par exemple, faites -le. La mettre en œuvre serait cependant plus douloureuse.la source
Suite à la solution très intelligente de Salvador Dali, la meilleure façon de la gérer est de s'assurer que tous les éléments sont de la même longueur, ainsi que les deux LISTES sont de la même longueur.
Aucun indice si cela est plus rapide ou plus lent que la solution regex recommandée par AshwiniChaudhary dans la réponse de Salvador Dali, qui se lit comme suit:
la source
str.format
n
heures pour formater la chaîne résultante. JE SUPPOSE .... :)Étant donné que vous devez faire autant de comparaisons, cela vaut-il la peine de parcourir vos listes pour les convertir en une sorte de forme canonique qui peut être facilement comparée?
Essayez-vous d'obtenir un ensemble de listes circulaires uniques? Si c'est le cas, vous pouvez les jeter dans un ensemble après les avoir convertis en tuples.
Toutes mes excuses à David Eisenstat pour ne pas avoir repéré sa réponse similaire.
la source
Vous pouvez rouler une liste comme ceci:
la source
Tout d' abord convertir tous les éléments de votre liste (dans une copie si nécessaire) pour que la version pivotée qui est lexicalement plus grand.
Ensuite, triez la liste de listes résultante (en conservant un index dans la position de liste d'origine) et unifiez la liste triée, en marquant tous les doublons dans la liste d'origine si nécessaire.
la source
En s'appuyant sur l'observation de @ SalvadorDali sur la recherche de correspondances de a dans n'importe quelle tranche de taille a dans b + b, voici une solution utilisant uniquement des opérations de liste.
2ème approche: [supprimé]
la source
rollmatch([1, 0, 1, 1], [0, 1, 1, 1])
.Pas une réponse complète et indépendante, mais sur le thème de l'optimisation en réduisant les comparaisons, je pensais moi aussi aux représentations normalisées.
À savoir, si votre alphabet d'entrée est {0, 1}, vous pouvez réduire considérablement le nombre de permutations autorisées. Faites pivoter la première liste vers une forme (pseudo-) normalisée (étant donné la distribution de votre question, je choisirais celle où l'un des 1 bits est à l'extrême gauche et l'un des 0 bits à l'extrême droite). Maintenant, avant chaque comparaison, tournez successivement l'autre liste à travers les positions possibles avec le même motif d'alignement.
Par exemple, si vous avez un total de quatre bits 1, il peut y avoir au plus 4 permutations avec cet alignement, et si vous avez des grappes de 1 bits adjacents, chaque bit supplémentaire dans un tel groupe réduit le nombre de positions.
Cela se généralise aux alphabets plus grands et aux différents modèles d'alignement; le principal défi est de trouver une bonne normalisation avec seulement quelques représentations possibles. Idéalement, ce serait une bonne normalisation, avec une seule représentation unique, mais étant donné le problème, je ne pense pas que ce soit possible.
la source
S'appuyant davantage sur la réponse de RocketRoy: convertissez toutes vos listes à l'avance en nombres 64 bits non signés. Pour chaque liste, faites pivoter ces 55 bits pour trouver la plus petite valeur numérique.
Il vous reste maintenant une seule valeur 64 bits non signée pour chaque liste que vous pouvez comparer directement avec la valeur des autres listes. La fonction is_circular_identical () n'est plus nécessaire.
(En substance, vous créez une valeur d'identité pour vos listes qui n'est pas affectée par la rotation des éléments des listes) Cela fonctionnerait même si vous avez un nombre arbitraire de l'un dans vos listes.
la source
C'est la même idée de Salvador Dali mais n'a pas besoin de la conversion de chaîne. Derrière, il y a la même idée de récupération KMP pour éviter une inspection de quart impossible. Ils appellent uniquement KMPModified (liste1, liste2 + liste2).
J'espère que cette aide!
la source
Simplifier le problème
(0,1)
1
s consécutifs dans un compte0
s consécutifs dans un compte négatifExemple
Vérification du processus
La prise
lookup
etlook-ahead
Pseudo-code
Les fonctions
MAP_LIST(LIST A):LIST
CARTEZ LES ÉLÉMENTS CONSQUETIFS COMME DES COMPTES DANS UNE NOUVELLE LISTELOOKUP_INDEX(LIST A, INTEGER E):LIST
RETOUR LISTE DES INDICES O L'ÉLÉMENTE
EXISTE DANS LA LISTEA
COUNT_CHAR(LIST A , INTEGER E):INTEGER
E
COMPTEZ LE NOMBRE DE FOIS UN ÉLÉMENT SUR UNE LISTEA
ALPHA_NGRAM(LIST A,LIST B,INTEGER I,INTEGER N):BOOLEAN
VÉRIFIEZ SIB[I]
EST ÉQUIVALENT AUXA[0]
N-GRAM
DEUX DIRECTIONSfinalement
Si la taille de la liste va être assez énorme ou si l'élément à partir duquel nous commençons à vérifier le cycle est souvent élevé, nous pouvons faire ce qui suit:
Recherchez l'élément le moins fréquent dans la première liste pour commencer
augmenter le paramètre n-gramme N pour réduire la probabilité de passer par un contrôle linéaire
la source
Une "forme canonique" efficace et rapide à calculer pour les listes en question peut être dérivée comme suit:
a
) doit être compris entre18
et52
(inclus). Recodez-le entre0
et34
.b
) doit être compris entre0
et26
, mais cela n'a pas beaucoup d'importance.52 - (a + b)
et n'ajoute aucune informationLa forme canonique est l'entier
b * 35 + a
, qui est compris entre0
et936
(inclus), ce qui est assez compact (il y a477
des listes circulairement uniques au total).la source
J'ai écrit une solution simple qui compare les deux listes et augmente simplement (et entoure) l'indice de la valeur comparée pour chaque itération.
Je ne connais pas bien python donc je l'ai écrit en Java, mais c'est vraiment simple, donc il devrait être facile de l'adapter à n'importe quel autre langage.
Par cela, vous pouvez également comparer des listes d'autres types.
la source
Comme d'autres l'ont mentionné, une fois que vous avez trouvé la rotation normalisée d'une liste, vous pouvez les comparer.
Voici un code de travail qui fait cela, la méthode de base consiste à trouver une rotation normalisée pour chaque liste et à comparer:
Notez que cette méthode ne dépend pas des nombres, vous pouvez passer des listes de chaînes (toutes les valeurs qui peuvent être comparées).
Au lieu de faire une recherche de liste dans la liste, nous savons que nous voulons que la liste commence par la valeur minimale - afin que nous puissions boucler sur les valeurs minimales, en recherchant jusqu'à ce que nous trouvions laquelle a les valeurs successives les plus basses, en la stockant pour d'autres comparaisons jusqu'à ce que nous ayons le meilleur.
Il existe de nombreuses possibilités de sortir tôt lors du calcul de l'indice, des détails sur certaines optimisations.
Notez qu'en Python, une recherche de liste dans une liste peut être plus rapide, mais j'étais intéressé par un algorithme efficace - qui pourrait également être utilisé dans d'autres langues. En outre, il y a un avantage à éviter de créer de nouvelles listes.
Voir: cet extrait de code pour d'autres tests / exemples.
la source
Vous pouvez vérifier assez facilement si une liste A est égale à un décalage cyclique de la liste B dans le temps O (N) attendu.
J'utiliserais une fonction de hachage polynomiale pour calculer le hachage de la liste A et chaque déplacement cyclique de la liste B.Lorsqu'un décalage de la liste B a le même hachage que la liste A, je comparerais les éléments réels pour voir s'ils sont égaux .
La raison pour laquelle c'est rapide est qu'avec les fonctions de hachage polynomiales (qui sont extrêmement courantes!), Vous pouvez calculer le hachage de chaque décalage cyclique par rapport au précédent en temps constant, de sorte que vous pouvez calculer les hachages pour tous les décalages cycliques dans O ( N) heure.
Cela fonctionne comme ceci:
Disons que B a N éléments, alors le hachage de B utilisant le premier P est:
C'est une manière optimisée d'évaluer un polynôme dans P, et équivaut à:
Remarquez comment chaque B [i] est multiplié par P ^ (N-1-i). Si nous décalons B vers la gauche de 1, alors chaque B [i] sera multiplié par un P supplémentaire, sauf le premier. Puisque la multiplication se distribue sur l'addition, nous pouvons multiplier tous les composants à la fois simplement en multipliant le hachage entier, puis fixer le facteur pour le premier élément.
Le hachage du décalage gauche de B est juste
Le deuxième décalage à gauche:
etc...
REMARQUE: tous les calculs ci-dessus sont effectués modulo une taille de mot machine, et vous ne devez calculer P ^ N qu'une seule fois.
la source
Pour coller à la manière la plus pythonique de le faire, utilisez des sets!
la source