簡體   English   中英

如何檢查兩個列表在Python中是否循環相同

[英]How to check whether two lists are circularly identical in Python

例如,我有以下列表:

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

它們似乎不同,但是如果假定起點和終點相連,則它們在循環上是相同的。

問題是,每個列表的長度為55,並且僅包含三個1和52個零。 如果沒有循環條件,則有26,235(55選擇3)個列表。 但是,如果存在條件“循環”,則存在大量循環相同的列表

目前,我通過以下方式循環檢查身份:

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

在最壞的情況下,此功能需要55次循環移位操作。 並且有26,235個列表可以相互比較。 簡而言之,我需要55 * 26,235 *(26,235-1)/ 2 = 18,926,847,225個計算。 大約有20 Giga!

有什么好方法可以減少計算量嗎? 還是支持循環的任何數據類型?

首先,這可以根據列表的長度在O(n)中完成。您會注意到,如果將列表重復2次( [1, 2, 3] )將為[1, 2, 3, 1, 2, 3]那么您的新列表肯定會包含所有可能的循環列表。

因此,您所需要做的就是檢查您要搜索的列表是否在起始列表的2倍之內。 在python中,您可以通過以下方式實現此目的(假設長度相同)。

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

關於我的oneliner的一些解釋: list * 2會將列表與其自身組合, map(str, [1, 2])將所有數字轉換為字符串,而' '.join()將轉換數組['1', '2', '111']轉換為字符串'1 2 111'

正如某些人在評論中指出的那樣,oneliner可能會給出一些誤報,因此涵蓋了所有可能的極端情況:

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談到時間復雜度時,值得注意的是O(n)如果子串中可以找到將達到O(n)時間。 它並非總是如此,並且取決於您所用語言的實現方式( 盡管可能可以例如在線性時間KMP中完成 )。

PS2對於那些害怕字符串操作的人,由於這個事實,他們認為答案並不理想。 重要的是復雜性和速度。 該算法可能在O(n)時間和O(n)空間中運行,這使其比O(n^2)域中的任何算法都要好得多。 要親自查看,可以運行一個小的基准測試(創建一個隨機列表會彈出第一個元素,並將其附加到末尾,從而創建一個循環列表。您可以自行進行操作)

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秒。 不太長。 現在嘗試將此與O(n^2)解決方案進行比較。 在進行比較時,您可以從美國到澳大利亞旅行(很可能乘游輪旅行)

在Python中沒有足夠的知識來以您所請求的語言來回答這個問題,但是在C / C ++中,給定您問題的參數,我會將零和一轉換為位,然后將它們壓入uint64_t的最低有效位。 這樣一來,您就可以比較所有55位-1個時鍾。

速度極快,整個過程都可以放入片上緩存(209,880字節)。 僅在CPU的寄存器中提供對所有55個列表成員同時右移的硬件支持。 同時比較所有55個成員時也是如此。 這樣可以將問題一對一地映射到軟件解決方案。 (並使用SIMD / SSE 256位寄存器,如果需要,最多可使用256個成員),因此,代碼對於讀者而言立即顯而易見。

您也許可以在Python中實現此功能,但我只是不太了解它是否可能或性能如何。

睡在上面之后,一些事情變得顯而易見,並且一切都變得更好。

1.)使用位旋轉循環鏈接列表是如此容易,以至於Dali不需要非常巧妙的技巧。 在64位寄存器中,標准移位將非常簡單地完成旋轉,並通過使用算術而非位操作來使這一切對Python更友好。

2.)使用2分頻可以輕松完成移位。

3.)通過模2可以很容易地檢查列表末尾的0或1。

4.)通過從2的尾部將0從列表的尾部“移動”到列表的頭部,這是因為,如果實際移動了0,則將使第55位為false,這已經是絕對不做的了。

5.)將a從末尾“ 1”移動到列表的頭部可以通過除以2並加上18,014,398,509,481,984-這是通過將第55位標記為true並將其余所有標記為false來創建的值。

6.)如果在任何給定的旋轉之后,錨點與組成的uint64_t的比較為TRUE,則中斷並返回TRUE。

我會將整個列表數組直接轉換為uint64_ts數組,以避免重復進行轉換。

在花了幾個小時嘗試優化代碼之后,研究了匯編語言,我能夠將運行時節省20%。 我還應該補充說,O / S和MSVC編譯器也在昨天中午更新。 無論出於何種原因,C編譯器生成的代碼質量在更新(11/15/2014)后都得到了顯着改善。 現在,運行時間約為70個時鍾,約17納秒 ,可以在12.5秒內完成並比較錨環與測試環的所有55圈,並將所有環的NxN與其他環進行比較。

