Pythonで2つのリストが循環的に同一かどうかを確認する方法


145

たとえば、私はリストがあります:

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つが3つ、0が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ギガです。

より少ない計算でそれを行う良い方法はありますか?または循環をサポートする任意のデータ型?


ただの直感:接尾辞の木がここで役立つかもしれないと感じています。en.wikipedia.org/wiki/Suffix_tree。作成するには、en.wikipedia.org
wiki / Ukkonen%27s_algorithm

1
@Mehrdadしかし、標準形式に変換するどの回答よりも実行時間がはるかに長く、整数に変換するよりも実行時間がはるかに遅く、David Eisenstatの実行時間よりもはるかに実行時間が長くなります。
Veedrac 2014年

2
すべての答えは一般的な問題を解決しようとしていますが、この特定のケースでは3つしかありませんが、3つの数が1の間のゼロの数であるすべてのリストを表すことができます。質問からのリストは、[0,0,2]、[0,2,0]、[2,0,0]として表すことができます。1回の実行でリストを削減してから、削減リストを確認できます。それらが「循環的に同一」である場合、オリジナルも同様です。
abc667 2014年

1
スタックオーバーフローは投票する必要がないと思います。必要なのは、すべてのソリューションでコードを実行し、終了した順に提示することです。
Dawood ibnカリーム2014年

2
これまで言及されていないため、@ abc667、Veedrac、およびEisenstatによって参照される「正規形式」は、Run Length Encoding en.wikipedia.org/wiki/Run-length_encoding
David Lovell

回答:


133

まず、これ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))

私のワンライナーに関するいくつかの説明: 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)ソリューションと比較してみましょう。それを比較している間、あなたは米国からオーストラリアに旅行することができます(おそらくクルーズ船で)


3
パディングスペース(各文字列の前と後の1つ)を追加するだけでうまくいきます。正規表現で過度に複雑にする必要はありません。(もちろん、同じ長さのリストを比較することを想定しています)
Rerito

2
@Reritoは、どちらのリストにも文字列が含まれていない限り、先頭または末尾にスペースがある可能性があります。それでも衝突を引き起こす可能性があります。
アダム・スミス

12
この答えは好きではありません。文字列操作のナンセンスは私にそれを嫌いにし、David Eisenstatの答えは私にそれを否定する意欲を持たせました。この比較文字列を使用してO(n)時間で実行できますが、整数を使用してO(n)時間で実行することもできます(自己削除として10kが必要。それにもかかわらず、David Eisenstatの回答は、回答にそれを必要としないため、比較を行うことはまったく意味がないことを示しています。
Veedrac 2014年

7
@Veedracは冗談ですか?計算の複雑さについて聞いたことがありますか?Davidsの答えはO(n ^ 2)の時間とO(n ^ 2)のスペースをとるだけで、小さな入力でも10 ^ 4の長さは22秒かかり、誰がどのくらいのRAMを知っているかを繰り返します。言うまでもなく、現時点では何も検索していません(すべての循環回転を生成したところです)。そして、私の文字列ナンセンスは、0.5秒未満で10 ^ 6のような入力に対して完全な結果を提供します。それを格納するためにO(n)スペースも必要です。そのため、結論に飛びつく前に、少し時間をかけて答えを理解してください。
サルバドールダリ

1
@SalvadorDaliあなたは非常に(ソフト)時間を重視しているようです;-)
e2-e4

38

要求された言語でこれに答えるのに十分なPythonの知識はありませんが、C / C ++では、質問のパラメーターを考慮して、0と1をビットに変換し、uint64_tの最下位ビットにプッシュします。これにより、1つのクロック-1クロックで55ビットすべてを比較できます。

驚くほど高速で、すべてがオンチップキャッシュ(209,880バイト)に収まります。55のすべてのリストメンバーを同時に右にシフトするためのハードウェアサポートは、CPUのレジスタでのみ使用できます。55人すべてのメンバーを同時に比較する場合も同様です。これにより、問題をソフトウェアソリューションに1対1でマッピングできます。(そしてSIMD / SSE 256ビットレジスタを使用し、必要に応じて最大256のメンバーを使用します)その結果、コードは読者にすぐにわかります。

あなたはこれをPythonで実装することができるかもしれませんが、それが可能かどうか、またはパフォーマンスがどうなるかを知るのに十分なほど知りません。

