Come verificare se due elenchi sono circolarmente identici in Python


145

Ad esempio, ho elenchi:

a[0] = [1, 1, 1, 0, 0]
a[1] = [1, 1, 0, 0, 1]
a[2] = [0, 1, 1, 1, 0]
# and so on

Sembrano diversi, ma se si suppone che l'inizio e la fine siano collegati, allora sono circolarmente identici.

Il problema è che ogni elenco che ho ha una lunghezza di 55 e ne contiene solo tre e 52 zeri. Senza condizioni circolari, ci sono 26.235 (55 scelgono 3) liste. Tuttavia, se esiste la condizione "circolare", esiste un numero enorme di elenchi circolari identici

Attualmente controllo l'identità circolare seguendo:

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

Questa funzione richiede 55 operazioni di spostamento cicliche nel caso peggiore. E ci sono 26.235 liste da confrontare tra loro. In breve, ho bisogno di 55 * 26.235 * (26.235 - 1) / 2 = 18.926.847.225 calcoli. Si tratta di circa 20 Giga!

C'è un buon modo per farlo con meno calcoli? O qualsiasi tipo di dati che supporti circolare ?


Solo un sospetto: sento che gli alberi di suffisso potrebbero aiutare qui. en.wikipedia.org/wiki/Suffix_tree . Per costruirne uno, vedi en.wikipedia.org/wiki/Ukkonen%27s_algorithm
Rerito

1
@Mehrdad Ma un tempo di esecuzione molto peggiore di qualsiasi risposta che si converte in una forma canonica, un tempo di esecuzione molto peggiore rispetto alla conversione in un numero intero e un tempo di esecuzione molto, molto peggiore di quello di David Eisenstat.
Veedrac,

2
Tutte le risposte cercano di risolvere il problema generale, ma in questo caso particolare con solo 3 puoi rappresentare ogni elenco con 3 numeri che rappresentano un numero di zeri tra quelli. L'elenco dalla domanda può essere rappresentato come [0,0,2], [0,2,0], [2,0,0]. Puoi semplicemente ridurre l'elenco in una corsa e quindi controllare l'elenco ridotto. Se sono "identicamente circolari", lo sono anche gli originali.
abc667,

1
Immagino che Stack Overflow non abbia bisogno di voto allora. Tutto ciò che serve è eseguire il codice in tutte le soluzioni e presentarle nell'ordine in cui terminano.
Dawood ibn Kareem,

2
Dato che non è stato menzionato finora, la "forma canonica" a cui fanno riferimento @ abc667, Veedrac ed Eisenstat si chiama Run Length Encoding en.wikipedia.org/wiki/Run-length_encoding
David Lovell

Risposte:


133

Prima di tutto, questo può essere fatto in O(n)termini di lunghezza dell'elenco. Puoi notare che se duplicherai il tuo elenco 2 volte ( [1, 2, 3]), il [1, 2, 3, 1, 2, 3]tuo nuovo elenco conterrà sicuramente tutti i possibili elenchi ciclici.

Quindi tutto ciò che serve è verificare se l'elenco che stai cercando si trova all'interno di 2 volte del tuo elenco di partenza. In Python puoi ottenerlo nel modo seguente (supponendo che le lunghezze siano le stesse).

list1 = [1, 1, 1, 0, 0]
list2 = [1, 1, 0, 0, 1]
print ' '.join(map(str, list2)) in ' '.join(map(str, list1 * 2))

Qualche spiegazione sul mio oneliner: list * 2combinerà un elenco con se stesso, map(str, [1, 2])convertirà tutti i numeri in stringa e ' '.join()convertirà l'array ['1', '2', '111']in una stringa '1 2 111'.

Come sottolineato da alcune persone nei commenti, oneliner può potenzialmente dare alcuni falsi positivi, in modo da coprire tutti i possibili casi limite:

def isCircular(arr1, arr2):
    if len(arr1) != len(arr2):
        return False

    str1 = ' '.join(map(str, arr1))
    str2 = ' '.join(map(str, arr2))
    if len(str1) != len(str2):
        return False

    return str1 in str2 + ' ' + str2

PS1 quando si parla di complessità temporale, vale la pena notare che O(n)sarà raggiunto se la sottostringa può essere trovata nel O(n)tempo. Non è sempre così e dipende dall'implementazione nella tua lingua ( anche se potenzialmente può essere fatta in KMP ad esempio in tempo lineare ).

PS2 per le persone che hanno paura delle stringhe e per questo motivo pensano che la risposta non sia buona. L'importante è la complessità e la velocità. Questo algoritmo potenzialmente funziona nel O(n)tempo e O(n)nello spazio, il che lo rende molto meglio di qualsiasi cosa nel O(n^2)dominio. Per vederlo da solo, puoi eseguire un piccolo benchmark (crea un elenco casuale apre il primo elemento e lo aggiunge alla fine creando così un elenco ciclico. Sei libero di fare le tue manipolazioni)

from random import random
bigList = [int(1000 * random()) for i in xrange(10**6)]
bigList2 = bigList[:]
bigList2.append(bigList2.pop(0))