這段代碼是如此緊張,除了4個寄存器外,其余99%的時間都無所事事。 匯編語言幾乎逐行匹配C代碼。 非常容易閱讀和理解。 如果有人自學的話,這是一個很棒的組裝項目。

硬件是Hazwell i7,MSVC 64位,全面優化。

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

在此處輸入圖片說明

在行之間閱讀時,聽起來好像您正在嘗試枚舉具有3個1和52個0的每個圓形等效類的字符串的一個代表。 讓我們從密集表示轉換為稀疏表示( range(55)的三個數字的集合)。 在此表示中, s乘以k的循環移位由理解set((i + k) % 55 for i in s) 一個類中的字典最小代表總是包含位置0。給定一組{0, i, j}0 < i < j ,該類中其他最小的候選者是{0, j - i, 55 - i}{0, 55 - j, 55 + i - j} 因此,我們需要(i, j) <= min((j - i, 55 - i), (55 - j, 55 + i - j))來使原件最小。 這是一些枚舉代碼。

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

重復第一個數組,然后使用Z算法 (O(n)時間)在第一個數組中找到第二個數組。

(注意:您不必物理地復制第一個數組。您可以在匹配期間進行環繞。)

Z算法的優點在於,與KMP,BM等相比,它非常簡單。
但是,如果您有雄心壯志,則可以在線性時間和恆定空間中進行字符串匹配-例如, strstr可以做到這一點。 但是,實施它會更加痛苦。

緊隨Salvador Dali的非常聰明的解決方案之后,處理該問題的最佳方法是確保所有元素的長度相同,並且兩個LIST的長度相同。

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

不知道這是快於或慢於薩爾瓦多·達利的答案中AshwiniChaudhary推薦的正則表達式解決方案的內容,該內容為:

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))

假設您需要進行大量比較,那么在初次遍歷列表時將它們轉換為某種可以輕松比較的規范形式是否值得?

您是否要獲取一組圓形唯一列表? 如果是這樣,您可以在轉換為元組后將它們放入集合中。

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)未能對他的相似答案表示歉意。

您可以像這樣滾動一個列表:

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

首先將每一個列表元素(在副本如有必要) 旋轉的版本,是詞法最大。

然后對列表的結果列表進行排序(在原始列表位置保留索引)並統一排序后的列表,並根據需要在原始列表中標記所有重復項。

Sa帶@SalvadorDali觀察在b + b中任何a長度大小的切片中尋找a的匹配項的觀察,這是僅使用列表操作的解決方案。

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

第二種方法:[已刪除]

這不是一個完整的,獨立的答案,但在通過減少比較來進行優化的主題上,我也在考慮標准化的表示形式。

即,如果您輸入的字母為{0,1},則可以大大減少允許的排列數量。 將第一個列表旋轉為(偽)歸一化形式(考慮到問題中的分布,我將選擇其中一個1位之一在最左端,而0個位之一在最右端的一個)。 現在,在每次比較之前,以相同的對齊方式在可能的位置上依次旋轉其他列表。

例如,如果您總共有四個1位,則此對齊方式最多可以有4個排列,並且如果您具有相鄰1位的簇,則此簇中的每個附加位都會減少位置數。

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

這可以概括為較大的字母和不同的對齊方式。 主要挑戰是找到僅包含幾種可能表示形式的良好歸一化方法。 理想情況下,這將是具有單個唯一表示形式的適當規范化,但是鑒於問題,我認為這是不可能的。

進一步建立在RocketRoy的答案上:將所有列表預先轉換為無符號的64位數字。 對於每個列表,將其旋轉55位以找到最小的數值。

現在,每個列表都剩下一個無符號的64位值,您可以直接將其與其他列表的值進行比較。 不再需要函數is_circular_identical()。

(從本質上講,您為列表創建了一個不受列表元素輪換影響的標識值)如果列表中有任意多個ID,則該值甚至可以工作。

這與Salvador Dali的想法相同,但是不需要字符串轉換。 后面是相同的KMP恢復想法,以避免不可能的輪班檢查。 它們僅調用KMPModified(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());
        }
    }

希望有幫助!

簡化問題

  • 問題包括訂購商品清單
  • 值的域是二進制(0,1)
  • 我們可以通過將連續的1 s映射為一個計數來減少問題
  • 並連續0 s為負數

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 ]
  • 此過程要求第一個項目和最后一個項目必須不同
  • 這將減少總體比較量

檢查流程

  • 如果我們假設它們是重復的,那么我們可以假設我們正在尋找什么
  • 基本上,第一個列表中的第一個項目必須存在於另一個列表中的某個位置
  • 其次是第一個列表中的后續內容,並且以相同的方式
  • 前面的項目應該是第一個列表中的最后一個項目
  • 因為是圓形的,所以順序是一樣的

