파이썬에서 두 목록이 원형으로 동일한 지 확인하는 방법


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과 52 개의 0 만 포함합니다. 순환 조건이 없으면 26,235 개 (55 개 중 3 개) 목록이 있습니다. 그러나 'circular'라는 조건이 존재하면 수많은 원형 적으로 동일한 목록이 있습니다.

현재 다음을 통해 순환 신원을 확인합니다.

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
Rerito

1
@Mehrdad 그러나 정식 형식으로 변환하는 답변보다 훨씬 나쁜 실행 시간, 정수로 변환하는 것보다 실행 시간이 훨씬 빠르며 David Eisenstat의 것보다 실행 시간이 훨씬 빠릅니다.
Veedrac

2
모든 문제는 일반적인 문제를 해결하려고하지만이 특별한 경우에는 3 개만있는 모든 목록을 나타낼 수 있습니다 .3 개의 숫자는 1 사이의 숫자가 0입니다. 질문 목록은 [0,0,2], [0,2,0], [2,0,0]으로 표시 할 수 있습니다. 한 번의 실행으로 간단히 목록을 축소 한 다음 축소 된 목록을 확인할 수 있습니다. "원형으로 동일"하면 원본도 동일합니다.
abc667

1
스택 오버플로는 투표가 필요하지 않은 것 같습니다. 필요한 것은 모든 솔루션에서 코드를 실행하고 완료 순서대로 제시하는 것입니다.
다우드 이븐 카림

2
그것은 지금까지 언급되지 않았기 때문에, 「정규의 형식은 "@ abc667, Veedrac에 의해 참조하고, Eisenstat은 실행 길이 인코딩이라고 en.wikipedia.org/wiki/Run-length_encoding
데이비드 로벨

답변:


133

먼저, 이것은 O(n)목록의 길이와 관련하여 수행 될 수 있습니다. 목록을 두 번 복제하면 ( [1, 2, 3]) [1, 2, 3, 1, 2, 3]새 목록이 가능한 모든 주기적 목록을 보유하게됩니다.

따라서 검색하려는 목록이 시작 목록의 2 회 안에 있는지 확인하면됩니다. 파이썬에서는 길이가 같다고 가정하면 다음과 같은 방법으로 이것을 달성 할 수 있습니다.

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)솔루션 과 비교해보십시오 . 비교하는 동안 미국에서 호주로 여행 할 수 있습니다 (대부분 유람선을 통해)


3
패딩 공간 (각 문자열 앞뒤에 1)을 추가하면 트릭을 수행합니다. 정규 표현식으로 작업을 복잡하게 할 필요가 없습니다. (물론 같은 길이의 목록을 비교한다고 가정합니다)
Rerito

2
@Rerito는 목록에 문자열이 포함되어 있지 않으면 선행 또는 후행 공백이있을 수 있습니다. 여전히 충돌이 발생할 수 있습니다.
Adam Smith

12
이 답변이 마음에 들지 않습니다. 말도 안되는 조작으로 나는 그것을 싫어했고 David Eisenstat의 대답 으로 그것을 기꺼이 줄였다. 이 비교 문자열을 사용하여 O (n) 시간에 수행 할 수 있지만 정수 (자체 삭제로 10k 필요 )를 사용하여 O (n) 시간에 수행 할 수도 있습니다 . 그럼에도 불구하고 David Eisenstat의 답변은 답변이 필요하지 않기 때문에 비교를 수행하는 것이 전혀 의미가 없음을 보여줍니다.
Veedrac

7
@Veedrac 당신은 나를 놀리고 있습니까? 계산 복잡성에 대해 들어 보셨습니까? Davids의 대답은 O (n ^ 2) 시간과 O (n ^ 2) 공간을 사용하여 작은 입력의 경우에도 10 ^ 4 길이는 22 초 정도 걸리며 누가 얼마나 많은 램을 알고 있는지 반복합니다. 우리가 지금 아무것도 검색하기 시작하지 않았다는 것은 말할 것도 없습니다 (우리는 모든 순환 회전을 생성했습니다). 그리고 내 문자열 넌센스는 0.5 초 이내에 10 ^ 6과 같은 입력에 대한 완벽한 결과를 제공합니다. 또한 저장하려면 O (n) 공간이 필요합니다. 결론을 내리기 전에 시간을내어 답변을 이해하십시오.
살바도르 달리

