Cách kiểm tra xem hai danh sách có giống nhau trong Python không


145

Chẳng hạn, tôi có danh sách:

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

Họ dường như là khác nhau, nhưng nếu nó được coi là bắt đầu và kết thúc được kết nối, sau đó họ tròn giống hệt nhau.

Vấn đề là, mỗi danh sách mà tôi có độ dài 55 và chỉ chứa ba số và 52 số không trong đó. Không có điều kiện tuần hoàn, có 26.235 (55 chọn 3) danh sách. Tuy nhiên, nếu điều kiện 'thông tư' tồn tại, có một số lượng lớn các danh sách giống hệt nhau

Hiện tại tôi kiểm tra danh tính theo vòng tròn bằng cách sau:

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

Hàm này yêu cầu 55 thao tác dịch chuyển theo chu kỳ trong trường hợp xấu nhất. Và có 26.235 danh sách được so sánh với nhau. Tóm lại, tôi cần 55 * 26.235 * (26.235 - 1) / 2 = 18.926.847.225 tính toán. Đó là khoảng gần 20 Giga!

Có cách nào tốt để làm điều đó với ít tính toán không? Hoặc bất kỳ loại dữ liệu hỗ trợ thông tư ?


Chỉ là một linh cảm: Tôi cảm thấy rằng cây hậu tố có thể giúp đỡ ở đây. vi.wikipedia.org/wiki/Suffix_tree . Để xây dựng một cái, hãy xem en.wikipedia.org/wiki/Ukkonen%27s_alacticm
Rerito

1
@Mehrdad Nhưng thời gian chạy tệ hơn nhiều so với bất kỳ câu trả lời nào chuyển đổi thành dạng chính tắc, thời gian chạy kém hơn nhiều so với chuyển đổi sang số nguyên và thời gian chạy kém hơn nhiều so với David Eisenstat.
Veedrac

2
Tất cả các câu trả lời đang cố gắng giải quyết vấn đề chung, nhưng trong trường hợp cụ thể này chỉ với 3 câu hỏi, bạn có thể đại diện cho mọi danh sách với 3 số là một số không giữa các số. Danh sách từ câu hỏi có thể được trình bày dưới dạng [0,0,2], [0,2,0], [2,0,0]. Bạn chỉ có thể giảm danh sách trong một lần chạy và sau đó kiểm tra danh sách giảm. Nếu chúng "giống hệt nhau" thì bản gốc cũng vậy.
abc667

1
Tôi đoán Stack Overflow không cần bình chọn sau đó. Tất cả những gì chúng ta cần là chạy mã trong tất cả các giải pháp và trình bày chúng theo thứ tự hoàn thành.
Dawood ibn Kareem

2
Vì nó chưa được đề cập cho đến nay, "hình thức kinh điển" được đề cập bởi @ abc667, Veedrac và Eisenstat được gọi là Run length Encoding en.wikipedia.org/wiki/Run-length_encoding
David Lovell

Câu trả lời:


133

Trước hết, điều này có thể được thực hiện theo O(n)độ dài của danh sách Bạn có thể nhận thấy rằng nếu bạn sẽ nhân đôi danh sách của mình 2 lần ( [1, 2, 3]) [1, 2, 3, 1, 2, 3]thì danh sách mới của bạn chắc chắn sẽ giữ tất cả các danh sách tuần hoàn có thể.

Vì vậy, tất cả những gì bạn cần là kiểm tra xem danh sách bạn đang tìm kiếm có nằm trong danh sách 2 lần trong danh sách bắt đầu của bạn không. Trong python bạn có thể đạt được điều này theo cách sau (giả sử rằng độ dài là như nhau).

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

Một số giải thích về oneliner của tôi: list * 2sẽ kết hợp một danh sách với chính nó, map(str, [1, 2])chuyển đổi tất cả các số thành chuỗi và ' '.join()sẽ chuyển đổi mảng ['1', '2', '111']thành một chuỗi '1 2 111'.