握力

  • 這里的問題是從哪里開始,在技術上稱為lookuplook-ahead
  • 我們將檢查第二個列表中第一個列表中第一個元素的位置
  • 鑒於我們將列表映射到直方圖中,頻繁元素的概率較低

偽代碼

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

功能

  • MAP_LIST(LIST A):LIST以新列表中的計數MAP_LIST(LIST A):LIST MAP決定性元素

  • LOOKUP_INDEX(LIST A, INTEGER E):LIST返回元素E在列表A存在的索引的列表

  • COUNT_CHAR(LIST A , INTEGER E):INTEGER LIST A元素E次數

  • ALPHA_NGRAM(LIST A,LIST B,INTEGER I,INTEGER N):BOOLEAN如果B[I]在兩個方向上均等價於A[0] N-GRAM ALPHA_NGRAM(LIST A,LIST B,INTEGER I,INTEGER N):BOOLEAN檢查


最后

如果列表大小將非常龐大,或者如果我們開始檢查周期的元素經常很高,則可以執行以下操作:

  • 在第一個列表中查找頻率最低的項目以

  • 增加n-gram N參數以降低通過線性檢查的可能性

有關列表的有效,快速計算的“規范形式”可以得出:

  • 計算一個之間的零數目(忽略環繞),以獲得三個數字。
  • 旋轉三個數字,以便最大的數字位於第一位。
  • 第一個數字( a )必須介於1852 (含)之間。 將其重新編碼為034之間。
  • 第二個數字( b )必須介於026之間,但這並不重要。
  • 刪除第三個數字,因為它只有52 - (a + b)並且不添加任何信息

規范形式是整數b * 35 + a ,介於0936之間(包括0936 ),該整數非常緊湊(總共有477圓形唯一列表)。

我寫了一個簡單的解決方案,它比較兩個列表,並為每次迭代增加(並包裝)比較值的索引。

我不太了解python,所以我用Java編寫了它,但是它非常簡單,因此應該很容易將其適應任何其他語言。

這樣,您還可以比較其他類型的列表。

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

正如其他人提到的那樣,一旦找到列表的標准化輪換,就可以對其進行比較。

這是執行此操作的一些工作代碼,基本方法是為每個列表查找歸一化的旋轉並進行比較:

  • 計算每個列表上的歸一化旋轉索引。
  • 循環使用兩個列表及其偏移量,比較每個項目,如果它們不匹配則返回。

請注意,此方法不依賴於數字,您可以傳入字符串列表(可以比較的任何值)。

我們知道我們希望列表以最小值開頭,而不是進行列表中列表搜索,因此我們可以遍歷最小值,直到找到哪個具有最低連續值為止,然后將其存儲以進行進一步比較直到我們擁有最好的。

計算索引時有很多機會提早退出,有關一些優化的詳細信息。

  • 如果只有一個,則跳過搜索最佳最小值。
  • 當前一個也是最小值時,跳過搜索最小值(永遠不會是更好的匹配項)。
  • 當所有值都相同時,跳過搜索。
  • 列表具有不同的最小值時,較早失敗。
  • 偏移量匹配時使用常規比較。
  • 調整偏移量以避免在比較期間將索引值包裝在列表之一上。

請注意,在Python中,列表中列表搜索可能會更快,但是我很想找到一種有效的算法-也可以在其他語言中使用該算法。 同樣,避免創建新列表也有一些優勢。

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)

請參閱: 此片段以獲取更多測試/示例。

您可以很容易地檢查列表A是否等於列表B在預期O(N)時間中的循環移位。

我將使用多項式哈希函數來計算列表A的哈希,以及列表B的每個循環移位。如果列表B的移位具有與列表A相同的哈希,則我將比較實際元素以查看它們是否相等。

之所以如此之快,是因為使用多項式哈希函數(這是非常常見的!),您可以在恆定時間內計算與上一個循環移位相比的每個循環移位的哈希,因此您可以為O()中的所有循環移位計算哈希N)時間。

它是這樣的:

假設B有N個元素,那么使用質數P的B的哈希為:

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

這是評估P中的多項式的一種優化方法,等效於:

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

注意如何將每個B [i]乘以P ^(N-1-i)。 如果我們將B左移1,則每個B [i]將乘以一個額外的P,第一個除外。 由於乘法分布在加法上,因此我們可以通過將整個哈希值乘以一次來乘以所有分量,然后為第一個元素固定因數。

B左移的哈希只是

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

第二個左移:

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

等等...

注意:上面的所有數學運算都是以某​​些機器字長為模,並且您只需計算一次P ^ N。

要粘合到最pythonic的方式,請使用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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM