Python'da iki listenin dairesel olarak aynı olup olmadığı nasıl kontrol edilir


145

Örneğin, listelerim var:

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

Farklı görünüyorlar, ancak eğer başlangıç ​​ve sonun bağlı olduğu varsayılırsa, dairesel olarak aynıdırlar.

Sorun şu ki, sahip olduğum her listenin uzunluğu 55 ve içinde sadece üç tane ve 52 tane sıfır var. Dairesel koşul olmadan, 26.235 (55 seçim 3) listesi vardır. Bununla birlikte, 'dairesel' koşulu mevcutsa, çok sayıda dairesel olarak özdeş liste vardır

Şu anda dairesel kimliğini aşağıdaki gibi kontrol ediyorum:

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

Bu işlev en kötü durumda 55 döngüsel vites değiştirme işlemi gerektirir. Ve birbirleriyle karşılaştırılacak 26.235 liste var. Kısacası, 55 * 26.235 * (26.235-1) / 2 = 18.926.847.225 hesaplamaya ihtiyacım var. Yaklaşık 20 Giga!

Daha az hesaplama ile yapmanın iyi bir yolu var mı? Veya genelge destekleyen herhangi bir veri türü var mı?


Sadece bir önsezi: Ek ağaçların burada yardımcı olabileceğini hissediyorum. en.wikipedia.org/wiki/Suffix_tree . Yapı tek için, bkz en.wikipedia.org/wiki/Ukkonen%27s_algorithm
Rerito

1
@Mehrdad Ama kanonik bir biçime dönüşen herhangi bir cevaptan çok daha kötü, tamsayıya dönüştürmekten çok daha kötü çalışma süresi ve David Eisenstat'ınkinden çok daha kötü çalışma süresi.
Veedrac

2
Tüm cevaplar genel problemi çözmeye çalışıyor, ancak bu özel durumda sadece 3 tane ile her listeyi temsil edebiliyorsunuz, 3 sayı her biri arasında sıfırlar. Söz konusu liste [0,0,2], [0,2,0], [2,0,0] olarak temsil edilebilir. Sadece bir seferde listeyi azaltabilir ve daha sonra indirgenmiş listeyi kontrol edebilirsiniz. "Dairesel olarak özdeş" ise orijinaller de öyle.
abc667

1
Sanırım Stack Overflow'un oylamaya ihtiyacı yok. Tek ihtiyacımız olan kodu tüm çözümlerde çalıştırmak ve bittikleri sırayla sunmaktır.
Dawood ibn Kareem

2
Bugüne kadar söz edilmemiştir beri "kanonik form" @ abc667, Veedrac tarafından sevk ve Eisenstat Çalışma Uzunluğu Kodlama denir en.wikipedia.org/wiki/Run-length_encoding
David Lovell

Yanıtlar:


133

Öncelikle, bu O(n)listenin uzunluğu açısından yapılabilir . Listenizi 2 kez çoğaltırsanız ( [1, 2, 3]) olacak [1, 2, 3, 1, 2, 3], yeni listeniz kesinlikle tüm olası döngüsel listeleri tutacaktır.

Tek yapmanız gereken, aradığınız listenin başlangıç ​​listenizin 2 katı içinde olup olmadığını kontrol etmektir. Python'da bunu aşağıdaki şekilde başarabilirsiniz (uzunlukların aynı olduğu varsayılarak).

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

Oneliner'imle ilgili bazı açıklamalar: list * 2bir listeyi kendisiyle birleştirir, map(str, [1, 2])tüm sayıları dizeye ' '.join()dönüştürür ve diziyi ['1', '2', '111']dizeye dönüştürür '1 2 111'.