Như được chỉ ra bởi một số người trong các bình luận, oneliner có khả năng đưa ra một số điểm tích cực sai, vì vậy để bao quát tất cả các trường hợp cạnh có thể:

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 khi nói về độ phức tạp thời gian, điều đáng chú ý O(n)sẽ đạt được nếu chuỗi con có thể được tìm thấy O(n)kịp thời. Không phải lúc nào cũng như vậy và phụ thuộc vào việc triển khai bằng ngôn ngữ của bạn ( mặc dù có khả năng nó có thể được thực hiện trong KMP thời gian tuyến tính chẳng hạn).

PS2 cho những người sợ chuỗi hoạt động và do thực tế này nghĩ rằng câu trả lời là không tốt. Điều quan trọng là sự phức tạp và tốc độ. Thuật toán này có khả năng chạy trong O(n)thời gian và O(n)không gian, làm cho nó tốt hơn nhiều so với mọi thứ trong O(n^2)miền. Để tự mình nhìn thấy điều này, bạn có thể chạy một điểm chuẩn nhỏ (tạo một danh sách ngẫu nhiên bật phần tử đầu tiên và nối nó vào cuối, do đó tạo ra một danh sách tuần hoàn. Bạn có thể tự do thực hiện các thao tác của riêng mình)

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 giây trên máy của tôi. Không thực sự dài. Bây giờ hãy thử so sánh điều này với O(n^2)các giải pháp. Trong khi so sánh nó, bạn có thể đi từ Mỹ đến Úc (rất có thể bằng tàu du lịch)


3
Chỉ cần thêm khoảng trắng đệm (1 trước và 1 sau mỗi chuỗi) sẽ thực hiện thủ thuật. Không cần phải quá phức tạp mọi thứ với regexes. (Tất nhiên tôi giả sử chúng ta so sánh các danh sách có cùng độ dài)
Rerito

2
@Rerito trừ khi một trong hai danh sách bao gồm các chuỗi, bản thân chúng có thể có khoảng trắng ở đầu hoặc cuối. Vẫn có thể gây ra va chạm.
Adam Smith

12
Tôi không thích câu trả lời này. Hoạt động chuỗi vô nghĩa khiến tôi không thích nó và câu trả lời của David Eisenstat khiến tôi sẵn sàng đánh giá thấp nó. Việc so sánh này có thể được thực hiện trong thời gian O (n) với một chuỗi nhưng cũng có thể được thực hiện trong thời gian O (n) với một số nguyên [cần 10k là tự xóa], nhanh hơn. Tuy nhiên, câu trả lời của David Eisenstat cho thấy rằng thực hiện bất kỳ so sánh nào là vô nghĩa vì câu trả lời không cần nó.
Veedrac

7
@Veedrac bạn đang đùa tôi à? Bạn đã nghe nói về độ phức tạp tính toán? Câu trả lời của Davids mất khoảng thời gian O (n ^ 2) và O (n ^ 2) chỉ để tạo ra tất cả các lần lặp lại của anh ta, ngay cả đối với các đầu vào nhỏ, 10 ^ 4 chiều dài mất 22 giây và ai biết được bao nhiêu ram. Chưa kể rằng chúng tôi chưa bắt đầu tìm kiếm bất cứ điều gì ngay bây giờ (chúng tôi chỉ tạo ra tất cả các vòng quay theo chu kỳ). Và chuỗi vô nghĩa của tôi cung cấp cho bạn một kết quả hoàn chỉnh cho các đầu vào như 10 ^ 6 trong chưa đầy 0,5 giây. Nó cũng cần không gian O (n) để lưu trữ nó. Vì vậy, xin vui lòng dành chút thời gian để hiểu câu trả lời trước khi đi vào kết luận.
Salvador Dali

1
@SalvadorDali Bạn dường như rất tập trung thời gian ;-)
e2-e4

38

Không đủ kiến ​​thức về Python để trả lời câu này bằng ngôn ngữ bạn yêu cầu, nhưng trong C / C ++, với các tham số của câu hỏi của bạn, tôi sẽ chuyển đổi các số 0 và số một thành bit và đẩy chúng lên các bit có trọng số thấp nhất của uint64_t. Điều này sẽ cho phép bạn so sánh tất cả 55 bit trong một lần trượt - 1 đồng hồ.

