Как проверить, одинаковы ли два списка в Python

Например, у меня есть списки:

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

Они кажутся разными, но если предполагается, что начало и конец связаны, то они кругообразно идентичны.

Проблема заключается в том, что каждый список, который у меня есть, имеет длину 55 и содержит только три и 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 гига!

Есть ли хороший способ сделать это с меньшими вычислениями? Или любые типы данных, которые поддерживают круговое?

Ответ 1

Во-первых, это можно сделать в 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

P.S.1, говоря о временной сложности, стоит заметить, что O(n) будет достигнуто, если подстроку можно найти в O(n) времени. Это не всегда так и зависит от реализации на вашем языке ( хотя, возможно, это может быть сделано в режиме линейного времени KMP, например).

P.S.2 для людей, которые боятся функционирования струн и из-за этого считают, что ответ не очень хорош. Какова важна сложность и скорость. Этот алгоритм потенциально работает в 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). Пока он сравнивает это, вы можете путешествовать из США в Австралию (скорее всего, круизным судном)

Ответ 2

Не достаточно хорошо осведомлен в Python, чтобы ответить на это на запрошенном вами языке, но в 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.) "Перемещение" 1 в голову списка из хвоста можно сделать, разделив на 2 и добавив 18,014,398,509,481,984 - это значение, созданное путем маркировки 55-го бита true и всего остального false.

6.) Если сравнение якоря и составленного uint64_t истинно после любого заданного вращения, перерыв и возврат TRUE.

Я бы преобразовал весь массив списков в массив uint64_ts прямо перед тем, чтобы избежать повторного преобразования.

Проведя несколько часов, пытаясь оптимизировать код, изучая язык ассемблера, я смог сэкономить 20% от времени выполнения. Я должен добавить, что компилятор O/S и MSVC вчера был обновлен в середине дня. По какой-либо причине/с качество кода, созданного компилятором C, значительно улучшилось после обновления (11/15/2014). Время выполнения составляет ~ 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 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 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;
}

enter image description here

Ответ 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

Ответ 4

Повторите первый массив, затем используйте Z-алгоритм (время O (n)), чтобы найти второй массив внутри первого.

(Примечание: вам не нужно физически копировать первый массив. Вы можете просто обернуть во время сопоставления.)

Хорошая вещь о алгоритме Z заключается в том, что он очень прост по сравнению с KMP, BM и т.д.
Однако, если вы чувствуете амбициозность, вы можете выполнить сопоставление строк в линейном времени и постоянном пространстве - например, strstr. Однако выполнение этого было бы более болезненным.

Ответ 5

Следуя за Сальвадором Дали очень умным решением, лучший способ справиться с этим - убедиться, что все элементы имеют одинаковую длину, а оба LISTS имеют одинаковую длину.

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

Ответ 6

Учитывая, что вам нужно сделать так много сравнений, возможно, стоит потратить время на прохождение ваших списков, чтобы преобразовать их в какую-то каноническую форму, которую можно легко сравнить?

Вы пытаетесь получить набор циклически уникальных списков? Если это так, вы можете отправить их в набор после преобразования в кортежи.

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.sililar ответ.

Ответ 7

Вы можете свернуть один список следующим образом:

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

Ответ 8

Сначала преобразуйте каждый из ваших элементов списка (в случае необходимости в копию) в эту повернутую версию, максимально лексически.

Затем сортируйте полученный список списков (сохраняя индекс в исходной позиции списка) и объедините отсортированный список, пометив все дубликаты в исходном списке по мере необходимости.

Ответ 9

Piggybacking при наблюдении @SalvadorDali при поиске совпадений a в любом сегменте с размером ланга в 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

Второй подход: [удалено]

Ответ 10

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

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

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

Ответ 11

Следуя дальнейшему решению RocketRoy: Преобразуйте все свои списки до 64-разрядных чисел без знака. Для каждого списка вращайте эти 55 бит вокруг, чтобы найти наименьшее числовое значение.

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