その上で眠った後、いくつかのことが明らかになり、すべてが良くなりました。

1.)ビットを使用して循環リンクリストをスピンするのは非常に簡単なので、Daliの非常に巧妙なトリックは必要ありません。64ビットレジスタの内部では、ビットシフトの代わりに算術を使用することにより、標準のビットシフトによりローテーションを非常に簡単に実行できます。

2.)2による除算を使用すると、ビットシフトを簡単に実行できます。

3.)リストの最後の0または1をチェックすることは、2を法として簡単に行うことができます。

4.)0を末尾からリストの先頭に「移動」するには、2で除算します。これは、実際にゼロが移動された場合、55番目のビットがfalseになるためです。

5.)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ナノ秒であり、アンカーリングを構成してテストリングの55ターンすべてと比較し、すべてのリングのNxNを他のすべてのリングに対して12.5秒で実行しています。

このコードは非常にタイトですが、99%の時間、4つのレジスタが何もしないで座っています。アセンブリ言語は、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;
}

ここに画像の説明を入力してください


23
人々は「サルバドールダリの解決策」について話し続け、私はここに座って混乱し、同じ名前の画家が古典的なアルゴリズムに何らかの方法で貢献した数学者でもあるのではないかと考えていました。それから私はそれが最も人気のある回答を投稿した人のユーザー名だと気づきました。私は賢い人ではありません。
Woodrow Barlow

10,000人の担当者がいる場合は、Numpyとベクトル化を使用して実装できます<10k向けの要旨ミラーDavid Eisenstatの回答では、一意のリストをすぐに生成できるので比較をまったく行う必要がないことを指摘しており、私は人々に彼のはるかに優れた回答を使用するよう勧めたいため、私の回答を削除しました。
Veedrac 2014年

@RocketRoyなぜPythonにビット操作がないと思いますか?一体、私はリンクしたコードでビット演算を使用しています。私はまだこの答えはほとんど必要ないと思います(David Eisenstatの答えは全体で1msかかります)が、私はそのステートメントを奇妙だと思いました。FWIW、Numpyで262M-「リスト」を検索するための同様のアルゴリズムは、私のコンピューターで約15秒かかります(一致が見つからないと想定)。リストの回転だけが内側のループではなく外側のループで行われます。
Veedrac 2014年

@ Quincunx、C ++の構文の色分けを正しく編集していただき、ありがとうございます。大感謝です!

@RocketRoy問題ありません。PPCGについて多くの質問に答えると、構文の色付けの方法がわかります。
ジャスティン

33

行間を読むと、3つの1と52の0を持つ文字列の各循環同値クラスの代表を1つ列挙しようとしているように聞こえます。密な表現から疎な表現(の3つの数値のセットrange(55))に切り替えましょう。この表現では、sby の循環シフトは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

2
@SalvadorDaliあなたは答えを誤解しました(彼が指摘するまで私もそうでした!)。これは、「3つの1と52のゼロを持つ文字列の各循環等価クラスを表す1つ」を直接生成します。彼のコードはすべての循環回転を生成するわけではありません。当初の費用¹はT(55²・26235²)です。コードは55²を55に改善し、T(55 *26235²)も改善します。デビッドアイゼンスタットの答えは、全体として 55²〜55³ です。55³≪ 55・26235²。ここでは、すべてのケースでO(1)の実際のコストとしてbig-Oの用語を話しているわけではありません。
Veedrac 2014年

1
@Veedracしかし、将来この質問に来る読者の99%は彼の制約を受けないだろうし、私の答えが彼らによりよく合うと信じている。さらに会話を膨らませることなく、私はOPに任せて、彼が正確に何を望んでいるかを説明します。
サルバドールダリ

5
@SalvadorDali OPはXY問題の餌食になっているようです。幸いなことに、質問自体はタイトルが何をしていないかを明らかにし、デイビッドは行の間を読むことができました。これが事実である場合、正しいことは、タイトルに答えて質問を無視するのではなく、タイトルを変更して実際の問題を解決することです。
アーロンデュフォー14年