Rất nhanh, và toàn bộ điều này sẽ phù hợp với bộ nhớ cache trên chip (209.880 byte). Hỗ trợ phần cứng để chuyển đồng thời tất cả 55 thành viên danh sách chỉ có sẵn trong các thanh ghi của CPU. Điều tương tự cũng xảy ra khi so sánh tất cả 55 thành viên cùng một lúc. Điều này cho phép ánh xạ 1 đến 1 của vấn đề đối với giải pháp phần mềm. (và sử dụng các thanh ghi 256 bit SIMD / SSE, tối đa 256 thành viên nếu cần) Kết quả là mã ngay lập tức rõ ràng đối với người đọc.

Bạn có thể thực hiện điều này trong Python, tôi chỉ không biết rõ về nó nếu có thể hoặc hiệu suất có thể là gì.

Sau khi ngủ trên đó một vài điều đã trở nên rõ ràng, và tất cả đều tốt hơn.

1.) Thật dễ dàng để quay danh sách liên kết vòng tròn bằng cách sử dụng các bit mà thủ thuật rất thông minh của Dali là không cần thiết. Bên trong một dịch chuyển bit tiêu chuẩn 64 bit sẽ thực hiện xoay vòng rất đơn giản và trong nỗ lực làm cho điều này trở nên thân thiện hơn với Python, bằng cách sử dụng số học thay vì bit ops.

2.) Dịch chuyển bit có thể được thực hiện dễ dàng bằng cách chia cho 2.

3.) Việc kiểm tra kết thúc danh sách cho 0 hoặc 1 có thể dễ dàng thực hiện bằng modulo 2.

4.) "Di chuyển" 0 đến đầu danh sách từ đuôi có thể được thực hiện bằng cách chia cho 2. Điều này bởi vì nếu số 0 thực sự được di chuyển, nó sẽ làm cho bit thứ 55 sai, mà nó hoàn toàn không làm gì cả.

5.) "Di chuyển" 1 đến đầu danh sách từ đuôi có thể được thực hiện bằng cách chia cho 2 và thêm 18,014,398,509,481,984 - là giá trị được tạo bằng cách đánh dấu bit thứ 55 đúng và tất cả phần còn lại là sai.

6.) Nếu so sánh neo và uint64_t được soạn thảo là TRUE sau bất kỳ phép quay nào, ngắt và trả về TRUE.

Tôi sẽ chuyển đổi toàn bộ mảng danh sách thành một mảng uint64_ts ngay trước mặt để tránh phải thực hiện chuyển đổi nhiều lần.

Sau khi dành vài giờ cố gắng tối ưu hóa mã, nghiên cứu ngôn ngữ lắp ráp, tôi đã có thể tắt 20% thời gian chạy. Tôi nên thêm rằng trình biên dịch O / S và MSVC cũng đã được cập nhật vào giữa ngày hôm qua. Vì bất kỳ lý do gì, chất lượng mã mà trình biên dịch C tạo ra được cải thiện đáng kể sau khi cập nhật (15/11/2014). Thời gian chạy bây giờ là ~ 70 đồng hồ, 17 nano giây để soạn thảo và so sánh một vòng neo với tất cả 55 vòng của vòng thử nghiệm và NxN của tất cả các vòng so với tất cả các vòng khác được thực hiện trong 12,5 giây .

Mã này rất chặt chẽ, nhưng 4 thanh ghi đang ngồi xung quanh không làm gì 99% thời gian. Ngôn ngữ lắp ráp khớp với mã C gần như dòng cho dòng. Rất dễ đọc và dễ hiểu. Một dự án lắp ráp tuyệt vời nếu ai đó đang tự dạy mình điều đó.

Phần cứng là Hazwell i7, MSVC 64 bit, tối ưu hóa đầy đủ.

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

nhập mô tả hình ảnh ở đây


23
mọi người cứ nói về "giải pháp của salvador dali" và tôi chỉ ngồi đây bối rối, tự hỏi liệu họa sĩ cùng tên cũng là một nhà toán học đã đóng góp cho các thuật toán cổ điển theo một cách quan trọng nào đó. sau đó tôi nhận ra đó là tên người dùng của người đăng câu trả lời phổ biến nhất. tôi không phải là người đàn ông thông minh
Woodrow Barlow