Yorumlarda bazı kişilerin işaret ettiği gibi, oneliner potansiyel olarak bazı yanlış pozitifler verebilir, böylece olası tüm uç durumları kapsayabilir:

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 , zaman karmaşıklığından bahsederken, O(n)alt dize O(n)zamanında bulunabilirse elde edilebileceğini fark etmeye değer . Her zaman böyle değildir ve dilinizdeki uygulamaya bağlıdır ( potansiyel olarak örneğin doğrusal zaman KMP'de yapılabilir ).

PS2 dizeleri operasyon korkuyor insanlar ve bu nedenle cevap iyi olmadığını düşünüyorum. Önemli olan karmaşıklık ve hızdır. Bu algoritma potansiyel olarak O(n)zaman ve O(n)mekanda çalışır ve bu da O(n^2)etki alanındaki her şeyden çok daha iyi olmasını sağlar . Bunu kendiniz görmek için küçük bir kriter çalıştırabilirsiniz (rastgele bir liste oluşturur, ilk öğeyi açar ve sonuna ekler, böylece döngüsel bir liste oluşturur. Kendi manipülasyonlarınızı yapmakta özgürsünüz)

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

Makinemde 0,3 saniye. Gerçekten uzun değil. Şimdi bunu O(n^2)çözümlerle karşılaştırmayı deneyin . Karşılaştırma yaparken, ABD'den Avustralya'ya seyahat edebilirsiniz (büyük olasılıkla bir yolcu gemisi ile)


3
Sadece dolgu boşlukları eklemek (her dizeden önce 1 ve sonra 1) işinizi görecektir. Regexes ile işleri aşırı karmaşıklaştırmaya gerek yok. (Tabii ki aynı uzunluktaki listeleri karşılaştırdığımızı varsayıyorum)
Rerito

2
@Rerito, her iki listede de önde veya arkada boşluk olabilecek dizeler içermiyorsa. Yine de çarpışmalara neden olabilir.
Adam Smith

12
Bu cevabı sevmiyorum. Dize operasyonu saçmalıktan hoşlanmama neden oldu ve David Eisenstat'ın cevabı beni aşağılamak istedi. Bu karşılaştırma yapabilirsiniz iple O (n) zaman içinde yapılabilir ama aynı zamanda bir tamsayı ile O (n) zaman içinde yapılabilir [olarak ihtiyaç 10k kendini silinmiş], hızlıdır. Bununla birlikte, David Eisenstat'ın cevabı, herhangi bir karşılaştırma yapmanın anlamsız olduğunu, çünkü cevabın buna ihtiyacı olmadığını gösteriyor.
Veedrac

7
@Veedrac benimle dalga mı geçiyorsun? Hesaplama karmaşıklığını duydunuz mu? Davids cevabı, 10 ^ 4 uzunluğundaki küçük girişler için bile 22 saniye süren ve ne kadar koç olduğunu bilen tüm tekrarlarını üretmek için O (n ^ 2) zamanını ve O (n ^ 2) alanını alır. Şimdilik bir şey aramaya başlamadığımızdan bahsetmiyoruz (sadece tüm döngüsel rotasyonları ürettik). Ve benim dize saçmalığım, 10 saniyeden daha kısa sürede 10 ^ 6 gibi girişler için tam bir sonuç verir. Ayrıca saklamak için O (n) boşluğuna ihtiyaç duyar. Bu yüzden lütfen sonuca varmadan önce cevabı anlamak için biraz zaman ayırın.
Salvador Dali

1
@SalvadorDali Çok (yumuşak) zaman odaklı görünüyorsun ;-)
e2-e4

38

Python'da bunu istediğiniz dilde cevaplayacak kadar bilgili değil, ancak C / C ++ 'da, sorunuzun parametreleri göz önüne alındığında, sıfırları ve olanları bitlere dönüştürüp uint64_t'nin en az önemli bitlerine iterdim. Bu, 55 bitin hepsini bir düşme taklidi ile karşılaştırmanıza olanak tanır - 1 saat.

Çok hızlı ve her şey çipli önbelleklere (209.880 bayt) sığacak. 55 liste üyesinin tümünü aynı anda kaydırmak için donanım desteği yalnızca CPU kayıtlarında mevcuttur. Aynı şey 55 üyenin tümünü aynı anda karşılaştırmak için de geçerlidir. Bu, sorunun bir yazılım çözümü ile bire bir eşlenmesini sağlar. (ve gerekirse SIMD / SSE 256 bit kayıtlarını kullanarak 256 üyeye kadar) Sonuç olarak kod okuyucu tarafından hemen anlaşılır.

Bunu Python'da uygulayabilirsiniz, bunun mümkün olup olmadığını veya performansın ne olabileceğini bilmek için yeterince iyi bilmiyorum.

Üzerinde uyuduktan sonra birkaç şey belirginleşti ve hepsi daha iyisi için.