1
@SalvadorDali 당신은 매우 (부드러운) 시간에 집중된 것처럼 보입니다 ;-)
e2-e4

38

파이썬에서는 요청한 언어 로이 질문에 대답하기에 충분하지 않지만 C / C ++에서는 질문의 매개 변수가 주어지면 0과 1을 비트로 변환하여 uint64_t의 가장 중요하지 않은 비트로 푸시합니다. 이를 통해 한 번의 감소로 1 개의 클럭으로 55 비트를 모두 비교할 수 있습니다.

엄청나게 빠르며 모든 것이 온칩 캐시 (209,880 바이트)에 적합합니다. 55 명의리스트 멤버를 동시에 오른쪽으로 시프트하기위한 하드웨어 지원은 CPU 레지스터에서만 사용 가능합니다. 55 명의 멤버를 동시에 비교할 때도 마찬가지입니다. 이를 통해 소프트웨어 솔루션에 문제를 1 : 1로 매핑 할 수 있습니다. (필요한 경우 최대 256 개의 멤버까지 SIMD / SSE 256 비트 레지스터 사용) 결과적으로 코드는 즉시 독자에게 분명합니다.

파이썬에서 이것을 구현할 수 있을지 모르겠지만, 그것이 가능한지 또는 성능이 무엇인지 알기에 충분하지 않습니다.

그것에 자고 난 후에, 몇 가지가 분명 해졌고, 모든 것이 더 좋아졌습니다.

1.) Dali의 매우 영리한 트릭이 필요하지 않은 비트를 사용하여 원형으로 연결된 목록을 회전시키는 것은 매우 쉽습니다. 64 비트 레지스터 표준 비트 시프트에서는 비트 연산 대신 산술을 사용하여 회전을 매우 간단하게 수행하고 더 파이썬 친화적으로 만들기 위해 노력합니다.

2) 2로 나누기를 사용하면 비트 시프 팅을 쉽게 수행 할 수 있습니다.

3) 모듈러스 2를 사용하여 목록 끝에서 0 또는 1을 쉽게 확인할 수 있습니다.

4.) 꼬리에서리스트의 헤드로 0을 "이동"하는 것은 2로 나눌 수 있습니다. 이것은 실제로 0이 이동 된 경우 55 번째 비트를 거짓으로 만들 수 있기 때문에 이미 아무것도하지 않습니다.

5.) 꼬리에서 목록의 머리 부분으로 1을 "이동"하는 것은 2로 나누어 18,014,398,509,481,984를 추가하여 수행 할 수 있습니다. 이는 55 번째 비트를 true로 표시하고 나머지는 모두 false로 표시하여 생성 된 값입니다.

6.) 주어진 회전 후에 앵커와 구성된 uint64_t의 비교가 TRUE이면 TRUE를 중단하고 반환합니다.

전체 목록 배열을 uint64_ts 배열로 변환하여 반복적으로 변환하지 않아도됩니다.

코드를 최적화하려고 몇 시간을 보낸 후 어셈블리 언어를 연구하여 런타임에서 20 %를 줄일 수있었습니다. O / S 및 MSVC 컴파일러도 어제 정오에 업데이트되었다고 덧붙여 야합니다. 어떤 이유로 든, C 컴파일러가 생성 한 코드의 품질은 업데이트 후 (2014 년 11 월 15 일) 극적으로 향상되었습니다. 런타임은 이제 ~ 70 클럭, 17 나노초 로 구성되어 테스트 링의 모든 55 회전으로 앵커 링을 구성하고 비교합니다. 다른 링에 대한 모든 링의 NxN은 12.5 초 안에 완료됩니다 .

이 코드는 너무 빡빡해서 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;
}

여기에 이미지 설명을 입력하십시오


23
사람들은 "살바도르 달리의 해결책"에 대해 계속 이야기하고 있으며, 같은 이름의 화가가 고전적인 알고리즘에 중요한 방식으로 기여한 수학자인지 궁금해 여기 혼란스러워 앉아있었습니다. 가장 인기있는 답변을 게시 한 사람의 사용자 이름임을 깨달았습니다. 나는 똑똑한 사람이 아닙니다.
Woodrow Barlow