# then test how much time will it take to come up with an answer
from datetime import datetime
startTime = datetime.now()
print isCircular(bigList, bigList2)
print datetime.now() - startTime    # please fill free to use timeit, but it will give similar results

0,3 secondi sulla mia macchina. Non molto a lungo. Ora prova a confrontare questo con le O(n^2)soluzioni. Mentre lo sta confrontando, puoi viaggiare dagli Stati Uniti all'Australia (molto probabilmente con una nave da crociera)


3
Basta aggiungere spazi di riempimento (1 prima e 1 dopo ogni stringa) farà il trucco. Non c'è bisogno di complicare eccessivamente le cose con le regex. (Naturalmente
suppongo

2
@Rerito a meno che una delle due liste non contenga stringhe, che possono avere spazi iniziali o finali. Può ancora causare collisioni.
Adam Smith,

12
Non mi piace questa risposta. L'operazione senza senso della stringa mi ha fatto non piacere e la risposta di David Eisenstat mi ha reso disposto a ridimensionarlo. Questo confronto può essere fatto in O (n) tempo con una stringa, ma può anche essere fatto in O (n) tempo con un numero intero [bisogno di 10k come auto-cancellato], che è più veloce. Tuttavia, la risposta di David Eisenstat mostra che fare qualsiasi confronto è inutile poiché la risposta non ne ha bisogno.
Veedrac,

7
@Veedrac mi stai prendendo in giro? Hai sentito parlare della complessità computazionale? La risposta di Davids richiede O (n ^ 2) tempo e O (n ^ 2) spazio solo per generare tutte le sue ripetizioni che anche per piccoli input di 10 ^ 4 di lunghezza impiegano 22 secondi e chissà quanto ram. Per non parlare del fatto che non abbiamo iniziato a cercare nulla in questo momento (abbiamo appena generato tutte le rotazioni cicliche). E la mia assurdità della stringa ti dà un risultato completo per input come 10 ^ 6 in meno di 0,5 secondi. Inoltre ha bisogno di O (n) spazio per memorizzarlo. Quindi, per favore, prenditi del tempo per capire la risposta prima di saltare alla conclusione.
Salvador Dali,

1
@SalvadorDali Sembri molto (soft) focalizzato sul tempo ;-)
e2-e4

38

Non abbastanza ben informato in Python per rispondere a questo nel linguaggio richiesto, ma in C / C ++, dati i parametri della tua domanda, convertivo gli zeri e quelli in bit e li spingerei sui bit meno significativi di un uint64_t. Ciò ti consentirà di confrontare tutti i 55 bit in un colpo solo - 1 orologio.

Wickedly fast, e il tutto andrà bene nella cache on-chip (209.880 byte). Il supporto hardware per spostare contemporaneamente tutti e 55 i membri dell'elenco è disponibile solo nei registri di una CPU. Lo stesso vale per il confronto di tutti e 55 i membri contemporaneamente. Ciò consente una mappatura 1 per 1 del problema su una soluzione software. (e utilizzando i registri a 256 bit SIMD / SSE, fino a 256 membri se necessario) Di conseguenza il codice è immediatamente ovvio per il lettore.

Potresti essere in grado di implementarlo in Python, semplicemente non lo so abbastanza bene da sapere se è possibile o quali potrebbero essere le prestazioni.

Dopo aver dormito su di esso, alcune cose sono diventate ovvie, e tutto per il meglio.

1.) È così facile girare l'elenco collegato in modo circolare usando bit che il trucco molto intelligente di Dalì non è necessario. All'interno di un registro a 64 bit, lo spostamento dei bit standard compirà la rotazione in modo molto semplice e nel tentativo di rendere tutto questo più compatibile con Python, usando l'aritmetica invece delle operazioni a bit.

2.) Lo spostamento dei bit può essere realizzato facilmente usando la divisione per 2.

3.) Il controllo della fine dell'elenco per 0 o 1 può essere eseguito facilmente dal modulo 2.

4.) "Spostare" uno 0 in testa alla lista dalla coda può essere fatto dividendo per 2. Questo perché se lo zero fosse effettivamente spostato renderebbe falso il 55 ° bit, cosa che già non fa assolutamente nulla.

5.) "Spostare" un 1 in testa alla lista dalla coda può essere fatto dividendo per 2 e aggiungendo 18.014.398.509.481.984 - che è il valore creato contrassegnando il 55esimo bit vero e tutto il resto falso.

6.) Se un confronto tra l'ancora e composto uint64_t è TRUE dopo una determinata rotazione, rompi e ritorna TRUE.

Vorrei convertire l'intero array di liste in un array di uint64_ts direttamente per evitare di dover ripetere la conversione ripetutamente.

Dopo aver trascorso alcune ore a cercare di ottimizzare il codice, studiando il linguaggio assembly sono stato in grado di ottenere uno sconto del 20% sul tempo di esecuzione. Vorrei aggiungere che ieri anche il compilatore O / S e MSVC è stato aggiornato a metà giornata. Per qualsiasi motivo, la qualità del codice prodotto dal compilatore C è migliorata notevolmente dopo l'aggiornamento (15/11/2014). Il tempo di esecuzione è ora di circa 70 orologi, 17 nanosecondi per comporre e confrontare un anello di ancoraggio con tutti i 55 giri di un anello di prova e NxN di tutti gli anelli rispetto a tutti gli altri viene eseguito in 12,5 secondi .