1.) Dali'nin akıllıca numarasına gerek duymadan dairesel olarak bağlı listeyi bitler kullanarak döndürmek o kadar kolay ki. 64 bitlik bir kayıtta standart bit kaydırma işlemi, dönüşü çok basit bir şekilde gerçekleştirir ve bit ops yerine aritmetik kullanarak bunu daha Python dostu hale getirmeye çalışır.

2.) Bit kaydırma 2'ye bölünerek kolayca yapılabilir.

3.) Listenin sonunun 0 veya 1 olup olmadığını kontrol etmek modulo 2 ile kolayca yapılabilir.

4.) Bir 0'ı listenin başından kuyruğa "taşımak", 2'ye bölünerek yapılabilir. Bu, eğer sıfır gerçekten hareket ettirilirse, 55. biti yanlış yapar, ki zaten hiçbir şey yapmaz.

5.) a 1'in listenin başından kuyruğa "taşınması" 2'ye bölünerek ve 55. bit doğru ve geri kalan tüm yanlışları işaretleyerek yaratılan değer olan 18,014,398,509,481,984 eklenerek yapılabilir.

6.) Herhangi bir döndürmeden sonra çapanın ve uint64_t öğesinin karşılaştırması TRUE ise, TRUE değerini kesin ve döndürün.

Tekrar tekrar dönüşüm yapmak zorunda kalmamak için listelerin tüm dizisini uint64_ts dizisinin hemen önüne dönüştürürdüm.

Kodu optimize etmek için birkaç saat geçirdikten sonra, montaj dilini inceleyerek çalışma süresinde% 20 tıraş edebildim. O / S ve MSVC derleyicisinin dün gün ortasında da güncellendiğini eklemeliyim. Hangi nedenle olursa olsun, C derleyicisinin ürettiği kodun kalitesi, güncellemeden sonra önemli ölçüde arttı (15/11/2014). Çalışma zamanı artık ~ 70 saat, bir çapa halkası oluşturmak ve bir test halkasının 55 turunun hepsini karşılaştırmak için 17 nanosaniyedir ve tüm halkaların diğerlerine karşı NxN'si 12.5 saniyede yapılır .

Bu kod o kadar sıkı ki 4 kayıt dışında% 99 hiçbir şey yapmadan oturuyorlar. Montaj dili, C kodunu neredeyse satır için eşleştirir. Okuması ve anlaması çok kolay. Birisi kendisine bunu öğretiyor olsaydı harika bir montaj projesi.

Donanım, Hazwell i7, MSVC 64 bit, tam optimizasyonlardır.

#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;
}

resim açıklamasını buraya girin


23
insanlar "salvador dali'nin çözümü" hakkında konuşmaya devam ediyorlardı ve burada sadece karışık bir şekilde oturuyordum, aynı ismin ressamının klasik algoritmalara önemli bir şekilde katkıda bulunan bir matematikçi olup olmadığını merak ediyorum. sonra en popüler yanıtı yayınlayan kişinin kullanıcı adı olduğunu fark ettim. ben akıllı bir adam değilim.
Woodrow Barlow

10k rep ve herkes için Numpy ve vectorization kullanarak uygulama mevcuttur . Ayna <10k olanlar için ayna . Cevabımı silindi çünkü David Eisenstat cevabı sen karşılaştırmalar yapmak gerekmez Puan hiç sadece hemen eşsiz listeleri oluşturabilir ve ben onun çok daha iyi cevap kullanmaya teşvik etmek istiyorum.
Veedrac

