Як перевірити, чи два списки кругові однакові в Python


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 і містить у ньому лише три та 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
Rerito

1
@Mehrdad Але набагато гірший час роботи, ніж будь-яка відповідь, що перетворюється на канонічну форму, набагато гірший час роботи, ніж перехід на ціле число і далеко, набагато гірший час роботи, ніж у Девіда Ейзенстата.
Ведрак

2
Усі відповіді намагаються вирішити загальну проблему, але в цьому конкретному випадку, лише з 3-ма, ви можете представляти кожен список, у якому 3 числа є числом нулів між ними. Список запитання можна представити у вигляді [0,0,2], [0,2,0], [2,0,0]. Ви можете просто зменшити список за один запуск, а потім перевірити зменшений список. Якщо вони "кругові однакові", то оригінали теж.
abc667

1
Я думаю, переповнення стека не потребує голосування. Все, що нам потрібно - це запустити код у всіх рішеннях і подати їх у тому порядку, в якому вони закінчуються.
Dawood ibn Kareem

2
Оскільки досі не згадувалося, "канонічну форму", на яку посилаються @ abc667, Veedrac та Eisenstat, називають Run Run Encoding en.wikipedia.org/wiki/Run-length_encoding
Девід Ловелл

Відповіді:


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

Деякі пояснення щодо мого 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 перед та 1 після кожної рядки) виконає трюк. Не потрібно занадто ускладнювати речі з допомогою регулярних виразів. (Звичайно, я припускаю, що ми порівнюємо списки однакової довжини)
Rerito

2
@Rerito, якщо жоден список не містить рядки, які самі можуть мати провідні або кінцеві пробіли. Все-таки може спричинити зіткнення.
Адам Сміт

12
Мені не подобається ця відповідь. Дурницька операція дурниць змусила мене не сподобатися, і відповідь Девіда Ейзенстата змусила мене зголоситися. Це порівняння може бути виконано в O (n) час за допомогою рядка, але це також можна зробити в O (n) час з цілим числом [потрібно 10k як самостійно видалено], що швидше. Тим не менш, відповідь Девіда Ейзенстата показує, що робити будь-яке порівняння безглуздо, оскільки відповідь не потребує цього.
Ведрак

7
@Veedrac ти мене жартуєш? Ви чули про складність обчислень? Відповідь Девідса займає час O (n ^ 2) і O (n ^ 2) просто для того, щоб створити всі його повторення, які навіть для невеликих входів довжиною 10 ^ 4 займають приблизно 22 секунди, і хто знає, скільки таран. Не кажучи вже про те, що ми не почали нічого шукати зараз (ми просто створили всі циклічні обертання). І мої строкові дурниці дають вам повний результат для таких даних, як 10 ^ 6, менше ніж за 0,5 секунди. Для його зберігання також потрібен O ​​(n) простір. Тому, будь ласка, знайдіть трохи часу, щоб зрозуміти відповідь, перш ніж прийти до висновку.
Сальвадор Далі

1
@SalvadorDali Ви, здається, дуже (м'які) зосереджені на часі ;-)
e2-e4

38

Не достатньо обізнаний у Python, щоб відповісти на це запитуваною мовою, але в C / C ++, з огляду на параметри вашого запитання, я перетворив би нулі та одиниці в біти і натиснув їх на найменш значущі біти uint64_t. Це дозволить вам порівняти всі 55 біт одним махом - 1 годин.

Злісно швидко, і вся справа впишеться в чіпи кешу (209,880 байт). Апаратна підтримка для одночасного переміщення всіх 55 членів списку доступна лише в регістрах процесора. Те саме стосується порівняння всіх 55 членів одночасно. Це дозволяє зіставити проблему 1 на 1 із програмним рішенням. (і за допомогою 256-бітових регістрів SIMD / SSE, до 256 членів, якщо потрібно). В результаті код читачеві відразу стає очевидним.

Можливо, ви зможете реалізувати це в Python, я просто не знаю цього досить добре, щоб знати, чи можливо це чи яка може бути ефективність.

Після сну на ньому кілька речей стали очевидними, і все на краще.

1.) Настільки легко закручувати круговий список за допомогою бітів, що дуже розумний трюк Далі не потрібен. Всередині 64-розрядного регістру стандартне переміщення бітів здійснить обертання дуже просто, і намагаючись зробити це ще більш зручним для Python, використовуючи арифметику замість бітових ops.