Questo codice è così stretto ma tutti e 4 i registri sono seduti in giro senza fare nulla il 99% delle volte. Il linguaggio assembly corrisponde al codice C quasi riga per riga. Molto facile da leggere e capire. Un grande progetto di assemblaggio se qualcuno lo insegnasse a loro stessi.

L'hardware è Hazwell i7, MSVC a 64 bit, ottimizzazioni complete.

#include "stdafx.h"
#include "stdafx.h"
#include <string>
#include <memory>
#include <stdio.h>
#include <time.h>

const uint8_t  LIST_LENGTH = 55;    // uint_8 supports full witdth of SIMD and AVX2
// max left shifts is 32, so must use right shifts to create head_bit
const uint64_t head_bit = (0x8000000000000000 >> (64 - LIST_LENGTH)); 
const uint64_t CPU_FREQ = 3840000000;   // turbo-mode clock freq of my i7 chip

const uint64_t LOOP_KNT = 688275225; // 26235^2 // 1000000000;

// ----------------------------------------------------------------------------
__inline uint8_t is_circular_identical(const uint64_t anchor_ring, uint64_t test_ring)
{
    // By trial and error, try to synch 2 circular lists by holding one constant
    //   and turning the other 0 to LIST_LENGTH positions. Return compare count.

    // Return the number of tries which aligned the circularly identical rings, 
    //  where any non-zero value is treated as a bool TRUE. Return a zero/FALSE,
    //  if all tries failed to find a sequence match. 
    // If anchor_ring and test_ring are equal to start with, return one.

    for (uint8_t i = LIST_LENGTH; i;  i--)
    {
        // This function could be made bool, returning TRUE or FALSE, but
        // as a debugging tool, knowing the try_knt that got a match is nice.
        if (anchor_ring == test_ring) {  // test all 55 list members simultaneously
            return (LIST_LENGTH +1) - i;
        }

        if (test_ring % 2) {    //  ring's tail is 1 ?
            test_ring /= 2;     //  right-shift 1 bit
            // if the ring tail was 1, set head to 1 to simulate wrapping
            test_ring += head_bit;      
        }   else    {           // ring's tail must be 0
            test_ring /= 2;     // right-shift 1 bit
            // if the ring tail was 0, doing nothing leaves head a 0
        }
    }
    // if we got here, they can't be circularly identical
    return 0;
}
// ----------------------------------------------------------------------------
    int main(void)  {
        time_t start = clock();
        uint64_t anchor, test_ring, i,  milliseconds;
        uint8_t try_knt;

        anchor = 31525197391593472; // bits 55,54,53 set true, all others false
        // Anchor right-shifted LIST_LENGTH/2 represents the average search turns
        test_ring = anchor >> (1 + (LIST_LENGTH / 2)); //  117440512; 

        printf("\n\nRunning benchmarks for %llu loops.", LOOP_KNT);
        start = clock();
        for (i = LOOP_KNT; i; i--)  {
            try_knt = is_circular_identical(anchor, test_ring);
            // The shifting of test_ring below is a test fixture to prevent the 
            //  optimizer from optimizing the loop away and returning instantly
            if (i % 2) {
                test_ring /= 2;
            }   else  {
                test_ring *= 2;
            }
        }
        milliseconds = (uint64_t)(clock() - start);
        printf("\nET for is_circular_identical was %f milliseconds."
                "\n\tLast try_knt was %u for test_ring list %llu", 
                        (double)milliseconds, try_knt, test_ring);

        printf("\nConsuming %7.1f clocks per list.\n",
                (double)((milliseconds * (CPU_FREQ / 1000)) / (uint64_t)LOOP_KNT));

        getchar();
        return 0;
}

inserisci qui la descrizione dell'immagine


23
la gente continua a parlare della "soluzione di Salvador Dalì" e io ero semplicemente seduto qui confuso, chiedendomi se il pittore con lo stesso nome fosse anche un matematico che contribuì in modo significativo agli algoritmi classici. poi mi sono reso conto che è il nome utente della persona che ha pubblicato la risposta più popolare. non sono un uomo intelligente.
Woodrow Barlow,

Per chiunque abbia un rappresentante di 10k e l'implementazione è disponibile qui usando Numpy e la vettorializzazione. Specchio Gist per quelli <10k . Ho eliminato la mia risposta perché la risposta di David Eisenstat sottolinea che non è necessario fare confronti in quanto è possibile generare subito elenchi univoci e voglio incoraggiare le persone a utilizzare la sua risposta di gran lunga migliore.
Veedrac,