Numpy 및 벡터화를 사용하여 10k 응답을 가진 사람이라면 누구나 구현할 수 있습니다 . <10k를위한 요지 거울 . 나는 내 대답을 삭제하기 때문에 다윗 Eisenstat의 응답 이 비교 작업을 수행 할 필요가 없습니다 지적 전혀 그냥 바로 고유의 목록을 생성 할 수 있고 나는 그의 훨씬 더 나은 해답을 사용하는 사람들을 격려하고자한다.
Veedrac

@RocketRoy 왜 파이썬에 비트 연산이 없을 것이라고 생각하십니까? 도대체 내가 링크 한 코드에서 비트 연산 을 사용합니다 . 나는 여전히이 답변이 필요하지 않다고 생각하지만 (David Eisenstat의 답변은 전체적으로 1ms가 걸립니다) 그 진술이 이상하다는 것을 알았습니다. FWIW, Numpy에서 262M- "목록"을 검색하는 비슷한 알고리즘은 내 컴퓨터에서 약 15 초가 걸리며 (일치가 없다고 가정) 내부 루프가 아닌 외부 루프에서 목록 회전 만 발생합니다.
Veedrac

@Quincunx, C ++에 맞는 구문 색상을 얻기 위해 편집 해 주셔서 감사합니다. 매우 감사히 생각한다!

@RocketRoy 문제 없습니다. PPCG 에 대해 많은 질문에 대답 하면 구문 색상 표시 방법을 배웁니다.
저스틴

33

행 사이를 읽으면 3의 1과 52의 0으로 각 원형 등가 클래스의 대표자를 열거하려고하는 것처럼 들립니다. 밀도가 높은 표현에서 희소 표현으로 전환합시다 (에서 세 개의 숫자 집합 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 개의 0을 가진 각 원형 등가 클래스를 나타내는 하나"를 직접 생성합니다. 그의 코드는 모든 주기적 회전을 생성하지는 않습니다. 원래 비용 ¹은 T (55² · 26235²)입니다. 코드는 55²를 55로 향상 시키므로 T (55 * 26235²)입니다. David Eisenstat의 답변은 55²에서 55³ 사이 입니다 . 55³ ≪ 55 · 26235². ¹ 모든 경우에 O (1)의 실제 비용으로 큰 O 용어를 말하는 것은 아닙니다.
Veedrac

1
@Veedrac 그러나 앞으로이 질문에 올 독자의 99 %는 그의 제약이 없으며 내 대답이 더 잘 맞을 것이라고 믿습니다. 대화를 더 부 풀리지 않고 OP에게 맡겨서 정확히 무엇을 원하는지 설명 할 것입니다.
살바도르 달리

5
@SalvadorDali OP가 XY 문제의 먹이로 떨어진 것 같습니다 . 다행스럽게도 질문 자체는 제목이 무엇을하지 않는지 명확하게 보여 주었으며 David은 그 행 사이를 읽을 수있었습니다. 이것이 사실 인 경우, 제목에 답하고 질문을 무시하기보다는 제목을 변경하고 실제 문제를 해결하는 것이 옳은 일입니다.
Aaron Dufour

1
@SalvadorDali, 덮개 아래에서 Python 코드는 문자열을 하위 문자열로 검색하는 C의 strstr ()과 동일합니다. 그런 다음 strcmp ()를 호출하여 string1의 각 문자를 string2와 비교하는 for () 루프를 실행합니다. 따라서 O (n)처럼 보이는 것은 검색 실패를 가정 한 O (n * 55 * 55)입니다. 고급 언어는 양날의 칼입니다. 구현 세부 정보를 숨기고 구현 세부 정보도 숨 깁니다. FWIW, 목록을 연결하는 당신의 통찰력은 훌륭했습니다. 여전히 uint8만큼 빠르며 비트보다 훨씬 빠릅니다. 하드웨어에서 쉽게 회전 할 수 있습니다.

2
@AleksandrDubinsky 컴퓨터가 더 단순하고 인간에게는 더 복잡합니다. 그대로 빠릅니다.
David Eisenstat

12

첫 번째 배열을 반복 한 다음 Z 알고리즘 (O (n) 시간)을 사용하여 첫 번째 배열의 두 번째 배열을 찾으십시오.

(참고 : 첫 번째 배열을 실제로 복사 할 필요는 없습니다. 일치하는 동안 랩핑 만하면됩니다.)

Z 알고리즘의 좋은 점은 KMP, BM 등과 비교할 때 매우 간단 하다는 것입니다 .
그러나 야심이 있다면 선형 시간과 일정한 공간 에서 문자열 일치를 수행 할 수 strstr있습니다. 그러나 그것을 구현하는 것은 더 고통 스러울 것입니다.


6

살바도르 달리의 매우 현명한 해결책에 따라, 그것을 처리하는 가장 좋은 방법은 모든 요소의 길이가 같고 두 목록의 길이가 같은지 확인하는 것입니다.

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
기본적으로 Salvador Dali의 답변을 조정하고 Ashwini의 변경 형식을 지정했기 때문에 wiki'd. 이 중 실제로는 거의 없습니다.
Adam Smith

1
입력 해 주셔서 감사합니다. 편집 된 솔루션에서 가능한 모든 사례를 다루었다고 생각합니다. 빠진 것이 있으면 알려주세요.
살바도르 달리

@SalvadorDali 아, 예 ... 문자열의 길이가 같은지 확인합니다. 가장 긴 요소를 찾은 다음 str.format n시간을 호출 하여 결과 문자열을 형식화하는 것보다 쉬운 방법이 있습니다 . I SUPPOSE .... :)
Adam Smith

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)