Đối với bất kỳ ai có đại diện 10k và việc triển khai có sẵn ở đây bằng cách sử dụng Numpy và vector hóa. Gương chính cho những người <10k . Tôi đã xóa câu trả lời của tôi bởi vì David Eisenstat của câu trả lời điểm ra rằng bạn không cần phải làm so sánh ở tất cả khi bạn chỉ có thể tạo ra danh sách độc đáo ngay lập tức và tôi muốn khuyến khích mọi người sử dụng câu trả lời tốt hơn của mình.
Veedrac

@RocketRoy Tại sao bạn nghĩ Python sẽ không có hoạt động bit? Heck, tôi sử dụng các hoạt động bit trong mã tôi liên kết . Tôi vẫn nghĩ rằng câu trả lời này hầu hết là không cần thiết (câu trả lời của David Eisenstat mất 1ms cho toàn bộ), nhưng tôi thấy câu nói đó thật kỳ lạ. FWIW, một thuật toán tương tự trong Numpy để tìm kiếm 262M- "danh sách" mất khoảng 15 giây trên máy tính của tôi (giả sử không tìm thấy kết quả khớp nào), chỉ có sự xoay vòng của danh sách xảy ra ở vòng ngoài, không phải vòng trong.
Veedrac

@Quincunx, cảm ơn bạn đã chỉnh sửa để có được cú pháp tô màu chính xác cho C ++. Rất cảm kích!

@RocketRoy Không có vấn đề. Khi bạn trả lời rất nhiều câu hỏi trên PPCG , bạn sẽ học cách thực hiện cú pháp tô màu.
Justin

33

Đọc giữa các dòng, có vẻ như bạn đang cố gắng liệt kê một đại diện của mỗi loại chuỗi tương đương tròn với 3 chuỗi và 52 số không. Chúng ta hãy chuyển từ một đại diện dày đặc sang một số thưa thớt (bộ ba số trong range(55)). Trong đại diện này, sự thay đổi của hình tròn sbằng kđược cho bởi sự hiểu biết set((i + k) % 55 for i in s). Đại diện tối thiểu từ vựng trong một lớp luôn chứa vị trí 0. Cho một tập hợp biểu mẫu {0, i, j}với 0 < i < j, các ứng cử viên khác cho tối thiểu trong lớp là {0, j - i, 55 - i}{0, 55 - j, 55 + i - j}. Do đó, chúng ta cần (i, j) <= min((j - i, 55 - i), (55 - j, 55 + i - j))cho bản gốc là tối thiểu. Đây là một số mã liệt kê.

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 Bạn đã hiểu nhầm câu trả lời (Tôi cũng làm vậy cho đến khi anh ấy chỉ ra!). Điều này trực tiếp tạo ra "một đại diện của mỗi loại chuỗi tương đương tròn với 3 chuỗi và 52 số không". Mã của anh ta không tạo ra tất cả các vòng quay theo chu kỳ. Chi phí ban đầu¹ là T (55² · 26235²). Mã của bạn cải thiện 55² lên 55, vì vậy chỉ là T (55 * 26235²). Câu trả lời của David Eisenstat là từ 55² đến 55³ cho toàn bộ . 55³ ≪ 55 · 26235². Không nói các thuật ngữ big-O ở đây là chi phí thực tế trong O (1) trong mọi trường hợp.
Veedrac

1
@Veedrac Nhưng 99% độc giả sẽ đến với câu hỏi này trong tương lai, sẽ không có ràng buộc của anh ấy và tôi tin rằng câu trả lời của tôi sẽ phù hợp với họ hơn. Không làm phiền cuộc trò chuyện thêm nữa, tôi sẽ rời khỏi OP để giải thích chính xác anh ấy muốn gì.
Salvador Dali