@RocketRoy Perché pensi che Python non avrebbe operazioni bit? Cavolo, utilizzo le operazioni di bit nel codice che ho collegato . Penso ancora che questa risposta sia per lo più inutile (la risposta di David Eisenstat richiede 1ms per l'intera cosa), ma ho trovato strana questa affermazione. FWIW, un algoritmo simile in Numpy per la ricerca di 262 M - "elenchi" richiede circa 15 secondi sul mio computer (supponendo che non venga trovata alcuna corrispondenza), solo la rotazione dell'elenco avviene nel ciclo esterno, non in quello interno.
Veedrac,

@Quincunx, grazie per la modifica per ottenere la colorazione della sintassi corretta per C ++. Molto apprezzato!

@RocketRoy Nessun problema. Quando rispondi a molte domande su PPCG , impari come eseguire la colorazione della sintassi.
Giustino,

33

Leggendo tra le righe, sembra che tu stia provando a enumerare un rappresentante di ogni classe circolare di equivalenza di stringhe con 3 e 52 zeri. Passiamo da una rappresentazione densa a una sparsa (set di tre numeri in range(55)). In questa rappresentazione, lo spostamento circolare di sby kè dato dalla comprensione set((i + k) % 55 for i in s). Il rappresentante minimo lessicografico in una classe contiene sempre la posizione 0. Dato un insieme del modulo {0, i, j}con 0 < i < j, gli altri candidati per il minimo nella classe sono {0, j - i, 55 - i}e {0, 55 - j, 55 + i - j}. Pertanto, è necessario (i, j) <= min((j - i, 55 - i), (55 - j, 55 + i - j))che l'originale sia il minimo. Ecco un po 'di codice di enumerazione.

def makereps():
    reps = []
    for i in range(1, 55 - 1):
        for j in range(i + 1, 55):
            if (i, j) <= min((j - i, 55 - i), (55 - j, 55 + i - j)):
                reps.append('1' + '0' * (i - 1) + '1' + '0' * (j - i - 1) + '1' + '0' * (55 - j - 1))
    return reps

2
@SalvadorDali Hai frainteso la risposta (l'ho fatto anche io fino a quando non l'ha sottolineato!). Questo genera direttamente "un rappresentante di ogni classe di equivalenza circolare di stringhe con 3 e 52 zeri". Il suo codice non genera tutte le rotazioni cicliche. Il costo originale¹ è T (55² · 26235²). Il tuo codice migliora da 55² a 55, quindi è solo T (55 * 26235²). La risposta di David Eisenstat è tra 55 ² e 55 for per tutto . 55³ ≪ 55 · 26235². ¹ In questo caso non si parla in termini di big-O come costo effettivo in O (1).
Veedrac,

1
@Veedrac Ma il 99% dei lettori che verranno a questa domanda in futuro, non avrà i suoi vincoli e credo che la mia risposta si adatterà meglio a loro. Senza gonfiare ulteriormente la conversazione, lascerò l'OP per spiegare cosa vuole esattamente.
Salvador Dali,

5
@SalvadorDali OP sembra essere caduto in preda al problema XY . Fortunatamente, la domanda stessa chiarisce cosa non il titolo e David è stato in grado di leggere tra le righe. In questo caso, la cosa giusta da fare è modificare il titolo e risolvere il problema reale, piuttosto che rispondere al titolo e ignorare la domanda.
Aaron Dufour,

1
@SalvadorDali, sotto le copertine il tuo codice Python chiama l'equivalente di C strstr () che cerca una stringa per una sottostringa. Che a sua volta chiama strcmp (), che esegue un ciclo for () confrontando ogni carattere in una stringa1 con stringa2. Pertanto, ciò che sembra O (n) è O (n * 55 * 55) ipotizzando una ricerca fallita. Le lingue di alto livello sono un'arma a doppio taglio. Ti nascondono i dettagli dell'implementazione, ma poi nascondono anche i dettagli dell'implementazione. FWIW, la tua intuizione per concatenare l'elenco è stata geniale. Ancora più veloce di uint8 e molto più veloce di bit, che può essere facilmente ruotato nell'hardware.

2
@AleksandrDubinsky Più semplice per il computer, più complicato per gli esseri umani. È abbastanza veloce così com'è.
David Eisenstat,

12

Ripeti il ​​primo array, quindi usa l' algoritmo Z (O (n) time) per trovare il secondo array all'interno del primo.

(Nota: non è necessario copiare fisicamente il primo array. Si può semplicemente avvolgere durante la corrispondenza.)

La cosa bella dell'algoritmo Z è che è molto semplice rispetto a KMP, BM, ecc.
Tuttavia, se ti senti ambizioso, potresti fare una corrispondenza delle stringhe nel tempo lineare e nello spazio costantestrstr , ad esempio. L'attuazione sarebbe però più dolorosa.


6

Seguendo la soluzione molto intelligente di Salvador Dalì, il modo migliore per gestirlo è assicurarsi che tutti gli elementi abbiano la stessa lunghezza, così come entrambi gli ELENCHI siano della stessa lunghezza.

def is_circular_equal(lst1, lst2):
    if len(lst1) != len(lst2):
        return False
    lst1, lst2 = map(str, lst1), map(str, lst2)
    len_longest_element = max(map(len, lst1))
    template = "{{:{}}}".format(len_longest_element)
    circ_lst = " ".join([template.format(el) for el in lst1]) * 2
    return " ".join([template.format(el) for el in lst2]) in circ_lst

Nessun indizio se questo è più veloce o più lento della soluzione regex raccomandata da AshwiniChaudhary nella risposta di Salvador Dalì, che recita:

import re

def is_circular_equal(lst1, lst2):
    if len(lst2) != len(lst2):
        return False
    return bool(re.search(r"\b{}\b".format(' '.join(map(str, lst2))),
                          ' '.join(map(str, lst1)) * 2))

1
ho fatto questo da quando ho praticamente modificato la risposta di Salvador Dalì e formattato le modifiche di Ashwini. Molto poco di questo è in realtà il mio.
Adam Smith,

1
grazie per l'input. Penso di aver coperto tutti i possibili casi nella mia soluzione modificata. Fammi sapere se manca qualcosa.
Salvador Dali,

@SalvadorDali ah, sì ... controllando che le stringhe abbiano la stessa lunghezza. Suppongo che sarebbe più semplice che scorrere l'elenco alla ricerca dell'elemento più lungo, quindi chiamare i str.format ntempi per formattare la stringa risultante. Suppongo .... :)
Adam Smith,

