Por exemplo, eu tenho listas:
a[0] = [1, 1, 1, 0, 0]
a[1] = [1, 1, 0, 0, 1]
a[2] = [0, 1, 1, 1, 0]
# and so on
Eles parecem ser diferentes, mas se supõe que o início e o fim estão conectados, eles são circularmente idênticos.
O problema é que cada lista que tenho tem um comprimento de 55 e contém apenas três e 52 zeros. Sem condição circular, existem 26.235 (55 escolha 3) listas. No entanto, se a condição 'circular' existir, haverá um grande número de listas circularmente idênticas
Atualmente, verifico circularmente a identidade, seguindo:
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
Esta função requer 55 operações de mudança cíclica na pior das hipóteses. E existem 26.235 listas para serem comparadas entre si. Em resumo, preciso de 55 * 26.235 * (26.235 - 1) / 2 = 18.926.847.225 cálculos. São cerca de 20 Giga!
Existe alguma maneira de fazer isso com menos cálculos? Ou algum tipo de dado que suporte circular ?
Respostas:
Primeiramente, isso pode ser feito em
O(n)
termos do tamanho da lista. Você pode notar que, se duplicar sua lista duas vezes ([1, 2, 3]
),[1, 2, 3, 1, 2, 3]
então sua nova lista conterá definitivamente todas as listas cíclicas possíveis.Então, tudo que você precisa é verificar se a lista que você está pesquisando está dentro de 2 vezes a sua lista inicial. Em python, você pode conseguir isso da seguinte maneira (assumindo que os comprimentos sejam iguais).
Alguma explicação sobre o meu oneliner:
list * 2
combinará uma lista consigo mesma,map(str, [1, 2])
converterá todos os números em string e' '.join()
converterá array['1', '2', '111']
em uma string'1 2 111'
.Conforme apontado por algumas pessoas nos comentários, o oneliner pode potencialmente fornecer alguns falsos positivos, de modo a cobrir todos os possíveis casos extremos:
PS1 ao falar sobre complexidade de tempo, vale notar que
O(n)
será alcançado se a substring puder ser encontrada aO(n)
tempo. Nem sempre é assim e depende da implementação em seu idioma ( embora potencialmente possa ser feito de forma linear tempo KMP, por exemplo).PS2 para pessoas que estão com medo de operação de cordas e, devido a esse fato, pensam que a resposta não é boa. O que é importante é complexidade e velocidade. Esse algoritmo é potencialmente executado no
O(n)
tempo e noO(n)
espaço, o que o torna muito melhor do que qualquer coisa noO(n^2)
domínio. Para ver isso sozinho, você pode executar uma pequena referência (cria uma lista aleatória que aparece o primeiro elemento e o anexa ao final, criando assim uma lista cíclica. Você é livre para fazer suas próprias manipulações)0,3 segundos na minha máquina. Não é muito longo. Agora tente comparar isso com as
O(n^2)
soluções. Enquanto o compara, você pode viajar dos EUA para a Austrália (provavelmente por um navio de cruzeiro)fonte
Não conhecedor o suficiente em Python para responder a isso na linguagem solicitada, mas em C / C ++, dados os parâmetros de sua pergunta, eu converteria os zeros e uns em bits e os empurraria para os bits menos significativos de um uint64_t. Isso permitirá que você compare todos os 55 bits de uma só vez - 1 relógio.
Muito rápido, e tudo se encaixa nos caches no chip (209.880 bytes). O suporte de hardware para mudar todos os 55 membros da lista para a direita simultaneamente está disponível apenas nos registros de uma CPU. O mesmo vale para comparar todos os 55 membros simultaneamente. Isso permite um mapeamento 1 por 1 do problema para uma solução de software. (e usando os registros SIMD / SSE de 256 bits, até 256 membros, se necessário) Como resultado, o código é imediatamente óbvio para o leitor.
Você pode implementar isso em Python, mas não o conheço o suficiente para saber se isso é possível ou qual pode ser o desempenho.
Depois de dormir, algumas coisas se tornaram óbvias, e tudo para melhor.
1.) É tão fácil girar a lista vinculada circularmente usando bits que o truque muito inteligente de Dali não é necessário. Dentro de um registro de 64 bits, a troca de bits padrão realizará a rotação de maneira muito simples e na tentativa de tornar tudo mais amigável ao Python, usando aritmética em vez de operações de bits.
2.) A troca de bits pode ser realizada facilmente usando a divisão por 2.
3.) Verificar o final da lista como 0 ou 1 pode ser facilmente realizado pelo módulo 2.
4.) "Mover" um 0 para o início da lista a partir da cauda pode ser feito dividindo-se por 2. Isso porque se o zero fosse realmente movido, tornaria o 55º bit falso, o que já é feito sem absolutamente nada.
5.) "Mover" um 1 para o topo da lista a partir da cauda pode ser feito dividindo por 2 e adicionando 18.014.398.509.481.984 - que é o valor criado pela marcação do 55º bit como verdadeiro e o restante como falso.
6.) Se uma comparação da âncora e do uint64_t composto for TRUE após uma rotação, interrompa e retorne TRUE.
Eu converteria toda a matriz de listas em uma matriz de uint64_ts logo no início, para evitar a conversão repetida.
Depois de passar algumas horas tentando otimizar o código, estudando a linguagem assembly, consegui economizar 20% do tempo de execução. Devo acrescentar que o compilador O / S e MSVC também foi atualizado no meio do dia ontem. Por qualquer motivo, a qualidade do código que o compilador C produziu melhorou drasticamente após a atualização (15/11/2014). O tempo de execução agora é de ~ 70 relógios, 17 nanossegundos para compor e comparar um anel de ancoragem com todas as 55 voltas de um anel de teste e o NxN de todos os anéis contra todos os outros é feito em 12,5 segundos .
Esse código é tão restrito, com exceção de quatro registros, sem fazer nada em 99% do tempo. A linguagem assembly corresponde ao código C quase linha por linha. Muito fácil de ler e entender. Um ótimo projeto de montagem se alguém estivesse ensinando isso a si mesmo.
O hardware é Hazwell i7, MSVC de 64 bits, otimizações completas.
fonte
Lendo nas entrelinhas, parece que você está tentando enumerar um representante de cada classe de equivalência circular de strings com 3 e 52 zeros. Vamos mudar de uma representação densa para uma esparsa (conjunto de três números
range(55)
). Nesta representação, o deslocamento circular des
pork
é dado pela compreensãoset((i + k) % 55 for i in s)
. O representante mínimo lexicographic em uma classe contém sempre a posição 0. Dado um conjunto da forma{0, i, j}
com0 < i < j
, os outros candidatos para mínima da classe são{0, j - i, 55 - i}
e{0, 55 - j, 55 + i - j}
. Portanto, precisamos(i, j) <= min((j - i, 55 - i), (55 - j, 55 + i - j))
que o original seja mínimo. Aqui está um código de enumeração.fonte
Repita a primeira matriz e use o algoritmo Z (O (n) time) para encontrar a segunda matriz dentro da primeira.
(Nota: você não precisa copiar fisicamente a primeira matriz. Você pode apenas girar durante a correspondência.)
O bom do algoritmo Z é que ele é muito simples comparado ao KMP, BM etc.
No entanto, se você estiver se sentindo ambicioso, poderá fazer a correspondência de strings em tempo linear e espaço constante -
strstr
por exemplo, faz isso. Implementá-lo seria mais doloroso, no entanto.fonte
Seguindo a solução muito inteligente de Salvador Dali, a melhor maneira de lidar com isso é garantir que todos os elementos tenham o mesmo comprimento, assim como os dois LISTAS tenham o mesmo comprimento.
Não há pista se isso é mais rápido ou mais lento que a solução de regex recomendada por AshwiniChaudhary na resposta de Salvador Dali, que diz:
fonte
str.format
n
horários para formatar a sequência resultante. SUPONHO .... :)Dado que você precisa fazer tantas comparações, vale a pena dar uma passada inicial em suas listas para convertê-las em algum tipo de forma canônica que pode ser facilmente comparada?
Você está tentando obter um conjunto de listas circularmente exclusivas? Nesse caso, você pode jogá-los em um conjunto depois de converter em tuplas.
Desculpas a David Eisenstat por não ter encontrado sua resposta semelhante.
fonte
Você pode rolar uma lista como esta:
fonte
Primeiro converter todos os de sua lista elementos (em uma cópia, se necessário) para que versão rotacionada que é lexicamente maior.
Em seguida, classifique a lista de listas resultante (mantendo um índice na posição da lista original) e unifique a lista classificada, marcando todas as duplicatas na lista original, conforme necessário.
fonte
Pegando carona na observação do @ SalvadorDali ao procurar correspondências de a em qualquer fatia de tamanho a em b + b, aqui está uma solução usando apenas operações de lista.
Segunda abordagem: [excluído]
fonte
rollmatch([1, 0, 1, 1], [0, 1, 1, 1])
.Não é uma resposta completa e independente, mas no tópico de otimizar reduzindo comparações, eu também estava pensando em representações normalizadas.
Ou seja, se o alfabeto de entrada for {0, 1}, você poderá reduzir significativamente o número de permutações permitidas. Gire a primeira lista para uma forma (pseudo-) normalizada (dada a distribuição em sua pergunta, eu escolheria uma em que um dos 1 bits esteja na extrema esquerda e um dos 0 bits na extrema direita). Agora, antes de cada comparação, gire sucessivamente a outra lista pelas posições possíveis com o mesmo padrão de alinhamento.
Por exemplo, se você tiver um total de quatro bits 1, pode haver no máximo 4 permutações com esse alinhamento e se você tiver grupos de 1 bits adjacentes, cada bit adicional nesse cluster reduzirá a quantidade de posições.
Isso generaliza para alfabetos maiores e diferentes padrões de alinhamento; o principal desafio é encontrar uma boa normalização com apenas algumas representações possíveis. Idealmente, seria uma normalização adequada, com uma única representação única, mas, dado o problema, não acho possível.
fonte
Aprofundando a resposta do RocketRoy: converta todas as suas listas antecipadamente em números de 64 bits não assinados. Para cada lista, gire esses 55 bits para encontrar o menor valor numérico.
Agora você tem um único valor de 64 bits não assinado para cada lista que você pode comparar diretamente com o valor das outras listas. A função is_circular_identical () não é mais necessária.
(Em essência, você cria um valor de identidade para suas listas que não é afetado pela rotação dos elementos das listas). Isso funcionaria mesmo se você tivesse um número arbitrário em suas listas.
fonte
Essa é a mesma idéia de Salvador Dali, mas não precisa da conversão de strings. Atrás está a mesma idéia de recuperação do KMP para evitar uma inspeção de turno impossível. Eles chamam apenas KMPModified (lista1, lista2 + lista2).
Espero que esta ajuda!
fonte
Simplificando o problema
(0,1)
1
s consecutivos em uma contagem0
s consecutivos em uma contagem negativaExemplo
Processo de Verificação
A aderência
lookup
elook-ahead
Pseudo-código
Funções
MAP_LIST(LIST A):LIST
MAPA DE ELEMENTOS CONSQUETIVOS COMO CONTA EM UMA NOVA LISTALOOKUP_INDEX(LIST A, INTEGER E):LIST
LISTA DE RETORNO DE ÍNDICES EM QUE O ELEMENTOE
EXISTE NA LISTAA
COUNT_CHAR(LIST A , INTEGER E):INTEGER
CONTAR QUANTAS VEZES UM ELEMENTOE
OCORRE EM UMA LISTAA
ALPHA_NGRAM(LIST A,LIST B,INTEGER I,INTEGER N):BOOLEAN
VERIFIQUE SEB[I]
É EQUIVALENTEA[0]
N-GRAM
EM DUAS DIREÇÕESFinalmente
Se o tamanho da lista for muito grande ou se o elemento do qual estamos começando a verificar o ciclo for frequentemente alto, podemos fazer o seguinte:
Procure o item menos frequente na primeira lista para começar
aumente o parâmetro n-gram N para diminuir a probabilidade de passar por uma verificação linear
fonte
Uma "forma canônica" eficiente e rápida de calcular para as listas em questão pode ser derivada como:
a
) deve estar entre18
e52
(inclusive). Recodifique-o como entre0
e34
.b
) deve estar entre0
e26
, mas não importa muito.52 - (a + b)
e não adiciona informaçãoA forma canônica é o número inteiro
b * 35 + a
, que está entre0
e936
(inclusive), que é bastante compacto (existem477
listas circularmente exclusivas no total).fonte
Eu escrevi uma solução simples que compara as duas listas e apenas aumenta (e envolve) o índice do valor comparado para cada iteração.
Como eu não conheço bem python, escrevi em Java, mas é realmente simples, portanto deve ser fácil adaptá-lo a qualquer outra linguagem.
Por isso, você também pode comparar listas de outros tipos.
fonte
Como outros já mencionaram, depois de encontrar a rotação normalizada de uma lista, você pode compará-las.
Heres algum código de trabalho que faz isso, o método básico é encontrar uma rotação normalizada para cada lista e comparar:
Note que este método é que não depende de números, você pode passar listas de strings (quaisquer valores que possam ser comparados).
Em vez de fazer uma pesquisa de lista na lista, sabemos que queremos que a lista comece com o valor mínimo - para que possamos percorrer os valores mínimos, pesquisando até encontrar qual deles tem os menores valores sucessivos, armazenando-o para comparações adicionais até que tenhamos o melhor.
Existem muitas oportunidades para sair mais cedo ao calcular o índice, detalhes sobre algumas otimizações.
Observe que no Python uma pesquisa de lista na lista pode muito bem ser mais rápida, no entanto, eu estava interessado em encontrar um algoritmo eficiente - que também pudesse ser usado em outros idiomas. Além disso, há algumas vantagens em evitar criar novas listas.
Veja: esse trecho para algumas mais testes / exemplos.
fonte
Você pode verificar se uma lista A é igual a uma mudança cíclica da lista B no tempo esperado de O (N) com bastante facilidade.
Eu usaria uma função de hash polinomial para calcular o hash da lista A e todos os turnos cíclicos da lista B. Onde um turno da lista B tem o mesmo hash da lista A, comparo os elementos reais para ver se são iguais .
A razão disso é rápido é que, com funções de hash polinomial (que são extremamente comuns!), É possível calcular o hash de cada mudança cíclica da anterior em tempo constante, para que você possa calcular hashes para todas as mudanças cíclicas em O ( N) tempo.
Funciona assim:
Digamos que B tenha N elementos, então o hash de B usando P primo é:
Essa é uma maneira otimizada de avaliar um polinômio em P e é equivalente a:
Observe como todo B [i] é multiplicado por P ^ (N-1-i). Se deslocarmos B para a esquerda por 1, todos os B [i] serão multiplicados por um P extra, exceto o primeiro. Como a multiplicação distribui pela adição, podemos multiplicar todos os componentes de uma só vez multiplicando todo o hash e, em seguida, fixar o fator para o primeiro elemento.
O hash do deslocamento esquerdo de B é apenas
O segundo turno esquerdo:
e assim por diante...
NOTA: toda a matemática acima é executada em módulos com tamanho de palavra de máquina e você só precisa calcular P ^ N uma vez.
fonte
Para colar da maneira mais pitônica, use conjuntos!
fonte