5
@SalvadorDali OP dường như đã trở thành con mồi của Vấn đề XY . May mắn thay, chính câu hỏi đã làm rõ tiêu đề không có gì, và David đã có thể đọc được giữa các dòng. Nếu đây là trường hợp thực tế, thì điều đúng đắn cần làm là thay đổi tiêu đề và giải quyết vấn đề thực tế, thay vì trả lời tiêu đề và bỏ qua câu hỏi.
Aaron Dufour

1
@SalvadorDali, dưới vỏ bọc, mã Python của bạn đang gọi tương đương với strstr của C () tìm kiếm một chuỗi cho chuỗi con. Đến lượt nó gọi strcmp (), chạy vòng lặp for () so sánh mỗi char trong chuỗi1 với chuỗi2. Do đó, những gì trông giống O (n) là O (n * 55 * 55) giả sử tìm kiếm thất bại. Ngôn ngữ cấp cao là con dao 2 lưỡi. Họ ẩn chi tiết thực hiện với bạn, nhưng sau đó họ cũng ẩn chi tiết triển khai từ bạn. FWIW, cái nhìn sâu sắc của bạn để nối danh sách là tuyệt vời. Nhanh hơn vẫn là uint8, và nhanh hơn nhiều so với bit - có thể dễ dàng xoay trong phần cứng.

2
@AleksandrDubinsky Đơn giản cho máy tính, phức tạp hơn cho con người. Nó đủ nhanh như vậy.
David Eisenstat

12

Lặp lại mảng đầu tiên, sau đó sử dụng thuật toán Z (O (n) time) để tìm mảng thứ hai bên trong mảng thứ nhất.

(Lưu ý: bạn không phải sao chép vật lý mảng đầu tiên. Bạn chỉ có thể quấn quanh trong khi khớp.)

Điểm hay của thuật toán Z là nó rất đơn giản so với KMP, BM, v.v.
Tuy nhiên, nếu bạn cảm thấy tham vọng, bạn có thể thực hiện khớp chuỗi trong thời gian tuyến tính và không gian không đổi - strstrví dụ, thực hiện điều này. Thực hiện nó sẽ đau đớn hơn, mặc dù.


6

Theo dõi giải pháp rất thông minh của Salvador Dali, cách tốt nhất để xử lý nó là đảm bảo tất cả các yếu tố có cùng độ dài, cũng như cả hai DANH SÁCH đều có cùng độ dài.

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

Không có manh mối nào nếu điều này nhanh hơn hoặc chậm hơn giải pháp regex được đề nghị của AshwiniChaudhary trong câu trả lời của Salvador Dali, có nội dung:

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
wiki điều này vì về cơ bản tôi chỉ điều chỉnh câu trả lời của Salvador Dali và định dạng các thay đổi của Ashwini. Rất ít trong số này thực sự là của tôi.
Adam Smith

1
Cảm ơn bạn đã nhập. Tôi nghĩ rằng tôi đã bao gồm tất cả các trường hợp có thể trong giải pháp chỉnh sửa của mình. Hãy cho tôi biết nếu thiếu một cái gì đó.
Salvador Dali

@SalvadorDali ah, vâng ... kiểm tra xem các chuỗi có cùng độ dài không. Tôi ủng hộ việc đó sẽ dễ dàng hơn là chạy qua danh sách tìm kiếm phần tử dài nhất, sau đó gọi str.format nthời gian để định dạng chuỗi kết quả. TÔI CUNG CẤP .... :)
Adam Smith

3

Cho rằng bạn cần phải thực hiện nhiều so sánh như vậy có đáng để bạn thực hiện trong khi vượt qua các danh sách ban đầu để chuyển đổi chúng thành một dạng chính tắc nào đó có thể dễ dàng so sánh?

Bạn đang cố gắng để có được một bộ danh sách độc đáo tròn? Nếu vậy bạn có thể ném chúng vào một bộ sau khi chuyển đổi thành bộ dữ liệu.

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)

Xin lỗi David Eisenstat vì đã không phát hiện ra câu trả lời v.similar của anh ấy.


3

Bạn có thể cuộn một danh sách như thế này:

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

Đầu chuyển đổi tất cả các yếu tố danh sách của bạn (trong một bản sao nếu cần thiết) để phiên bản xoay đó là lexically nhất.