3

Dato che hai bisogno di fare così tanti confronti potrebbe valere la pena fare un passaggio iniziale attraverso le tue liste per convertirle in una sorta di forma canonica che può essere facilmente confrontata?

Stai cercando di ottenere una serie di elenchi circolari univoci? In tal caso puoi gettarli in un set dopo averli convertiti in tuple.

def normalise(lst):
    # Pick the 'maximum' out of all cyclic options
    return max([lst[i:]+lst[:i] for i in range(len(lst))])

a_normalised = map(normalise,a)
a_tuples = map(tuple,a_normalised)
a_unique = set(a_tuples)

Ci scusiamo con David Eisenstat per non aver individuato la sua risposta simile.


3

È possibile scorrere un elenco in questo modo:

list1, list2 = [0,1,1,1,0,0,1,0], [1,0,0,1,0,0,1,1]

str_list1="".join(map(str,list1))
str_list2="".join(map(str,list2))

def rotate(string_to_rotate, result=[]):
    result.append(string_to_rotate)
    for i in xrange(1,len(string_to_rotate)):
        result.append(result[-1][1:]+result[-1][0])
    return result

for x in rotate(str_list1):
    if cmp(x,str_list2)==0:
        print "lists are rotationally identical"
        break

3

Per prima cosa converti tutti gli elementi della tua lista (in una copia se necessario) in quella versione ruotata che è lessicamente maggiore.

Quindi ordinare l'elenco risultante di elenchi (mantenendo un indice nella posizione dell'elenco originale) e unificare l'elenco ordinato, contrassegnando tutti i duplicati nell'elenco originale come necessario.


2

Ripercorrendo l'osservazione di @ SalvadorDali sulla ricerca di corrispondenze di a in qualsiasi sezione di dimensioni allungate in b + b, ecco una soluzione che utilizza solo le operazioni dell'elenco.

def rollmatch(a,b):
    bb=b*2
    return any(not any(ax^bbx for ax,bbx in zip(a,bb[i:])) for i in range(len(a)))

l1 = [1,0,0,1]
l2 = [1,1,0,0]
l3 = [1,0,1,0]

rollmatch(l1,l2)  # True
rollmatch(l1,l3)  # False

2o approccio: [eliminato]


La prima versione è O (n²) e la seconda non funziona rollmatch([1, 0, 1, 1], [0, 1, 1, 1]).
Veedrac,

Bella cattura, lo cancellerò!
PaulMcG

1

Non una risposta completa e indipendente, ma sul tema dell'ottimizzazione riducendo i confronti, anch'io stavo pensando a rappresentazioni normalizzate.

Vale a dire, se l'alfabeto di input è {0, 1}, è possibile ridurre significativamente il numero di permutazioni consentite. Ruota il primo elenco in una forma (pseudo-) normalizzata (data la distribuzione nella tua domanda, sceglierei uno in cui uno dei 1 bit è all'estrema sinistra e uno degli 0 bit all'estrema destra). Ora prima di ogni confronto, ruota successivamente l'altro elenco attraverso le possibili posizioni con lo stesso modello di allineamento.

Ad esempio, se si dispone di un totale di quattro 1 bit, ci possono essere al massimo 4 permutazioni con questo allineamento e se si hanno cluster di 1 bit adiacenti, ogni bit aggiuntivo in un tale cluster riduce la quantità di posizioni.

List 1   1 1 1 0 1 0

List 2   1 0 1 1 1 0  1st permutation
         1 1 1 0 1 0  2nd permutation, final permutation, match, done

Questo si generalizza in alfabeti più grandi e differenti schemi di allineamento; la sfida principale è trovare una buona normalizzazione con solo alcune possibili rappresentazioni. Idealmente, sarebbe una normale normalizzazione, con un'unica rappresentazione unica, ma dato il problema, non credo sia possibile.


0

Sviluppando ulteriormente la risposta di RocketRoy: converti tutti i tuoi elenchi in anticipo in numeri a 64 bit senza segno. Per ogni elenco, ruota quei 55 bit in giro per trovare il valore numerico più piccolo.

