Например, у меня есть списки:
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 Гига!
Есть ли хороший способ сделать это с меньшим количеством вычислений? Или какие-либо типы данных, которые поддерживают циклический ?
Ответы:
Во-первых, это может быть сделано с
O(n)
точки зрения длины списка. Вы можете заметить, что если вы дублируете свой список 2 раза ([1, 2, 3]
) будет[1, 2, 3, 1, 2, 3]
то ваш новый список определенно будет содержать все возможные циклические списки.Так что все, что вам нужно, это проверить, находится ли список, который вы ищете, в 2 раза вашего стартового списка. В Python вы можете достичь этого следующим образом (при условии, что длины одинаковы).
Некоторые пояснения по поводу моего oneliner:
list * 2
объединит список с самим собой,map(str, [1, 2])
преобразует все числа в строку и' '.join()
преобразует массив['1', '2', '111']
в строку'1 2 111'
.Как отмечают некоторые люди в комментариях, oneliner потенциально может дать некоторые ложные срабатывания, чтобы охватить все возможные крайние случаи:
PS1, говоря о сложности времени, стоит отметить, что
O(n)
это будет достигнуто, если подстрока будет найдена воO(n)
времени. Это не всегда так и зависит от реализации на вашем языке ( хотя потенциально это может быть сделано, например, в линейном времени KMP).PS2 для людей, которые боятся эксплуатации струн и из-за этого считают, что ответ не хороший. Что важно, это сложность и скорость. Этот алгоритм потенциально работает во
O(n)
времени иO(n)
пространстве, что делает его намного лучше, чем что-либо вO(n^2)
области. Чтобы убедиться в этом самостоятельно, вы можете запустить небольшой тест (создающий случайный список, выскакивающий первый элемент и добавляющий его в конец, создавая таким образом циклический список. Вы можете делать свои собственные манипуляции)0,3 секунды на моей машине. Не очень долго Теперь попробуйте сравнить это с
O(n^2)
решениями. Пока он сравнивается, вы можете путешествовать из США в Австралию (скорее всего, на круизном судне)источник
Не обладающий достаточными знаниями в Python, чтобы ответить на этот вопрос на требуемом вами языке, но в C / C ++, учитывая параметры вашего вопроса, я бы преобразовал нули и единицы в биты и поместил их в младшие значащие биты uint64_t. Это позволит вам сравнить все 55 бит одним махом - 1 такт.
Чрезвычайно быстро, и все это поместится в кэш-память на кристалле (209 880 байт). Аппаратная поддержка одновременного смещения всех 55 элементов списка доступна только в регистрах ЦП. То же самое касается сравнения всех 55 членов одновременно. Это позволяет отображать проблему «один на один» с программным решением. (и с использованием 256-битных регистров SIMD / SSE, при необходимости до 256 членов). В результате код сразу становится очевидным для читателя.
Возможно, вам удастся реализовать это в Python, я просто недостаточно хорошо это знаю, чтобы понять, возможно ли это или какова производительность.
Спать на нем несколько вещей стало очевидным, и все к лучшему.
1.) Закручивать циклически связанный список с помощью битов так легко, что очень умный трюк Дали не нужен. Внутри 64-битного регистра стандартное сдвиг битов будет выполнять вращение очень просто, и в попытке сделать все это более дружественным к Python, используя арифметику вместо битовых операций.
2.) Сдвиг бит может быть легко выполнен с помощью деления на 2.
3.) Проверка конца списка на 0 или 1 может быть легко выполнена по модулю 2.
4.) «Перемещение» 0 в начало списка из хвоста можно сделать, разделив на 2. Это потому, что если бы ноль был фактически перемещен, то это сделало бы 55-й бит ложным, что уже происходит, абсолютно ничего не делая.
5.) «Перемещение» 1 к началу списка из хвоста можно сделать, разделив на 2 и добавив 18 014 398 509 481 984 - это значение, созданное путем пометки 55-го бита истинным, а все остальные ложными.
6.) Если сравнение якоря и составного uint64_t имеет значение ИСТИНА после любого данного вращения, прервите и верните ИСТИНА.
Я бы преобразовал весь массив списков в массив uint64_ts сразу, чтобы избежать необходимости повторного преобразования.
Потратив несколько часов, пытаясь оптимизировать код, изучая язык ассемблера, я смог сэкономить 20% времени выполнения. Я должен добавить, что компилятор O / S и MSVC также обновился вчера. По любой причине / с качество кода, созданного компилятором C, значительно улучшилось после обновления (15.11.2014). Время выполнения теперь составляет ~ 70 часов, 17 наносекунд для составления и сравнения якорного кольца со всеми 55 витками испытательного кольца, и NxN всех колец против всех остальных выполняется за 12,5 секунд. .
Этот код настолько сжат, что все, кроме 4-х регистров, бездействуют в 99% случаев. Язык ассемблера соответствует коду C почти строка за строкой. Очень легко читать и понимать. Отличный сборочный проект, если кто-то учил себя этому.
Аппаратное обеспечение - Hazwell i7, MSVC 64-bit, полная оптимизация.
источник
Читая между строк, кажется, что вы пытаетесь перечислить одного представителя каждого класса циклической эквивалентности строк с 3 единицами и 52 нулями. Давайте переключимся с плотного представления на разреженное (набор из трех чисел в
range(55)
). В этом представлении круговое смещениеs
byk
определяется пониманием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))
чтобы оригинал был минимальным. Вот некоторый код перечисления.источник
Повторите первый массив, затем используйте алгоритм Z (O (n) time), чтобы найти второй массив внутри первого.
(Примечание: вам не нужно физически копировать первый массив. Вы можете просто обернуться во время сопоставления.)
Хорошая особенность алгоритма Z состоит в том, что он очень прост по сравнению с KMP, BM и т. Д.
Однако, если вы чувствуете себя амбициозно, вы можете выполнить сопоставление строк в линейном времени и в постоянном пространстве -
strstr
например, так. Реализация этого была бы более болезненной, все же.источник
Следуя очень умному решению Сальвадора Дали, лучший способ справиться с ним - убедиться, что все элементы имеют одинаковую длину, а также оба СПИСКА одинаковой длины.
Понятия не имею, если это быстрее или медленнее, чем рекомендованное решение регулярного выражения AshwiniChaudhary в ответе Сальвадора Дали, который гласит:
источник
str.format
n
время для форматирования результирующей строки. Я полагаю .... :)Учитывая, что вам нужно сделать так много сравнений, может быть, стоит провести первоначальный проход по спискам, чтобы преобразовать их в некую каноническую форму, которую можно легко сравнить?
Вы пытаетесь получить набор циркулярно уникальных списков? Если это так, вы можете бросить их в набор после преобразования в кортежи.
Извиняюсь перед Дэвидом Эйзенстатом за то, что не заметил его v.
источник
Вы можете свернуть один список следующим образом:
источник
Сначала преобразуйте каждый из элементов списка (при необходимости, в копию) в эту повернутую версию, которая является лексически наибольшей.
Затем сортируйте полученный список списков (сохраняя индекс в исходной позиции списка) и объединяйте отсортированный список, помечая все дубликаты в исходном списке по мере необходимости.
источник
Если обратить внимание на наблюдение @ SalvadorDali о поиске совпадений a в любом срезе длиной a в b + b, вот решение, использующее только операции со списком.
2-й подход: [удалено]
источник
rollmatch([1, 0, 1, 1], [0, 1, 1, 1])
.Не полный, автономный ответ, но на тему оптимизации путем сокращения сравнений я тоже думал о нормализованных представлениях.
А именно, если ваш входной алфавит равен {0, 1}, вы можете значительно сократить количество разрешенных перестановок. Поверните первый список в (псевдо-) нормализованную форму (учитывая распределение в вашем вопросе, я бы выбрал такой, где один из 1 бита находится слева направо, а один из 0 бит справа). Теперь перед каждым сравнением последовательно поворачивайте другой список через возможные позиции с тем же шаблоном выравнивания.
Например, если у вас есть в общей сложности четыре 1 бита, может быть не более 4 перестановок с этим выравниванием, и если у вас есть кластеры смежных 1 бит, каждый дополнительный бит в таком кластере уменьшает количество позиций.
Это обобщает для больших алфавитов и различных шаблонов выравнивания; главная задача - найти хорошую нормализацию только с несколькими возможными представлениями. В идеале это была бы правильная нормализация с единственным уникальным представлением, но, учитывая проблему, я не думаю, что это возможно.
источник
Основываясь на ответе RocketRoy: конвертируйте все ваши списки заранее в 64-разрядные числа без знака. Для каждого списка поверните эти 55 битов вокруг, чтобы найти наименьшее числовое значение.
Теперь у вас осталось одно беззнаковое 64-битное значение для каждого списка, которое вы можете сравнить напрямую со значением других списков. Функция is_circular_identical () больше не требуется.
(По сути, вы создаете значение идентификатора для своих списков, на которое не влияет ротация элементов списков). Это даже сработало бы, если бы в ваших списках было произвольное число единиц.
источник
Это та же самая идея Сальвадора Дали, но она не нуждается в преобразовании строк. Позади та же самая идея восстановления KMP, чтобы избежать невозможной проверки смены. Их называют только KMPModified (list1, list2 + list2).
Надеюсь, это поможет!
источник
Упрощение проблемы
(0,1)
1
s в число0
с в отрицательный счетпример
Процесс проверки
Захват
lookup
иlook-ahead
Псевдо-код
функции
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 параметр, чтобы снизить вероятность прохождения линейной проверки
источник
Эффективная, быстро вычисляемая «каноническая форма» для рассматриваемых списков может быть получена как:
a
) должно быть между18
и52
(включительно). Перекодируйте его как между0
и34
.b
) должно быть между0
и26
, но это не имеет большого значения.52 - (a + b)
и не добавляет информацииКаноническая форма - это целое число
b * 35 + a
, которое находится между0
и936
(включительно), которое является довольно компактным (всего имеется477
кругово-уникальные списки).источник
Я написал простое решение, которое сравнивает оба списка и просто увеличивает (и оборачивает) индекс сравниваемого значения для каждой итерации.
Я плохо знаю Python, поэтому я написал его на Java, но он действительно прост, поэтому его легко адаптировать к любому другому языку.
По этому вы также можете сравнить списки других типов.
источник
Как уже упоминали другие, когда вы найдете нормализованное вращение списка, вы можете сравнить их.
Вот некоторый рабочий код, который делает это. Основной метод - найти нормализованное вращение для каждого списка и сравнить:
Обратите внимание, что этот метод не зависит от чисел, вы можете передать в списках строк (любые значения, которые можно сравнить).
Вместо того, чтобы выполнять поиск по списку, мы знаем, что мы хотим, чтобы список начинался с минимального значения - поэтому мы можем циклически проходить по минимальным значениям, ища, пока не найдем, какое из них имеет наименьшие последовательные значения, сохраняя его для дальнейших сравнений. пока у нас нет лучшего
При расчете индекса существует множество возможностей для досрочного выхода, подробности некоторых оптимизаций.
Обратите внимание, что в Python поиск по списку может быть более быстрым, однако мне было интересно найти эффективный алгоритм, который можно было бы использовать и в других языках. Кроме того, есть некоторое преимущество в том, чтобы избегать создания новых списков.
Смотрите: этот фрагмент для некоторых других тестов / примеров.
источник
Вы можете легко проверить, равен ли список A циклическому сдвигу списка B в ожидаемое время O (N).
Я бы использовал полиномиальную хеш-функцию для вычисления хеша списка A и каждого циклического сдвига списка B. Если у сдвига списка B такой же хеш, что и у списка A, я бы сравнил фактические элементы, чтобы увидеть, равны ли они ,
Причина, по которой это быстро, заключается в том, что с полиномиальными хеш-функциями (которые чрезвычайно распространены!) Вы можете вычислять хеш каждого циклического сдвига от предыдущего за постоянное время, поэтому вы можете вычислить хеш-значения для всех циклических сдвигов в O ( Н) время.
Это работает так:
Скажем, B имеет N элементов, тогда хэш B, использующий простое P:
Это оптимизированный способ оценки полинома в P, который эквивалентен:
Обратите внимание, как каждый B [i] умножается на P ^ (N-1-i). Если мы сместим B влево на 1, то каждый каждый B [i] будет умножен на дополнительный P, кроме первого. Поскольку умножение распределяется по сложению, мы можем умножить все компоненты одновременно, просто умножив весь хэш, а затем зафиксировать коэффициент для первого элемента.
Хеш левого сдвига B просто
Вторая левая смена:
и так далее...
ПРИМЕЧАНИЕ: вся приведенная выше математика выполняется по модулю некоторого машинного слова, и вам нужно только вычислить P ^ N один раз.
источник
Чтобы склеить самый питонский способ сделать это, используйте наборы!
источник