@RocketRoy Sizce Python neden biraz işlem yapmıyor? Heck, bağladığım kodda bit işlemleri kullanıyorum . Hala bu cevabın gereksiz olduğunu düşünüyorum (David Eisenstat'ın cevabı her şey için 1 ms sürüyor), ama bu ifadeyi garip buldum. Numpy'de 262M- "listeler" araması için benzer bir algoritma olan FWIW, bilgisayarımda yaklaşık 15 saniye sürüyor (eşleşme bulunmadığı varsayılarak), listenin dönüşü iç döngüde değil, dış döngüde gerçekleşir.
Veedrac

@Quincunx, C ++ için sözdizimi renklendirmesini doğru şekilde düzenlemenizi sağladığınız için teşekkür ederiz. Oldukça memnun olarak!

@RocketRoy Sorun değil. PPCG ile ilgili birçok soruyu cevapladığınızda , sözdizimi renginin nasıl yapılacağını öğrenirsiniz.
Justin

33

Çizgiler arasında okuma yapmak, 3 tane ve 52 sıfır ile her bir dairesel eşdeğerlik dizesi sınıfının bir temsilcisini numaralandırmaya çalıştığınıza benziyor. Yoğun bir gösterimden seyrek bir gösterime geçelim (üç sayı kümesi range(55)). Bu gösterimde, dairesel kayma ssaldırıdaki kanlama verilir set((i + k) % 55 for i in s). Bir sınıfta lexicographic asgari temsilcisi daima 0. formunun bir dizi Verilen pozisyonunu içeren {0, i, j}ile 0 < i < jsınıfta en az diğer adaylardır {0, j - i, 55 - i}ve {0, 55 - j, 55 + i - j}. Bu nedenle, (i, j) <= min((j - i, 55 - i), (55 - j, 55 + i - j))orijinalin minimum olması gerekir. İşte bazı numaralandırma kodu.

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 Cevabı yanlış anladınız (bunu gösterene kadar ben de yaptım!). Bu doğrudan "3 tane ve 52 sıfırlı dizelerin her dairesel eşdeğerlik sınıfının bir temsilcisini" oluşturur. Kodu tüm döngüsel dönüşleri üretmez. Orijinal maliyet T T'dir (55² · 26235²). Kodunuz 55²'yi 55'e yükseltir, sadece T'dir (55 * 26235²). David Eisenstat'ın yanıtı her şey için 55² ile 55³ arasında . 55³ ≪ 55 · 26235². BigBu durumda büyük O terimleriyle konuşmak, her durumda O (1) 'deki gerçek maliyet değildir.
Veedrac

1
@Veedrac Ancak gelecekte bu soruya gelecek okuyucuların% 99'u kısıtlamalarına sahip olmayacak ve cevabımın onlara daha iyi uyacağına inanıyorum. Konuşmayı daha fazla şişirmeden tam olarak ne istediğini açıklamak için OP'ye gideceğim.
Salvador Dali

5
@SalvadorDali OP, XY Probleminin avına düşmüş gibi görünüyor . Neyse ki, sorunun kendisi başlığın ne yapmadığını açıkça ortaya koyuyor ve David satırlar arasında okuyabiliyordu. Aslında durum buysa, o zaman yapılacak doğru şey başlığı değiştirmek ve soruyu görmezden gelmek yerine başlığı değiştirmek ve asıl sorunu çözmek.
Aaron Dufour

1
@SalvadorDali, kapakların altında Python kodunuz alt dizeyi aramak için bir dizgiyi arayan C'nin strstr () eşdeğerini çağırıyor. Bu da, string1'deki her bir karakteri string2 ile karşılaştıran bir for () döngüsü çalıştıran strcmp () öğesini çağırır. Bu nedenle, O (n) 'ye benzeyen, başarısızlık arayışı varsayarak O (n * 55 * 55)' dir. Üst düzey diller 2 kenarlı bir kılıçtır. Uygulama ayrıntılarını sizden gizlerler, ancak uygulama ayrıntılarını sizden de gizlerler. FWIW, listeyi bitirme anlayışınız mükemmeldi. Donanımda kolayca döndürülebilen uint8 kadar hızlı ve bit kadar hızlı.

2
@AleksandrDubinsky Bilgisayar için daha basit, insanlar için daha karmaşık. Olduğu kadar hızlı.
David Eisenstat

12

İlk diziyi tekrarlayın, ardından ilk dizinin içindeki ikinci diziyi bulmak için Z algoritmasını (O (n) zaman) kullanın.

(Not: ilk diziyi fiziksel olarak kopyalamanız gerekmez. Eşleme sırasında etrafta dolaşabilirsiniz.)

Z algoritması hakkında güzel olan şey , KMP, BM, vs. ile karşılaştırıldığında çok basit olmasıdır.
Ancak, hırslı hissediyorsanız, doğrusal zaman ve sabit alanda dize eşleştirmesi yapabilirsiniz - strströrneğin bunu yapar. Yine de bunu uygulamak daha acı verici olurdu.


6

Salvador Dali'nin çok akıllı çözümünü takip etmenin en iyi yolu, tüm öğelerin aynı uzunlukta olduğundan ve her iki LIST'in de aynı uzunlukta olduğundan emin olmaktır.

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

Bu, AshwiniChaudhary'in Salvador Dali'nin cevabındaki önerilen normal ifade çözümünden daha hızlı veya daha yavaşsa ipucu yok:

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
Salvador Dali'nin cevabını basitçe değiştirdim ve Ashwini'nin değişikliklerini biçimlendirdiğimden beri bunu yaptım. Bunun çok azı aslında benim.
Adam Smith

1
girdi için teşekkür ederim. Düzenlenmiş çözümümdeki tüm olası vakaları ele aldığımı düşünüyorum. Bir şeyin eksik olup olmadığını bana bildirin.
Salvador Dali

@SalvadorDali ah, evet ... tellerin aynı uzunlukta olduğunu kontrol etmek. Ben en uzun eleman arıyor, sonra str.format nelde edilen dize biçimlendirmek için kez çağırıyor liste üzerinden çalışan daha kolay olacağını DÜŞÜNÜN . SEVİYORUM .... :)
Adam Smith