Ora viene lasciato un singolo valore a 64 bit senza segno per ogni elenco che è possibile confrontare direttamente con il valore degli altri elenchi. La funzione is_circular_identical () non è più richiesta.

(In sostanza, crei un valore di identità per i tuoi elenchi che non è influenzato dalla rotazione degli elementi degli elenchi) Funzionerebbe anche se hai un numero arbitrario di uno nei tuoi elenchi.


0

Questa è la stessa idea di Salvador Dalì ma non è necessaria la conversione di stringhe. Dietro c'è la stessa idea di recupero di KMP per evitare un'ispezione del turno impossibile. Chiamano solo KMPModified (lista1, lista2 + lista2).

    public class KmpModified
    {
        public int[] CalculatePhi(int[] pattern)
        {
            var phi = new int[pattern.Length + 1];
            phi[0] = -1;
            phi[1] = 0;

            int pos = 1, cnd = 0;
            while (pos < pattern.Length)
                if (pattern[pos] == pattern[cnd])
                {
                    cnd++;
                    phi[pos + 1] = cnd;
                    pos++;
                }
                else if (cnd > 0)
                    cnd = phi[cnd];
                else
                {
                    phi[pos + 1] = 0;
                    pos++;
                }

            return phi;
        }

        public IEnumerable<int> Search(int[] pattern, int[] list)
        {
            var phi = CalculatePhi(pattern);

            int m = 0, i = 0;
            while (m < list.Length)
                if (pattern[i] == list[m])
                {
                    i++;
                    if (i == pattern.Length)
                    {
                        yield return m - i + 1;
                        i = phi[i];
                    }
                    m++;
                }
                else if (i > 0)
                {
                    i = phi[i];
                }
                else
                {
                    i = 0;
                    m++;
                }
        }

        [Fact]
        public void BasicTest()
        {
            var pattern = new[] { 1, 1, 10 };
            var list = new[] {2, 4, 1, 1, 1, 10, 1, 5, 1, 1, 10, 9};
            var matches = Search(pattern, list).ToList();

            Assert.Equal(new[] {3, 8}, matches);
        }

        [Fact]
        public void SolveProblem()
        {
            var random = new Random();
            var list = new int[10];
            for (var k = 0; k < list.Length; k++)
                list[k]= random.Next();

            var rotation = new int[list.Length];
            for (var k = 1; k < list.Length; k++)
                rotation[k - 1] = list[k];
            rotation[rotation.Length - 1] = list[0];

            Assert.True(Search(list, rotation.Concat(rotation).ToArray()).Any());
        }
    }

Spero che questo aiuto!


0

Semplificare il problema

  • Il problema è costituito dall'elenco degli articoli ordinati
  • Il dominio del valore è binario (0,1)
  • Possiamo ridurre il problema mappando 1i numeri consecutivi in un conteggio
  • e 0s consecutive in un conteggio negativo

Esempio

A = [ 1, 1, 1, 0, 0, 1, 1, 0 ]
B = [ 1, 1, 0, 1, 1, 1, 0, 0 ]
~
A = [ +3, -2, +2, -1 ]
B = [ +2, -1, +3, -2 ]
  • Questo processo richiede che il primo e l'ultimo articolo debbano essere diversi
  • Ciò ridurrà la quantità di confronti complessivi

Verifica del processo

  • Se assumiamo che siano duplicati, allora possiamo assumere ciò che stiamo cercando
  • Fondamentalmente il primo elemento del primo elenco deve esistere da qualche parte nell'altro elenco
  • Seguito da ciò che è seguito nel primo elenco e nello stesso modo
  • Gli elementi precedenti dovrebbero essere gli ultimi elementi del primo elenco
  • Poiché è circolare, l'ordine è lo stesso

La presa

  • La domanda qui è da dove cominciare, tecnicamente noto come lookupelook-ahead
  • Verificheremo solo dove esiste il primo elemento del primo elenco attraverso il secondo elenco
  • La probabilità di un elemento frequente è inferiore dato che abbiamo mappato le liste in istogrammi

Pseudo-Code

FUNCTION IS_DUPLICATE (LIST L1, LIST L2) : BOOLEAN

    LIST A = MAP_LIST(L1)
    LIST B = MAP_LIST(L2)

    LIST ALPHA = LOOKUP_INDEX(B, A[0])

    IF A.SIZE != B.SIZE
       OR COUNT_CHAR(A, 0) != COUNT_CHAR(B, ALPHA[0]) THEN

        RETURN FALSE

    END IF

    FOR EACH INDEX IN ALPHA

        IF ALPHA_NGRAM(A, B, INDEX, 1) THEN

            IF IS_DUPLICATE(A, B, INDEX) THEN

                RETURN TRUE

            END IF

        END IF

    END FOR

    RETURN FALSE

END FUNCTION

FUNCTION IS_DUPLICATE (LIST L1, LIST L2, INTEGER INDEX) : BOOLEAN

    INTEGER I = 0

    WHILE I < L1.SIZE DO

        IF L1[I] != L2[(INDEX+I)%L2.SIZE] THEN

            RETURN FALSE

        END IF

        I = I + 1

    END WHILE

    RETURN TRUE

END FUNCTION