2.) Переміщення бітів можна легко здійснити, використовуючи ділення на 2.

3.) Перевірка кінця списку на 0 або 1 може бути легко виконана за модулем 2.

4.) "Переміщення" 0 на голову списку з хвоста можна здійснити діленням на 2. Це тому, що якби нуль був фактично переміщений, це зробило б 55-й біт помилковим, що вже робиться абсолютно нічого.

5.) "Переміщення" a 1 на голову списку з хвоста можна зробити, поділивши на 2 і додавши 18,014,398,509,481,984 - це значення, створене шляхом позначення 55-го біта істинним, а всі інші помилковими.

6.) Якщо порівняння якоря і складеного uint64_t дорівнює TRUE після будь-якого заданого обертання, перервіть і поверніть ІСТИНА.

Я перетворив би весь масив списків у масив uint64_ts прямо вперед, щоб уникнути повторного перетворення.

Провівши кілька годин, намагаючись оптимізувати код, вивчаючи мову складання, я зміг поголити 20% часу виконання. Додам, що компілятор O / S та MSVC також вчора оновився в середині дня. З будь-якої причини / s, якість коду, який видав компілятор C, значно покращився після оновлення (15.11.2014). Час виконання становить ~ 70 годин, 17 наносекунд для складання та порівняння якірного кільця з усіма 55 витками тестового кільця та NxN всіх кілець проти всіх інших робиться за 12,5 секунд .

Цей код настільки щільний, що всі, крім 4 реєстрів, сидять, не займаючись нічого 99% часу. Мова складання майже відповідає рядку коду С для рядка. Дуже легко читати та розуміти. Чудовий монтажний проект, якби хтось навчав цього.

Обладнання Hazwell i7, 64-розрядний MSVC, повна оптимізація.

#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 кп, і тут доступна реалізація за допомогою Numpy та векторизації. Суттєве дзеркало для цих <10k . Я видалив свою відповідь , тому що відповідь Девіда Айзенштат вказує, що вам не потрібно робити порівняння на всіх , як ви можете просто генерувати унікальні списки відразу , і я хочу , щоб заохочувати людей , щоб використовувати його набагато краще відповісти.
Ведрак

@RocketRoy Чому, на вашу думку, Python не мав би операцій? Чорт, я використовую бітові операції в коді, який я зв'язав . Я все ще вважаю, що ця відповідь здебільшого не потрібна (відповідь Девіда Ейзенстата займає 1мс на всю справу), але я вважав це твердження дивним. FWIW, аналогічний алгоритм Numpy для пошуку 262M- "списків" займає на моєму комп’ютері близько 15 секунд (якщо припустити, що не знайдено відповідності), лише обертання списку відбувається у зовнішньому циклі, а не у внутрішньому.
Ведрак

@Quincunx, дякую вам за вашу редакцію, щоб отримати синтаксичне забарвлення правильним для C ++. Дуже вдячний!

@RocketRoy Немає проблем. Коли ви відповідаєте на багато запитань щодо PPCG , ви дізнаєтесь, як робити забарвлення синтаксису.
Джастін

33