Sau đó sắp xếp danh sách kết quả của danh sách (giữ lại một chỉ mục vào vị trí danh sách ban đầu) và thống nhất danh sách đã sắp xếp, đánh dấu tất cả các bản sao trong danh sách ban đầu nếu cần.


2

Cõng theo quan sát của @ SalvadorDali về việc tìm kiếm các trận đấu của bất kỳ lát cắt có kích thước dài nào trong b + b, đây là một giải pháp chỉ sử dụng các thao tác liệt kê.

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

Cách tiếp cận thứ 2: [đã xóa]


Phiên bản đầu tiên là O (n²) và phiên bản thứ hai không hoạt động rollmatch([1, 0, 1, 1], [0, 1, 1, 1]).
Veedrac

Bắt tốt đẹp, tôi sẽ xóa nó!
PaulMcG

1

Không phải là một câu trả lời hoàn chỉnh, tự do, nhưng về chủ đề tối ưu hóa bằng cách giảm so sánh, tôi cũng đã nghĩ đến các biểu diễn được chuẩn hóa.

Cụ thể, nếu bảng chữ cái đầu vào của bạn là {0, 1}, bạn có thể giảm đáng kể số lượng hoán vị được phép. Xoay danh sách đầu tiên sang dạng chuẩn hóa (giả) (được phân phối trong câu hỏi của bạn, tôi sẽ chọn một trong đó một trong số 1 bit ở cực bên trái và một trong 0 bit ở cực bên phải). Bây giờ trước mỗi so sánh, lần lượt xoay danh sách khác qua các vị trí có thể có cùng mẫu căn chỉnh.

Ví dụ: nếu bạn có tổng cộng bốn 1 bit, có thể có tối đa 4 hoán vị với sự liên kết này và nếu bạn có các cụm 1 bit liền kề, thì mỗi bit bổ sung trong cụm đó sẽ giảm số lượng vị trí.

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

Điều này khái quát cho bảng chữ cái lớn hơn và các mẫu căn chỉnh khác nhau; thách thức chính là tìm ra một sự bình thường hóa tốt chỉ với một vài đại diện có thể. Lý tưởng nhất, đó sẽ là một sự chuẩn hóa đúng đắn, với một đại diện duy nhất, nhưng đưa ra vấn đề, tôi không nghĩ điều đó là có thể.


0

Xây dựng thêm về câu trả lời của RocketRoy: Chuyển đổi tất cả danh sách của bạn lên phía trước thành số 64 bit không dấu. Đối với mỗi danh sách, xoay 55 bit đó xung quanh để tìm giá trị số nhỏ nhất.

Bây giờ bạn còn lại một giá trị 64 bit không dấu cho mỗi danh sách mà bạn có thể so sánh thẳng với giá trị của các danh sách khác. Hàm is_circular_identical () không còn cần thiết nữa.

(Về bản chất, bạn tạo một giá trị nhận dạng cho danh sách của mình mà không bị ảnh hưởng bởi vòng xoay của các thành phần danh sách) Điều đó thậm chí sẽ hoạt động nếu bạn có số lượng tùy ý trong danh sách của mình.


0

Đây là ý tưởng tương tự của Salvador Dali nhưng không cần sự hội tụ chuỗi. Đằng sau là cùng một ý tưởng phục hồi KMP để tránh kiểm tra thay đổi không thể. Họ chỉ gọi 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());
        }
    }

Hy vọng điều này giúp đỡ!


0

Đơn giản hóa vấn đề

  • Vấn đề bao gồm danh sách các mặt hàng được đặt hàng
  • Miền giá trị là nhị phân (0,1)
  • Chúng ta có thể giảm vấn đề bằng cách ánh xạ 1s liên tiếp vào số đếm
  • và liên tiếp 0s thành một số âm

Thí dụ

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 ]
  • Quá trình này yêu cầu mục đầu tiên và mục cuối cùng phải khác nhau
  • Điều này sẽ làm giảm số lượng so sánh tổng thể