(По сути, вы создаете значение идентификатора для своих списков, на которое не влияет ротация элементов списков) Это будет работать, если у вас есть произвольное число в списках.

Ответ 12

Это та же идея Сальвадора Дали, но не нужна конвертация строк. Позади есть одна и та же идея восстановления 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());
        }
    }

Надеюсь на эту помощь!

Ответ 13

Упрощение проблемы

  • Проблема состоит из списка упорядоченных элементов
  • Домен значения двоичный (0,1)
  • Мы можем уменьшить проблему путем сопоставления последовательных 1 в count
  • и последовательный 0 в отрицательный счетчик

Пример

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-gram N, чтобы снизить вероятность прохождения линейной проверки

Ответ 14

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

  • Подсчитайте количество нулей между ними (игнорируя обертку), чтобы получить три числа.
  • Поверните три числа, чтобы первое число было первым.
  • Первое число (a) должно быть между 18 и 52 (включительно). Перекодируйте его как между 0 и 34.
  • Второе число (b) должно быть между 0 и 26, но это не имеет большого значения.
  • Отбросьте третье число, так как оно просто 52 - (a + b) и не добавляет никакой информации

Каноническая форма представляет собой целое число b * 35 + a, которое находится между 0 и 936 (включительно), что довольно компактно (в общем случае есть 477 круговые уникальные списки).

Ответ 15

Я написал простое решение, которое сравнивает оба списка и просто увеличивает (и обертывает) индекс сравниваемого значения для каждой итерации.

Я не знаю 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;
    }
}

Ответ 16

Как уже упоминалось, как только вы найдете нормализованное вращение списка, вы можете сравнить их.

Вот какой рабочий код, который делает это, Основной метод - найти нормализованное вращение для каждого списка и сравнить:

  • Вычислить нормированный индекс вращения в каждом списке.
  • Перебирайте оба списка с их смещениями, сравнивая каждый элемент, возвращая, если они неверно совпадают.

Обратите внимание, что этот метод не зависит от чисел, вы можете передавать списки строк (любые значения, которые можно сравнить).

Вместо того, чтобы выполнять поиск по списку в списке, мы знаем, что мы хотим, чтобы список начинался с минимального значения, поэтому мы можем перебирать минимальные значения, ища, пока не найдем, какая из них имеет самые низкие последовательные значения, сохраняя это для дальнейших сравнений, пока у нас не будет лучшего.

Есть много возможностей для выхода из системы при расчете индекса, подробностей некоторых оптимизаций.

  • Пропустить поиск наилучшего минимального значения, когда только один.
  • Пропустить минимальные значения поиска, когда предыдущее также является минимальным значением (оно никогда не будет лучшим совпадением).
  • Пропустить поиск, когда все значения одинаковы.
  • Сбой раньше, когда списки имеют разные минимальные значения.
  • Используйте регулярное сравнение при совпадении смещений.
  • Отрегулируйте смещения, чтобы избежать обертывания значений индекса в одном из списков во время сравнения.

Обратите внимание, что в 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)

Смотрите: этот фрагмент для еще нескольких тестов/примеров.

Ответ 17

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

Я бы использовал многочленную хэш-функцию для вычисления хеста списка A и каждого циклического сдвига списка B. Если сдвиг списка B имеет тот же хеш, что и список A, я бы сравнил фактические элементы, чтобы увидеть, они равны.

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

Он работает следующим образом:

Пусть, скажем, B имеет N элементов, то хэш B с использованием простого P равен:

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, кроме первого. Поскольку умножение распределяется по сложениям, мы можем сразу размножить все компоненты, просто умножив весь хэш, а затем зафиксируем коэффициент для первого элемента.

Хеш левого сдвига В есть просто

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

Второй сдвиг влево:

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

и т.д.

ПРИМЕЧАНИЕ. Вся математика выше выполняется по модулю некоторого размера машинного слова, и вам нужно только вычислить P ^ N один раз.

Ответ 18

Чтобы приклеить к самому питоническому способу сделать это, используйте наборы!

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