그의 v. 유사한 답변을 찾지 못해서 David Eisenstat에게 사과드립니다.


3

다음과 같이 하나의 목록을 굴릴 수 있습니다.

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의 Piggybacking에서 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

두 번째 접근 방식 : [삭제됨]


첫 번째 버전은 O (n²)이고 두 번째 버전은 작동하지 않습니다 rollmatch([1, 0, 1, 1], [0, 1, 1, 1]).
Veedrac

캐치, 삭제하겠습니다!
PaulMcG

1

완전하고 독립적 인 답변은 아니지만 비교를 줄임으로써 최적화라는 주제에 대해 표준화 된 표현을 생각하고있었습니다.

즉, 입력 알파벳이 {0, 1}이면 허용되는 순열 수를 크게 줄일 수 있습니다. 첫 번째 목록을 (의사) 정규화 된 형식으로 회전하십시오 (질문에서 분포를 고려할 때 1 비트 중 하나가 맨 왼쪽에 있고 0 비트 중 하나가 맨 오른쪽에있는 것을 선택합니다). 이제 각 비교 전에 동일한 정렬 패턴으로 가능한 위치를 통해 다른 목록을 연속적으로 회전시킵니다.

예를 들어 총 4 개의 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

이것은 더 큰 알파벳과 다른 정렬 패턴으로 일반화됩니다. 가장 큰 과제는 몇 가지 가능한 표현으로 좋은 정규화를 찾는 것입니다. 이상적으로는 고유 한 단일 표현으로 적절한 정규화가 될 것이지만 문제가 있다고 생각하면 가능하지 않다고 생각합니다.


0

RocketRoy의 답변을 더 발전시키는 방법 : 모든 목록을 부호없는 64 비트 숫자로 변환하십시오. 각 목록에 대해 55 비트를 돌려 가장 작은 숫자 값을 찾으십시오.

이제 각 목록에 대해 단일 부호없는 64 비트 값을 남겨두고 다른 목록의 값과 직접 비교할 수 있습니다. is_circular_identical () 함수는 더 이상 필요하지 않습니다.

본질적으로리스트 요소의 회전에 영향을받지 않는리스트에 대한 항등 값을 작성합니다.리스트에 임의의 수의리스트가있는 경우에도 작동합니다.


0

이것은 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

문제를 단순화

  • 문제는 주문한 항목 목록으로 구성됩니다.
  • 가치의 영역은 이진 (0,1)
  • 연속적인 1s를 카운트로 매핑하여 문제를 줄일 수 있습니다
  • 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 새로운 목록에있는 개수만큼 연속적인 요소를 매핑

  • LOOKUP_INDEX(LIST A, INTEGER E):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-GRAMIN 양 방향