Quy trình kiểm tra

  • Nếu chúng ta cho rằng chúng trùng lặp, thì chúng ta có thể giả định những gì chúng ta đang tìm kiếm
  • Về cơ bản, mục đầu tiên từ danh sách đầu tiên phải tồn tại ở đâu đó trong danh sách khác
  • Tiếp theo là những gì được theo dõi trong danh sách đầu tiên, và theo cách tương tự
  • Các mục trước phải là mục cuối cùng từ danh sách đầu tiên
  • Vì nó là hình tròn, nên thứ tự là như nhau

Sự nắm lấy

  • Câu hỏi ở đây là bắt đầu từ đâu, được gọi là kỹ thuật lookuplook-ahead
  • Chúng tôi sẽ chỉ kiểm tra nơi phần tử đầu tiên của danh sách đầu tiên tồn tại thông qua danh sách thứ hai
  • Xác suất của yếu tố thường xuyên thấp hơn do chúng tôi đã ánh xạ các danh sách thành biểu đồ

Mã giả

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

Chức năng

  • MAP_LIST(LIST A):LIST BẢN ĐỒ YẾU TỐ YÊU CẦU NHƯ CÁC QUỐC GIA TRONG DANH SÁCH MỚI

  • LOOKUP_INDEX(LIST A, INTEGER E):LISTRETURN DANH MỤC CÁC CHỈ SỐ NƠI THE ELEMENT Etồn tại trong DANHA

  • COUNT_CHAR(LIST A , INTEGER E):INTEGERQUẬN NHIỀU THỜI GIAN NHIỀU EOCCUR TRONG DANH SÁCHA

  • ALPHA_NGRAM(LIST A,LIST B,INTEGER I,INTEGER N):BOOLEANKIỂM TRA NẾU B[I]CÓ THIẾT BỊ A[0] N-GRAMTRÊN TRỰC TIẾP


Cuối cùng

Nếu kích thước danh sách sẽ khá lớn hoặc nếu phần tử chúng ta bắt đầu kiểm tra chu kỳ từ mức cao thường xuyên, thì chúng ta có thể làm như sau:

  • Tìm kiếm mục ít gặp nhất trong danh sách đầu tiên để bắt đầu

  • tăng tham số N-gram N để giảm xác suất vượt qua kiểm tra tuyến tính


0

Một "hình thức chính tắc" hiệu quả, nhanh chóng để tính toán cho các danh sách được đề cập có thể được lấy từ:

  • Đếm số lượng số 0 giữa các số (bỏ qua bao quanh), để có ba số.
  • Xoay ba số sao cho số lớn nhất là đầu tiên.
  • Số đầu tiên ( a) phải nằm giữa 1852(đã bao gồm). Mã hóa lại nó là giữa 034.
  • Số thứ hai ( b) phải nằm giữa 026, nhưng nó không quan trọng lắm.
  • Bỏ số thứ ba, vì nó chỉ 52 - (a + b)và không thêm thông tin

Dạng chính tắc là số nguyên b * 35 + anằm giữa 0936(bao gồm), khá nhỏ gọn (có 477tổng số danh sách duy nhất theo vòng tròn).


0

Tôi đã viết một giải pháp đơn giản để so sánh cả hai danh sách và chỉ tăng (và bao quanh) chỉ số của giá trị được so sánh cho mỗi lần lặp.

Tôi không biết rõ về python nên tôi đã viết nó bằng Java, nhưng nó thực sự đơn giản nên dễ dàng thích nghi với bất kỳ ngôn ngữ nào khác.

Bằng cách này, bạn cũng có thể so sánh danh sách các loại khác.

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

Như những người khác đã đề cập, một khi bạn tìm thấy vòng quay chuẩn hóa của một danh sách, bạn có thể so sánh chúng.

Đây là một số mã làm việc thực hiện điều này, Phương pháp cơ bản là tìm một vòng quay chuẩn hóa cho mỗi danh sách và so sánh:

  • Tính toán một chỉ số xoay vòng chuẩn hóa trên mỗi danh sách.
  • Lặp lại cả hai danh sách với phần bù của chúng, so sánh từng mục, trả về nếu chúng khớp sai.

Lưu ý rằng phương pháp này không phụ thuộc vào số, bạn có thể chuyển vào danh sách các chuỗi (bất kỳ giá trị nào có thể so sánh).