Читаючи між рядками, це здається так, ніби ви намагаєтесь перерахувати одного представника кожного кругового рівня еквівалентності рядків з 3 одиницями та 52 нулями. Перейдемо від щільного подання до розрізненого (набір з трьох чисел у 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

2
@SalvadorDali Ви неправильно зрозуміли відповідь (я теж робив це, поки він не вказав на це!). Це безпосередньо генерує "одного представника кожного кругового рівня еквівалентності рядків з 3 одиницями та 52 нулями". Його код не генерує всіх циклічних обертів. Початкова вартість¹ становить T (55² · 26235²). Ваш код покращує 55² до 55, так само T (55 * 26235²). Відповідь Девіда Ейзенстата - від 55 до 55 до всього . 55³ ≪ 55 · 26235². HereНе кажучи тут про великі терміни O як фактичну вартість у O (1) у всіх випадках.
Ведрак

1
@Veedrac Але 99% читачів, які в майбутньому прийдуть до цього питання, не матимуть його обмежень, і я вважаю, що моя відповідь їм підійде краще. Не роздуваючи далі розмови, я залишу в ОП пояснити, що саме він хоче.
Сальвадор Далі

5
@SalvadorDali OP, здається, стала здобиччю проблеми XY . На щастя, саме питання дає зрозуміти, чого не має заголовок, і Девід зміг прочитати між рядків. Якщо це насправді так, то правильне, що потрібно зробити, - це змінити назву та вирішити актуальну проблему, а не відповідати заголовком та ігнорувати питання.
Аарон Дюфур

1
@SalvadorDali, під обкладинками ваш код Python викликає еквівалент strstr () C, який шукає рядок для підрядка. Це в свою чергу викликає strcmp (), який виконує цикл for (), порівнюючи кожну таблицю в string1 з string2. Тому те, що схоже на O (n), є O (n * 55 * 55), припускаючи пошук до невдачі. Мови високого рівня - меч з двома кінцями. Вони приховують від вас деталі реалізації, але потім також приховують від вас деталі реалізації. FWIW, ваше уявлення про об'єднання списку було геніальним. Швидше, як uint8, і набагато швидше, ніж біти - які можна легко обертати в апаратному забезпеченні.

2
@AleksandrDubinsky Простіший для комп'ютера, складніший для людини. Це досить швидко, як є.
Девід Ейзенстат

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

Немає поняття, чи швидше це чи повільніше, ніж рекомендований рішенням регулярного виразного походження 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
wiki'd це, оскільки я в основному лише підробив відповідь Сальвадора Далі та відформатував зміни Ашвіні. Дуже мало цього насправді є моїм.
Адам Сміт

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)

Вибачте Девіда Ейзенстата за те, що не помітив його подібну відповідь.


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

Piggybacking на спостереженні @ SalvadorDali щодо пошуку збігів будь-якого з подовженого розміру зрізу в b + b, ось рішення, використовуючи операції просто списку.

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²), а друга не працює rollmatch([1, 0, 1, 1], [0, 1, 1, 1]).
Ведрак

Гарний улов, я його видалю!
PaulMcG

1

Не повна, вільна відповідь, але на тему оптимізації шляхом зменшення порівнянь я теж думав про нормалізовані уявлення.

А саме, якщо ваш вхідний алфавіт дорівнює {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

Це узагальнює великі алфавіти та різні шаблони вирівнювання; головне завдання - знайти хорошу нормалізацію лише з кількома можливими уявленнями. В ідеалі це була б належна нормалізація з єдиним унікальним представленням, але враховуючи проблему, я не думаю, що це можливо.


0

Надалі далі на відповідь RocketRoy: Перетворіть усі свої списки вперед на 64-бітові без підпису. Для кожного списку обертайте ці 55 біт навколо, щоб знайти найменше числове значення.

Тепер вам залишається одне неподписане 64-бітове значення для кожного списку, яке ви можете порівняти прямо зі значенням інших списків. Функція is_circular_identical () більше не потрібна.

(По суті, ви створюєте значення ідентичності для своїх списків, на яке не впливає поворот елементів списків). Це навіть спрацювало, якщо у ваших списках є довільне число.


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)
  • Ми можемо зменшити проблему, зіставивши послідовну 1s у кількість
  • і послідовний 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 ]
  • Цей процес вимагає, щоб перший і останній елемент мали бути різними
  • Це зменшить загальну кількість порівнянь