드디어

목록 크기가 꽤 크거나 사이클을 확인하기 시작하는 요소가 자주 높은 경우 다음을 수행 할 수 있습니다.

  • 첫 번째 목록에서 가장 빈번한 항목을 찾아

  • 선형 검사를 통과 할 확률을 낮추려면 n-gram N 매개 변수를 늘리십시오.


0

문제의 목록에 대해 효율적이고 계산하기 쉬운 "정식 형식"은 다음과 같이 파생 될 수 있습니다.

  • 3 사이의 숫자를 얻으려면 1 사이의 0 수를 세십시오 (랩 어라운드 무시).
  • 가장 큰 숫자가 첫 번째가되도록 세 개의 숫자를 돌립니다.
  • 첫 번째 숫자 ( a)는 18~ 52(포함) 사이 여야합니다 . 간으로 다시 인코딩을 0하고 34.
  • 두 번째 숫자는 ( b) 사이 여야 0하고 26, 그러나 그것은별로 중요하지 않습니다.
  • 세 번째 숫자는 삭제하고 52 - (a + b)정보를 추가하지 않기 때문에

정규 형식은 정수 b * 35 + a사이에, 0그리고 936(가 상당히 컴팩트 (포함), 477원형 고유 목록이 총).


0

나는 두 목록을 비교하고 각 반복에 대한 비교 값의 색인을 늘리고 감싸는 간단한 솔루션을 작성했습니다.

파이썬을 잘 모르므로 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

다른 사람들이 언급했듯이 목록의 정규 회전을 찾으면 비교할 수 있습니다.

이 작업을 수행하는 작업 코드가 있습니다. 기본 방법은 각 목록에 대해 정규화 된 회전을 찾아 비교하는 것입니다.

  • 각 목록에서 정규화 된 회전 지수를 계산합니다.
  • 두 목록을 오프셋으로 반복하여 각 항목을 비교하고 일치하지 않으면 반환합니다.

이 방법은 숫자에 의존하지 않으므로 문자열 목록 (비교할 수있는 값)을 전달할 수 있습니다.

List-in-list 검색을 수행하는 대신 목록을 최소값으로 시작하려고합니다. 따라서 최소값을 반복하여 연속 된 값이 가장 낮은 값을 찾을 때까지 검색하여 추가 비교를 위해 저장합니다. 우리가 최고가 될 때까지

인덱스를 계산할 때 일찍 종료 할 수있는 많은 기회가 있습니다. 일부 최적화에 대한 세부 사항입니다.

  • 최소값이 하나만있을 때 최상의 최소값 검색을 건너 뜁니다.
  • 이전 값이 최소값 일 때는 최소값 검색을 건너 뜁니다 (더 이상 일치하지는 않음).
  • 모든 값이 같으면 검색을 건너 뜁니다.
  • 목록의 최소값이 다르면 일찍 실패합니다.
  • 오프셋이 일치하면 정기적 인 비교를 사용하십시오.
  • 비교 중에 목록 중 하나의 색인 값을 줄이지 않도록 오프셋을 조정하십시오.

파이썬에서는리스트-인-리스트 검색이 더 빠를 수도 있지만, 다른 언어에서도 사용될 수있는 효율적인 알고리즘을 찾고 싶었습니다. 또한 새 목록을 만들지 않는 것이 좋습니다.

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

목록 A가 예상 O (N) 시간에 목록 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로 왼쪽으로 이동하면 모든 B [i]마다 첫 번째 P를 제외하고 여분의 P가 곱해집니다. 곱셈은 ​​더하기에 분산되므로 전체 해시를 곱하여 모든 구성 요소를 한 번에 곱한 다음 첫 번째 요소의 요인을 수정할 수 있습니다.

B의 왼쪽 시프트 해시는

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

두 번째 왼쪽 교대조 :

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 : 문자열을 선택하고 두 번째 단계로 순서를 확인하십시오
Louis

그것들은 같은 선형 순서가 아닙니다. 그것들은 동일한 '원형'순서로되어 있습니다. 2 단계로 설명하는 것은 원래 문제입니다.
GeneralBecos
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.