3

Çok fazla karşılaştırma yapmanız gerektiği göz önüne alındığında, listeleri kolayca karşılaştırılabilecek bir tür kanonik forma dönüştürmek için listelerinizden ilk geçiş yaparken size değer olabilir mi?

Bir dizi dairesel benzersiz liste almaya mı çalışıyorsunuz? Eğer öyleyse, tupllere dönüştürdükten sonra onları bir sete atabilirsiniz.

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)

David Eisenstat'a benzer cevabını bulamadığı için özür dileriz.


3

Bir listeyi şu şekilde döndürebilirsiniz:

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

İlk (a kopya gerekirse içinde) liste elemanlarının her dönüştürmek olduğunu lexically en fazla olduğu döndürülmüş sürümü.

Sonra ortaya çıkan liste listesini sıralayın (bir dizini orijinal liste konumuna getirin) ve sıralanmış listeyi birleştirerek orijinal listedeki tüm kopyaları gerektiği gibi işaretleyin.


2

@ SalvadorDali'nin b + b'de herhangi bir uzun boylu dilimdeki eşleşmeleri aramak konusundaki gözleminde piggybacking, işte sadece liste işlemlerini kullanan bir çözüm.

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

2. yaklaşım: [silindi]


İlk sürüm O (n²) ve ikincisi çalışmıyor rollmatch([1, 0, 1, 1], [0, 1, 1, 1]).
Veedrac

Güzel yakaladım, onu sileceğim!
PaulMcG

1

Tam, bağımsız bir cevap değil, ama karşılaştırmaları azaltarak optimize etme konusunda ben de normalleştirilmiş temsiller düşünüyordum.

Yani, giriş alfabeniz {0, 1} ise, izin verilen permütasyon sayısını önemli ölçüde azaltabilirsiniz. İlk listeyi (sözde-) normalleştirilmiş bir forma döndürün (sorunuzdaki dağılım göz önüne alındığında, 1 bitten birinin en solda ve 0 bitten birinin aşırı sağda olduğu bir tane seçerim). Şimdi her karşılaştırmadan önce, diğer listeyi aynı hizalama desenine sahip olası konumlar boyunca arka arkaya döndürün.

Örneğin, toplamda dört adet 1 bitiniz varsa, bu hizalamayla en fazla 4 permütasyon olabilir ve bitişik 1 bitlik kümeleriniz varsa, bu kümedeki her ek bit konumların miktarını azaltır.

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

Bu daha büyük alfabe ve farklı hizalama kalıpları için genelleme yapar; asıl zorluk, sadece birkaç olası temsil ile iyi bir normalleşme bulmaktır. İdeal olarak, tek bir benzersiz temsil ile uygun bir normalleşme olurdu, ancak sorun göz önüne alındığında, bunun mümkün olduğunu düşünmüyorum.


0

RocketRoy'un cevabına daha fazla odaklanmak: Tüm listelerinizi önden işaretsiz 64 bit sayılara dönüştürün. Her liste için, en küçük sayısal değeri bulmak üzere bu 55 biti döndürün.

Artık her liste için diğer listelerin değerleriyle doğrudan karşılaştırabileceğiniz tek bir işaretsiz 64 bit değeri kaldı. Artık is_circular_identical () işlevi gerekli değildir.