1
@SalvadorDaliの内部では、Pythonコードが文字列からサブ文字列を検索するCのstrstr()に相当するものを呼び出しています。次に、strcmp()が呼び出され、string1の各文字をstring2と比較するfor()ループが実行されます。したがって、O(n)のように見えるのは、検索が失敗した場合のO(n * 55 * 55)です。高水準言語は両刃の剣です。実装の詳細は非表示になりますが、実装の詳細も非表示になります。FWIW、リストを連結するあなたの洞察は素晴らしいものでした。ハードウェアで簡単にローテーションできる、uint8と同じくらい高速で、bitsよりもはるかに高速です。

2
@AleksandrDubinskyコンピューターの方が単純で、人間の場合はさらに複雑です。そのままで十分高速です。
David Eisenstat

12

最初の配列を繰り返し、次にZアルゴリズム(O(n)時間)を使用して、最初の配列内の2番目の配列を見つけます。

(注:最初の配列を物理的にコピーする必要はありません。マッチング中にラップアラウンドできます。)

Zアルゴリズムの良い点は、KMPやBMなどに比べて非常にシンプル
であることです。ただし、意欲的な場合は、線形時間と定数空間で文字列照合を行うことができますstrstr。たとえば、これを行います。しかし、それを実装するのはもっと大変です。


6

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

これが、Salvador Daliの回答にある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))

1
私は基本的にサルバドール・ダリの答えを微調整し、アシュビニーの変更をフォーマットしたので、これをウィキに入れました。これのほとんどは実際には私のものではありません。
アダム・スミス

1
ご入力ありがとうございます。編集したソリューションで考えられるすべてのケースをカバーしたと思います。何か不足している場合はお知らせください。
サルバドールダリ

@SalvadorDaliああ、はい...文字列が同じ長さであることを確認しています。私は、最長の要素を探してリストを実行し、str.format n時間を呼び出して結果の文字列をフォーマットするよりも簡単だと思います。私は賛成します... :)
アダム・スミス

3

非常に多くの比較を行う必要があることを考えると、リストを最初に通過して、それらを簡単に比較できるある種の標準的な形式に変換する価値があるでしょうか?

循環的に一意のリストのセットを取得しようとしていますか?もしそうなら、タプルに変換した後、それらをセットに入れることができます。

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への謝罪。


3

次のように1つのリストをロールできます。

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

(必要に応じてコピーして)最初にリスト要素のすべての変換という字句最大となる回転バージョン。

次に、結果のリストのリストを並べ替え(インデックスを元のリストの位置に保持)、並べ替えたリストを統合し、必要に応じて元のリストのすべての重複をマークします。


2

@SalvadorDaliのb + bの任意の長さのサイズのスライスで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

2番目のアプローチ:[削除済み]


最初のバージョンはO(n²)で、2番目のバージョンはで機能しませんrollmatch([1, 0, 1, 1], [0, 1, 1, 1])
Veedrac 2014年

いいキャッチ、私はそれを削除します!
PaulMcG 2014年

1

完全で独立した答えではありませんが、比較を減らして最適化することについて、私も正規化表現について考えていました。

つまり、入力アルファベットが{0、1}の場合、許可される順列の数を大幅に減らすことができます。最初のリストを(疑似)正規化形式にローテーションします(質問の分布を考えると、1ビットの1つが左端にあり、0ビットの1つが右端にあるものを選びます)。次に、各比較の前に、同じ配置パターンで可能な位置を介して他のリストを順番に回転させます。

たとえば、1ビットが合計4つある場合、この配置では最大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

これは、より大きなアルファベットと異なる配置パターンに一般化されます。主な課題は、可能な少数の表現のみで適切な正規化を見つけることです。理想的には、1つの一意の表現による適切な正規化ですが、問題があるため、それは不可能だと思います。


0

RocketRoyの答えをさらに発展させる:すべてのリストを事前に符号なし64ビットの数値に変換します。各リストについて、それらの55ビットを回転させて、最小の数値を見つけます。

これで、リストごとに1つの符号なし64ビット値が残り、他のリストの値と直接比較できます。関数is_circular_identical()は不要になりました。

(本質的には、リスト要素のローテーションの影響を受けないリストのID値を作成します)リストに任意の数の1がある場合でも機能します。


0

これはサルバドールダリと同じアイデアですが、文字列変換は必要ありません。背後には、不可能なシフト検査を回避するための同じ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