funzioni

  • MAP_LIST(LIST A):LIST MAPPA ELEMENTI CONSQUETIVI COME CONTI IN UNA NUOVA ELENCO

  • LOOKUP_INDEX(LIST A, INTEGER E):LISTELENCO DI RITORNI DEGLI INDICI DOVE ESISTONO ELEMENTO ENELL'ELENCOA

  • COUNT_CHAR(LIST A , INTEGER E):INTEGERCONTA QUANTI TEMPI ESI PRESENTA UN ELEMENTO IN UN ELENCOA

  • ALPHA_NGRAM(LIST A,LIST B,INTEGER I,INTEGER N):BOOLEANCONTROLLARE SE B[I]È EQUIVALENTE A[0] N-GRAMIN ENTRAMBE LE DIREZIONI


Finalmente

Se la dimensione dell'elenco sarà abbastanza grande o se l'elemento da cui stiamo iniziando a controllare il ciclo è spesso elevato, allora possiamo fare quanto segue:

  • Cerca l'elemento meno frequente nel primo elenco con cui iniziare

  • aumentare il parametro N-grammo per ridurre la probabilità di passare attraverso un controllo lineare


0

Una "forma canonica" efficiente e rapida da calcolare per gli elenchi in questione può essere derivata come:

  • Contare il numero di zero tra quelli (ignorando il giro), per ottenere tre numeri.
  • Ruota i tre numeri in modo che il numero più grande sia il primo.
  • Il primo numero ( a) deve essere compreso tra 18e 52(compreso). Ricodificalo come tra 0e 34.
  • Il secondo numero ( b) deve essere compreso tra 0e 26, ma non importa molto.
  • Rilascia il terzo numero, poiché è giusto 52 - (a + b)e non aggiunge informazioni

La forma canonica è l'intero b * 35 + a, che è tra 0e 936(compreso), che è abbastanza compatto (ci sono 477elenchi circolari univoci in totale).


0

Ho scritto una soluzione semplice che confronta entrambe le liste e aumenta (e avvolge) l'indice del valore confrontato per ogni iterazione.

Non conosco bene Python, quindi l'ho scritto in Java, ma è davvero semplice, quindi dovrebbe essere facile adattarlo a qualsiasi altra lingua.

Con questo puoi anche confrontare elenchi di altri tipi.

public class Main {

    public static void main(String[] args){
        int[] a = {0,1,1,1,0};
        int[] b = {1,1,0,0,1};

        System.out.println(isCircularIdentical(a, b));
    }

    public static boolean isCircularIdentical(int[] a, int[]b){
        if(a.length != b.length){
            return false;
        }

        //The outer loop is for the increase of the index of the second list
        outer:
        for(int i = 0; i < a.length; i++){
            //Loop trough the list and compare each value to the according value of the second list
            for(int k = 0; k < a.length; k++){
                // I use modulo length to wrap around the index
                if(a[k] != b[(k + i) % a.length]){
                    //If the values do not match I continue and shift the index one further
                    continue outer;
                }
            }
            return true;
        }
        return false;
    }
}

0

Come altri hanno già detto, una volta trovata la rotazione normalizzata di un elenco, è possibile confrontarli.

Ecco qualche codice funzionante che lo fa, il metodo di base è quello di trovare una rotazione normalizzata per ogni elenco e confrontare:

  • Calcola un indice di rotazione normalizzato su ciascun elenco.
  • Scorri su entrambe le liste con i loro offset, confrontando ogni elemento, ritornando se corrispondono male.

Si noti che questo metodo è che non dipende dai numeri, è possibile passare in elenchi di stringhe (tutti i valori che possono essere confrontati).

Invece di fare una ricerca nella lista, sappiamo che vogliamo che la lista inizi con il valore minimo, quindi possiamo passare in rassegna i valori minimi, cercando fino a trovare quello che ha i valori successivi più bassi, memorizzandolo per ulteriori confronti finché non avremo il meglio.

Ci sono molte opportunità di uscire presto quando si calcola l'indice, dettagli su alcune ottimizzazioni.

  • Salta la ricerca del miglior valore minimo quando ce n'è solo uno.
  • Salta la ricerca di valori minimi quando anche il precedente è un valore minimo (non sarà mai una corrispondenza migliore).
  • Salta la ricerca quando tutti i valori sono uguali.
  • Fallimento anticipato quando gli elenchi hanno valori minimi diversi.
  • Usa il confronto regolare quando gli offset corrispondono.
  • Regola gli offset per evitare di avvolgere i valori dell'indice su uno degli elenchi durante il confronto.

Si noti che in Python una ricerca in elenco potrebbe essere più veloce, tuttavia ero interessato a trovare un algoritmo efficiente, che potesse essere utilizzato anche in altre lingue. Inoltre, è possibile evitare di creare nuovi elenchi.