(Esasen, listeleriniz için liste öğelerinin rotasyonundan etkilenmeyen bir kimlik değeri oluşturursunuz) Listelerinizde rastgele bir tane varsa bile bu işe yarar.


0

Bu Salvador Dali ile aynı fikir ama dize dönüşümüne gerek yok. Arkasında imkansız vardiya denetimini önlemek için aynı KMP kurtarma fikri. Sadece KMPModified'i çağırırlar (list1, list2 + list2).

    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());
        }
    }

Umarım bu yardım!


0

Sorunu Basitleştirme

  • Sorun, sipariş edilen öğelerin listesinden oluşuyor
  • Değerin etki alanı ikilidir (0,1)
  • Ardışıkları 1bir sayıya eşleyerek sorunu azaltabiliriz
  • ve art arda 0negatif sayı

Misal

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 ]
  • Bu işlem için ilk öğenin ve son öğenin farklı olması gerekir
  • Bu, genel olarak karşılaştırma miktarını azaltacaktır.

Kontrol Süreci

  • Kopya olduklarını varsayarsak, aradığımızı varsayabiliriz
  • Temel olarak, ilk listedeki ilk öğe diğer listede bir yerde olmalıdır
  • Bunu ilk listede ve aynı şekilde takip edilenler izler
  • Önceki öğeler ilk listeden son öğeler olmalıdır
  • Dairesel olduğundan sipariş aynıdır

Kavrama

  • Buradaki soru, teknik olarak lookupvelook-ahead
  • Sadece ikinci listeden birinci listenin ilk elemanının nerede olduğunu kontrol edeceğiz
  • Listeleri histogramlarla eşleştirdiğimiz için sık eleman olasılığı düşüktür

Sözde Kod

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

Fonksiyonlar

  • MAP_LIST(LIST A):LIST REKABETÇİ ELEMANLARI YENİ BİR LİSTEDE ÜLKE HARİTASI

  • LOOKUP_INDEX(LIST A, INTEGER E):LISTELEMANIN LİSTEDE MEVCUT OLDUĞU ENDEKSLER ELİSTESİA

  • COUNT_CHAR(LIST A , INTEGER E):INTEGERCOUNT BİR ELİSTEDE ELEMANIN OLDUĞU BİR KEZA

  • ALPHA_NGRAM(LIST A,LIST B,INTEGER I,INTEGER N):BOOLEANSlayt IF B[I]EŞDEĞER OLMAK A[0] N-GRAMİÇİN İKİ TARİFİ


En sonunda

Liste boyutu oldukça büyük olacaksa veya döngüyü kontrol etmeye başladığımız öğe sık sık yüksekse, aşağıdakileri yapabiliriz:

  • Başlamak için ilk listede en az kullanılan öğeye bakın

  • doğrusal bir kontrolden geçme olasılığını azaltmak için n-gram N parametresini artırın


0

Söz konusu listeler için etkili, hesaplaması hızlı bir "standart form" şu şekilde türetilebilir:

  • Üç sayı elde etmek için, bunlar arasındaki sıfır sayısını sayın (etrafı sararak).
  • Üç sayıyı, en büyük sayı ilk olacak şekilde döndürün.
  • İlk sayı ( a) 18ve 52(dahil) arasında olmalıdır . Arasında olduğu gibi yeniden kodlamak onu 0ve 34.
  • İkinci sayı ( b) arasında olmalıdır 0ve 26, ama çok önemli değil.
  • Üçüncü sayıyı bırakın, çünkü sadece 52 - (a + b)ve hiçbir bilgi eklemiyor

Kanonik form, oldukça kompakt olan ve (dahil) b * 35 + aarasında tamsayıdır ( toplamda dairesel olarak benzersiz listeler vardır).0936477


0

Her iki listeyi karşılaştıran ve her yineleme için karşılaştırılan değerin indeksini artıran (ve saran) basit bir çözüm yazdım.

Python'u iyi bilmiyorum, bu yüzden Java'da yazdım, ama gerçekten basit, bu yüzden başka bir dile uyarlamak kolay olmalı.

Bu şekilde diğer tür listelerini de karşılaştırabilirsiniz.

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

Diğerlerinin de belirttiği gibi, bir listenin normalleştirilmiş rotasyonunu bulduğunuzda, bunları karşılaştırabilirsiniz.