Процес перевірки

  • Якщо ми припустимо, що вони дублюються, то ми можемо припустити те, що шукаємо
  • В основному перший елемент з першого списку повинен існувати десь в іншому списку
  • Далі слідує те, що слідує у першому списку, і таким же чином
  • Попередні пункти повинні бути останніми з першого списку
  • Оскільки він круговий, порядок той самий

Хватка

  • Тут питання - з чого почати, технічно відомий як lookupіlook-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):INTEGERСКЛАДУЙТЕ, КОЛЬКІ EЧАСИ ЕЛЕМЕНТ ОКУР У СПИСОКІA

  • ALPHA_NGRAM(LIST A,LIST B,INTEGER I,INTEGER N):BOOLEANПЕРЕВІРТЕ, ЯКЩО B[I]РІВНОВАЄ ДЛЯ A[0] N-GRAMДІЯЛЬНИХ НАПРАВЛІВ


Нарешті

Якщо розмір списку буде досить величезним або якщо елемент, з якого ми починаємо перевіряти цикл, часто високий, то ми можемо зробити наступне:

  • Шукайте найменш частий предмет у першому списку, для початку

  • збільшити n-грам N-параметр, щоб знизити ймовірність проходження лінійної перевірки


0

Ефективна, швидка для обчислення "канонічна форма" для відповідних списків може бути отримана як:

  • Порахуйте кількість нулів між ними (ігноруючи обертання), щоб отримати три числа.
  • Поверніть три числа так, щоб найбільше число було першим.
  • Перше число ( a) повинно бути між 18і 52(включно). Перекодуйте його як між 0і 34.
  • Друге число ( b) повинно бути між 0і 26, але це не має великого значення.
  • Відкиньте третє число, оскільки це просто 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

Як уже згадували інші, щойно ви знайдете нормалізований поворот списку, ви можете порівняти їх.

Ось деякий робочий код, який робить це, Основний метод полягає у пошуку нормалізованого обертання для кожного списку та порівнянні:

  • Обчисліть нормалізований індекс обертання у кожному списку.
  • Переведіть петлі на обидва списки з їх зрушеннями, порівнюючи кожен елемент, повертаючи, якщо вони не відповідають.

Зауважте, що цей метод не залежить від чисел, ви можете передавати у списки рядків (будь-які значення, які можна порівняти).

Замість того, щоб здійснювати пошук у списку, ми знаємо, що хочемо, щоб список починався з мінімального значення - тому ми можемо перебирати мінімальні значення, шукаючи, поки не знайдемо, яке з них має найнижчі послідовні значення, зберігаючи це для подальших порівнянь поки у нас є найкращі.

Існує багато можливостей вийти рано при обчисленні індексу, детальна інформація про деякі оптимізації.

  • Пропустіть пошук найкращого мінімального значення, коли є лише одне.
  • Пропустіть пошук мінімальних значень, коли попереднє також є мінімальним значенням (воно ніколи не буде кращим).
  • Пропустіть пошук, коли всі значення однакові.
  • Невдача рано, коли списки мають різні мінімальні значення.
  • Використовуйте регулярне порівняння, коли компенсації відповідають.
  • Відрегулюйте зміщення, щоб уникнути загортання значень індексу в одному зі списків під час порівняння.

Зауважте, що в 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

Ви можете перевірити, чи список A дорівнює циклічному зсуву списку B у очікуваний час O (N) досить легко.

Я використовував би функцію полінома хешу, щоб обчислити хеш списку А та кожен циклічний зсув списку В. Якщо зсув списку В має той же хеш, що і список А, я порівняв би фактичні елементи, щоб побачити, чи вони рівні .

Причина цього швидко полягає в тому, що за допомогою поліноміальних хеш-функцій (які є надзвичайно поширеними!) Ви можете обчислювати хеш кожного циклічного зсуву від попереднього за постійний час, так що ви можете обчислити хеші для всіх циклічних зрушень в O ( N) час.

Це працює так:

Скажімо, B має N елементів, то хеш B з використанням простого P:

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

Це оптимізований спосіб оцінювання полінома в Р і еквівалентний:

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.


-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, - це первісна проблема.
GeneralBecos
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.