def normalize_rotation_index(ls, v_min_other=None):
    """ Return the index or -1 (when the minimum is above `v_min_other`) """

    if len(ls) <= 1:
        return 0

    def compare_rotations(i_a, i_b):
        """ Return True when i_a is smaller.
            Note: unless there are large duplicate sections of identical values,
            this loop will exit early on.
        """
        for offset in range(1, len(ls)):
            v_a = ls[(i_a + offset) % len(ls)]
            v_b = ls[(i_b + offset) % len(ls)]
            if v_a < v_b:
                return True
            elif v_a > v_b:
                return False
        return False

    v_min = ls[0]
    i_best_first = 0
    i_best_last = 0
    i_best_total = 1
    for i in range(1, len(ls)):
        v = ls[i]
        if v_min > v:
            v_min = v
            i_best_first = i
            i_best_last = i
            i_best_total = 1
        elif v_min == v:
            i_best_last = i
            i_best_total += 1

    # all values match
    if i_best_total == len(ls):
        return 0

    # exit early if we're not matching another lists minimum
    if v_min_other is not None:
        if v_min != v_min_other:
            return -1
    # simple case, only one minimum
    if i_best_first == i_best_last:
        return i_best_first

    # otherwise find the minimum with the lowest values compared to all others.
    # start looking after the first we've found
    i_best = i_best_first
    for i in range(i_best_first + 1, i_best_last + 1):
        if (ls[i] == v_min) and (ls[i - 1] != v_min):
            if compare_rotations(i, i_best):
                i_best = i

    return i_best


def compare_circular_lists(ls_a, ls_b):
    # sanity checks
    if len(ls_a) != len(ls_b):
        return False
    if len(ls_a) <= 1:
        return (ls_a == ls_b)

    index_a = normalize_rotation_index(ls_a)
    index_b = normalize_rotation_index(ls_b, ls_a[index_a])

    if index_b == -1:
        return False

    if index_a == index_b:
        return (ls_a == ls_b)

    # cancel out 'index_a'
    index_b = (index_b - index_a)
    if index_b < 0:
        index_b += len(ls_a)
    index_a = 0  # ignore it

    # compare rotated lists
    for i in range(len(ls_a)):
        if ls_a[i] != ls_b[(index_b + i) % len(ls_b)]:
            return False
    return True


assert(compare_circular_lists([0, 9, -1, 2, -1], [-1, 2, -1, 0, 9]) == True)
assert(compare_circular_lists([2, 9, -1, 0, -1], [-1, 2, -1, 0, 9]) == False)
assert(compare_circular_lists(["Hello" "Circular", "World"], ["World", "Hello" "Circular"]) == True)
assert(compare_circular_lists(["Hello" "Circular", "World"], ["Circular", "Hello" "World"]) == False)

Vedi: questo frammento per altri test / esempi.


0

È possibile verificare se un elenco A è uguale a uno spostamento ciclico dell'elenco B nel tempo O (N) previsto abbastanza facilmente.

Userei una funzione di hash polinomiale per calcolare l'hash dell'elenco A e ogni spostamento ciclico dell'elenco B. Se uno spostamento dell'elenco B ha lo stesso hash dell'elenco A, confronterei gli elementi effettivi per vedere se sono uguali .

La ragione per cui questo è veloce è che con le funzioni di hash polinomiale (che sono estremamente comuni!), Puoi calcolare l'hash di ogni spostamento ciclico da quello precedente in tempo costante, in modo da poter calcolare gli hash per tutti gli spostamenti ciclici in O ( N) tempo.

Funziona così:

Diciamo che B ha N elementi, quindi l'hash di B che usa P primo è:

Hb=0;
for (i=0; i<N ; i++)
{
    Hb = Hb*P + B[i];
}

Questo è un modo ottimizzato per valutare un polinomio in P ed è equivalente a:

Hb=0;
for (i=0; i<N ; i++)
{
    Hb += B[i] * P^(N-1-i);  //^ is exponentiation, not XOR
}

Notare come ogni B [i] viene moltiplicato per P ^ (N-1-i). Se spostiamo B a sinistra per 1, allora ogni B [i] verrà moltiplicato per una P in più, tranne il primo. Poiché la moltiplicazione si distribuisce sull'addizione, possiamo moltiplicare tutti i componenti contemporaneamente semplicemente moltiplicando l'intero hash e quindi aggiustando il fattore per il primo elemento.

L'hash dello spostamento a sinistra di B è giusto

Hb1 = Hb*P + B[0]*(1-(P^N))

Il secondo turno a sinistra:

Hb2 = Hb1*P + B[1]*(1-(P^N))

e così via...

NOTA: tutti i calcoli sopra riportati vengono eseguiti con alcune dimensioni della parola macchina e devi calcolare P ^ N una sola volta.


-1

Per incollare il modo più pitonico per farlo, usa i set!

from sets import Set
a = Set ([1, 1, 1, 0, 0])
b = Set ([0, 1, 1, 1, 0]) 
c = Set ([1, 0, 0, 1, 1])
a==b
True
a==b==c
True

questo corrisponderebbe anche alle stringhe con lo stesso numero di 0 e 1 non necessariamente nello stesso ordine
GeneralBecos

GeneralBecos: basta selezionare quelle stringhe e controllare l'ordine in una seconda fase
Louis

Non sono nello stesso ordine lineare. Sono nello stesso ordine "circolare". Quello che descrivi come passaggio 2 è il problema originale.
GeneralBecos,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.