Heres bunu yapan bazı çalışma kodları, Temel yöntem, her liste için normalleştirilmiş bir dönüş bulmak ve karşılaştırmaktır:

  • Her bir listede normalleştirilmiş bir dönüş indeksi hesaplayın.
  • Her iki listeyi de ofsetleriyle döngüler, her öğeyi karşılaştırır, yanlış eşleşirlerse geri döner.

Bu yöntemin sayılara bağlı olmadığını, dizelerin listelerini (karşılaştırılabilecek herhangi bir değer) aktarabileceğinizi unutmayın.

Listede bir liste araması yapmak yerine, listenin minimum değerle başlamasını istediğimizi biliyoruz - böylece minimum değerlerin üstesinden gelebilir, hangisinin ardışık en düşük değerlere sahip olduğunu bulana kadar arama yapabilir, bunu daha fazla karşılaştırma için saklayabiliriz. en iyisini elde edene kadar.

Endeks hesaplanırken erken çıkış için birçok fırsat vardır, bazı optimizasyonlarla ilgili ayrıntılar.

  • Yalnızca bir olduğunda en iyi minimum değeri aramayı atlayın.
  • Önceki değer de minimum değer olduğunda minimum değerleri aramayı atlayın (asla daha iyi eşleşmeyecektir).
  • Tüm değerler aynı olduğunda aramayı atla.
  • Listeler farklı minimum değerlere sahip olduğunda erken başarısız olun.
  • Ofsetler eşleştiğinde düzenli karşılaştırmayı kullanın.
  • Karşılaştırma sırasında dizin değerlerini listelerden birine kaydırmamak için ofsetleri ayarlayın.

Python'da bir liste listesi araması daha hızlı olabilir, ancak diğer dillerde de kullanılabilecek etkili bir algoritma bulmak ilgimi çekti. Ayrıca, yeni listeler oluşturmaktan kaçınmanın bir avantajı vardır.

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)

Daha fazla test / örnek için bu snippet'e bakın .


0

A listesinin beklenen O (N) zamanında oldukça kolay bir şekilde B listesinin döngüsel olarak değişip değişmediğini kontrol edebilirsiniz.

A listesinin karmasını ve B listesinin her döngüsel kaymasını hesaplamak için polinom karma işlevini kullanardım. B listesindeki bir kaymanın A listesiyle aynı karmaya sahip olması durumunda, gerçek öğeleri eşit olup olmadığını görmek için karşılaştırırdım .

Bunun hızlı olmasının nedeni, polinom hash fonksiyonları ile (son derece yaygındır!), Her bir döngüsel kaymanın karmasını sabit bir öncekinden hesaplayabilmenizdir, böylece O'daki tüm döngüsel kaymalar için karmaları hesaplayabilirsiniz ( N) zaman.

Şöyle çalışır:

Diyelim ki B'nin N elementi var, o zaman B'nin P üssünü kullanan karması:

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

Bu, P'deki bir polinomu değerlendirmek için optimize edilmiş bir yoldur ve aşağıdakilere eşdeğerdir:

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

Her B [i] 'nin P ^ (N-1-i) ile nasıl çarpıldığına dikkat edin. B'yi sola kaydırdığımızda, her B [i] birincisi dışında fazladan bir P ile çarpılır. Çarpma toplama üzerine dağıtıldığından, tüm bileşenleri bir kerede tüm karmayı çarparak çoğaltabilir ve ardından ilk öğe için faktörü düzeltebiliriz.

B'nin sol kaymasının karması sadece

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

İkinci sola kaydırma:

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

ve bunun gibi...

NOT: yukarıdaki tüm matematik bazı makine kelime boyutu modulo yapılır ve sadece bir kez P ^ N hesaplamak zorunda.


-1

Bunu yapmanın en pythonic yolunu yapıştırmak için setleri kullanın!

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

bu, aynı sayıda 0 ve 1 içeren dizeleri aynı sırada olması gerekmez
GeneralBecos

GeneralBecos: Sadece bu dizeleri seçin ve ikinci adımda siparişi kontrol edin
Louis

Aynı doğrusal sırada değiller. Aynı 'dairesel' sıradalar. Adım 2 olarak tanımladığınız şey orijinal sorundur.
GeneralBecos
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.