問題を単純化する

  • 問題は注文したアイテムのリストで構成されています
  • 値のドメインはバイナリです (0,1)
  • 連続してマッピングすることで問題を軽減できます 1したをカウントにます
  • 連続する0sを負の数に

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
  • 最初のリストの最初の要素が2番目のリストのどこに存在するかを確認します
  • ヒストグラムにリストをマッピングした場合、頻繁な要素の確率は低くなります

疑似コード

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 新しいリストのカウントとしての拘束性要素のマッピング

  • LOOKUP_INDEX(LIST A, INTEGER E):LIST要素インデックスの戻りLIST Eリストに存在しA

  • COUNT_CHAR(LIST A , INTEGER E):INTEGEREリスト内の要素が何回発生するかを数えるA

  • ALPHA_NGRAM(LIST A,LIST B,INTEGER I,INTEGER N):BOOLEANCHECK IFはB[I]相当IS A[0] N-GRAM両方向


最後に

リストのサイズが非常に大きくなる場合、またはサイクルのチェックを開始する要素が頻繁に多い場合は、次のことを実行できます。

  • 最初に、最初のリストで最も頻度の低い項目を探します

  • 線形チェックを通過する確率を下げるために、n-gram Nパラメータを増やします


0

問題のリストの効率的で迅速に計算される「正規形」は、次のようにして導出できます。

  • 3つの数値を取得するには、1の間のゼロの数を数えます(ラップアラウンドは無視)。
  • 最大の数字が最初になるように、3つの数字を回転させます。
  • 最初の番号は、( a)の間でなければならない1852(包括的)。間として再エンコードそれ034
  • 2番目の数(b)はとの間0でなければなり26ませんが、あまり問題ではありません。
  • 3番目の数値52 - (a + b)は、情報を追加するだけなので、それを削除します。

標準形式は整数b * 35 + aの間であり、0そして936(存在するかなりコンパクトである(両端を含む)、477円形固有リストは合計で)。


0

私は両方のリストを比較し、各反復で比較された値のインデックスを増やす(そして折り返す)単純なソリューションを書きました。

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

0

他の人が述べたように、リストの正規化された回転を見つけたら、それらを比較できます。

これを行ういくつかの作業コードをここに示します。基本的な方法は、各リストの正規化された回転を見つけて比較することです。

  • 各リストの正規化された回転インデックスを計算します。
  • 両方のリストをオフセットでループし、各項目を比較して、一致しない場合は返します。

このメソッドは数値に依存しないことに注意してください。文字列のリスト(比較できる任意の値)を渡すことができます。

リストインリスト検索を実行する代わりに、リストを最小値から開始したいので、最小値をループして、連続する値が最も低い値が見つかるまで検索し、これをさらに比較するために保存します。最高になるまで。

インデックス、いくつかの最適化の詳細を計算するとき、早期に終了する多くの機会があります。

  • 1つしかない場合は、最適な最小値の検索をスキップします。
  • 前の値も最小値である場合は、最小値の検索をスキップします(より適切な一致になることはありません)。
  • すべての値が同じ場合は検索をスキップします。
  • リストの最小値が異なる場合、早期に失敗します。
  • オフセットが一致する場合は、定期的な比較を使用します。
  • 比較中にリストの1つでインデックス値がラップしないようにオフセットを調整します。

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)

参照:いくつかのテスト/例については、このスニペット


0

予想されるO(N)時間でリストAがリストBの循環シフトに等しいかどうかを簡単に確認できます。

多項式ハッシュ関数を使用して、リスト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だけ左にシフトすると、最初の1つを除いて、すべてのB [i]に追加のPが乗算されます。乗算は加算に分散するため、ハッシュ全体を乗算するだけですべてのコンポーネントを一度に乗算してから、最初の要素の係数を修正できます。

Bの左シフトのハッシュはちょうどです

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

2番目の左シフト:

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

等々...

注:上記のすべての計算は、いくつかのマシンワードサイズを法として実行され、P ^ Nを一度計算するだけで済みます。


-1

それを行うための最もパイソン的な方法に接着するには、セットを使用してください!

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

これは、同じ数の0と1が必ずしも同じ順序であるとは限らない文字列にも一致します
GeneralBecos

GeneralBecos:これらの文字列を選択して、2番目のステップで注文を確認するだけ
Louis

それらは同じ線形順序ではありません。それらは同じ「循環」順序です。ステップ2として説明するのは、元の問題です。
GeneralBecos 2014年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.