Thay vì thực hiện tìm kiếm danh sách trong danh sách, chúng tôi biết rằng chúng tôi muốn danh sách bắt đầu với giá trị tối thiểu - vì vậy chúng tôi có thể lặp qua các giá trị tối thiểu, tìm kiếm cho đến khi chúng tôi tìm thấy giá trị nào có giá trị thấp nhất liên tiếp, lưu trữ này để so sánh thêm cho đến khi chúng ta có điều tốt nhất

Có nhiều cơ hội để thoát sớm khi tính toán chỉ số, chi tiết về một số tối ưu hóa.

  • Bỏ qua tìm kiếm giá trị tối thiểu tốt nhất khi chỉ có một.
  • Bỏ qua tìm kiếm các giá trị tối thiểu khi trước đó cũng là một giá trị tối thiểu (nó sẽ không bao giờ phù hợp hơn).
  • Bỏ qua tìm kiếm khi tất cả các giá trị là như nhau.
  • Thất bại sớm khi danh sách có giá trị tối thiểu khác nhau.
  • Sử dụng so sánh thường xuyên khi offset phù hợp.
  • Điều chỉnh độ lệch để tránh gói các giá trị chỉ mục vào một trong các danh sách trong khi so sánh.

Lưu ý rằng trong Python, tìm kiếm trong danh sách có thể nhanh hơn, tuy nhiên tôi rất thích tìm một thuật toán hiệu quả - cũng có thể được sử dụng trong các ngôn ngữ khác. Ngoài ra, có một số lợi thế để tránh để tạo danh sách mới.

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)

Xem: đoạn trích này để biết thêm một số bài kiểm tra / ví dụ.


0

Bạn có thể kiểm tra xem danh sách A có bằng với sự dịch chuyển theo chu kỳ của danh sách B trong thời gian O (N) dự kiến ​​khá dễ dàng không.

Tôi sẽ sử dụng hàm băm đa thức để tính toán hàm băm của danh sách A và mọi thay đổi theo chu kỳ của danh sách B. Trường hợp một sự thay đổi của danh sách B có cùng hàm băm như danh sách A, tôi sẽ so sánh các phần tử thực tế để xem chúng có bằng nhau không .

Lý do nhanh là vì với hàm băm đa thức (cực kỳ phổ biến!), Bạn có thể tính băm của mỗi ca tuần hoàn từ lần trước trong thời gian không đổi, vì vậy bạn có thể tính băm cho tất cả các dịch chuyển theo chu kỳ trong O ( N) thời gian.

Nó hoạt động như thế này:

Giả sử B có N phần tử, thì hàm băm của B sử dụng số nguyên tố P là:

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

Đây là một cách tối ưu hóa để đánh giá đa thức trong P và tương đương với:

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

Lưu ý rằng mọi B [i] được nhân với P ^ (N-1-i). Nếu chúng ta dịch chuyển B sang trái 1, thì mỗi B [i] sẽ được nhân với một P phụ, ngoại trừ cái đầu tiên. Vì phép nhân phân phối trên phép cộng, chúng ta có thể nhân tất cả các thành phần cùng một lúc chỉ bằng cách nhân toàn bộ hàm băm, sau đó sửa lại hệ số cho phần tử đầu tiên.

Hàm băm của dịch chuyển trái của B chỉ là

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

Ca trái thứ hai:

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

và như thế...

LƯU Ý: tất cả các phép toán ở trên được thực hiện modulo một số kích thước từ của máy và bạn chỉ phải tính P ^ N một lần.


-1

Để dán theo cách pythonic nhất để làm điều đó, sử dụng bộ!

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

điều này cũng phù hợp với các chuỗi có cùng số 0 và 1 không nhất thiết phải theo cùng một thứ tự
GeneralBecos

GeneralBecos: Chỉ cần chọn các chuỗi đó và kiểm tra thứ tự trong bước thứ hai
Louis

Chúng không theo thứ tự tuyến tính giống nhau. Chúng theo thứ tự 'tròn'. Những gì bạn mô tả như bước 2 là vấn đề ban đầu.
GeneralBecos
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.