Найти - Пользователи
Полная версия: Клонирование итератора
Начало » Python для экспертов » Клонирование итератора
1 2 3 4
Ed
Это классическая задача поиска циклов в последовательности: http://en.wikipedia.org/wiki/Cycle_detection
Там же есть код 2х решений на Питоне.
Isem
Там приводятся два алгоритма, оба которые используют два движущихся с разной скоростью указателя на список. А что если невозможно запустить два указателя, то есть итератор нельзя скопировать и инициировать вторую его копию для параллельного прохода?
Естественно, здесь не имеется в виду то, что нужно запоминать кучу значений (ибо памяти, для простоты, хватает только на один элемент).
Ed
Достаточно одного прохода и небольшого кэша. Как сделать с кэшем в один элемент не знаю и не уверен, что это вообще возможно.
Isem
Если невозможно сделать алгоритм с кэшем из одного элемента, то и нельзя сделать однопроходной (с одним указателем) алгоритм с любым конечным кэшем, ибо всегда можно подобрать такой длинный список, а точнее хвост списка, что он будет больше кэша и алгоритм работать не будет.

Но это возможно.
Мне пришел в голову такой алгоритм после того, как один бухгалтер стал возмущаться, что решение с двумя указателями - это не решение.
И так, мы бежим одним указателем по списку. Второй указатель мы создать не можем и памяти нам хватает только для того, чтобы запомнить один элемент списка. Что мы делаем? Мы эмулируем второй указатель. Это значит, что мы запоминаем каждый k-й элемент списка в ту же самую единственную ячейку, где k меняется как 2k. Вот пример:
1) Пусть N - это порядковый номер елемента списка, на который указывает наш указатель (N = 1,2,3,4,…..)
2) Запоминаем текущий N-й елемент списка в нашу единственную ячейку
3) Делаем N шагов указателем по списку, проверяя каждый раз елемент из списка с элементом из нашей ячейки.
4) Если элемент не найден, то, очевидно, номер текущего элемента списка есть N = 2*N (заменяем N на 2 умножить на N ) и переходим к пункту 2)
5) Если удалось найти совпадение элементов, то далее элементарно определяем длину цикла списка, продолжая бежать по списку. Конец.

Назовем этот алгоритм алгоритмом Ширкалина.
Ed
Isem
Если невозможно сделать алгоритм с кэшем из одного элемента, то и нельзя сделать однопроходной (с одним указателем) алгоритм с любым конечным кэшем, ибо всегда можно подобрать такой длинный список, а точнее хвост списка, что он будет больше кэша и алгоритм работать не будет.
Это спорное утверждение. Кэш нужен, чтобы хранить уже прочитанные элементы итератора. По идее он должен включать начало и первый цикл как минимум. Его можно уменьшать по мере прохождения, так что он может быть и меньше. Кэш динамический, естественно. В случае питона - просто список элементов, которые мы уже достали из итератора.


И так, мы бежим одним указателем по списку. Второй указатель мы создать не можем и памяти нам хватает только для того, чтобы запомнить один элемент списка. Что мы делаем? Мы эмулируем второй указатель. Это значит, что мы запоминаем каждый k-й элемент списка в ту же самую единственную ячейку, где k меняется как 2k. Вот пример:
1) Пусть N - это порядковый номер елемента списка, на который указывает наш указатель (N = 1,2,3,4,…..)
Оч хорошо, посмотрим на ваш алгоритм. Возьмем последовательность 2,1,5,1,7,8,1,6,3,1,6,3,1,6,3…
Ответом должен по идее быть цикл 1,6,3, если я правильно понял условие задачи.

2) Запоминаем текущий N-й елемент списка в нашу единственную ячейку
N = 1, запоминаем значение 2.

3) Делаем N шагов указателем по списку, проверяя каждый раз елемент из списка с элементом из нашей ячейки.
N шагов - это значит в данном случае 1? У нас же N=1? То есть смотрим следующий элемент, 1.

4) Если элемент не найден, то, очевидно, номер текущего элемента списка есть N = 2*N (заменяем N на 2 умножить на N ) и переходим к пункту 2)
Не найден 1!=2. N*=2, N становится равным 2.
Переходим к пункту 2: запоминаем в нашей ячейке значение 1.
Переходим к пункту 3: делаем 2 шага(N=2), На первом шаге несовпадение 1!=5, на втором шаге у нас совпадение 1=1

5) Если удалось найти совпадение элементов, то далее элементарно определяем длину цикла списка, продолжая бежатьпо списку. Конец.
Удалось. И где цикл? Мы до него еще не дошли.

Назовем этот алгоритм алгоритмом Ширкалина.
Договорились :)

Я предполагаю, что мог вас неправильно понять. Предлагаю показать код. Алгоритм по виду простой, так что думаю вас не затруднит его запрограммировать.
Isem
Ed
Это спорное утверждение. Кэш нужен, чтобы хранить уже прочитанные элементы итератора. По идее он должен включать начало и первый цикл как минимум. Его можно уменьшать по мере прохождения, так что он может быть и меньше. Кэш динамический, естественно. В случае питона - просто список элементов, которые мы уже достали из итератора.
Подчеркну, что всегда можно подобрать такой список, что его начало не поместится в кэш. Поэтому алгоритм, основанный на том, что запоминает уже прочитанные элементы, в общем случае работать не будет, а для тех “коротких” списков, когда доступной памяти и хватит, работать он будет чудовищно медленно, так как нужно будет проверять каждый новый элемент списка с каждым запомненным (иначе зачем запоминать все элементы списка?). Другими словами, если бы мы могли сформировать весь список в памяти (как список в питоне в вашем случае), то не было бы и задачи и не было бы тех двух алгоритмов, описанные на страничке в википедии по ссылке, что вы дали. По этому поводу есть прекрасная логическая задачка на математическую индукцию про почтового голубя, с помощью которого два отряда пытались договориться о времени, когда нужно начинать совместную атаку.
Ed
Оч хорошо, посмотрим на ваш алгоритм. Возьмем последовательность 2,1,5,1,7,8,1,6,3,1,6,3,1,6,3…
Ответом должен по идее быть цикл 1,6,3, если я правильно понял условие задачи.
Каждый элемент нашего односвязного списка уникален, иначе бы это был уже не список. Ваш пример не подходит для этого. Из вашей последовательности получается такой список: 2, 1, 5, 1, 5, 1, 5, 1,… то есть ответ таков: 1,5 - это цикл из 2-х элементов, 2 - это начало списка из одного элемента.

Далее ваши рассуждения уже не относятся к условию задачи.

Ed
Я предполагаю, что мог вас неправильно понять. Предлагаю показать код. Алгоритм по виду простой, так что думаю вас не затруднит его запрограммировать.
Алгоритм действительно простой, а простота - печать истины :). Привожу его вместе с примером (для 3-го питона).
Немного модифицировав программу, можно так же найти сам элемент, с какого начинается зацикливание списка.

def gen():
""" simple cycle list generator """
for i in [1,5,7,45,18,2]: # head of list
yield i
while True:
for i in [65,87,44,70,59,21,3,4]: # cycle of list
yield i

def getCycleLength( source ):
""" the function detects the cycle in singly connected list and returns its length """
mem = next(source) # remember 1st item
N = 1 # the next index of item to memorize
for i, item in enumerate( source, 1 ):
if item == mem:
return i - N//2 # return the length of cycle
if i == N:
mem = item # remember Nth item
N = N*2 + 1 # double the index for next item to memorize

print( "Cycle length = ", getCycleLength( iter(gen()) ) )
В качестве еще одного примера списка можно посмотреть вот такой генератор:
def gen1():
a = 23
while True:
a = (a*a + 4) % 856421
yield a
У списка, генерируемого этим генератором длина цикла = 28, а длина ‘головы’ списка выражается 4-х значным числом.
Ed
Isem
Каждый элемент нашего односвязного списка уникален, иначе бы это был уже не список.
С каких это пор список подразумевает уникальность элементов?
http://en.wikipedia.org/wiki/Linked_list#Singly-.2C_doubly-.2C_and_multiply-linked_lists

Кроме того, не забывайте, мы с вами на каком мы с вами форуме:
>>> type([1,1,1])
<type 'list'>
Далее ваши рассуждения уже не относятся к условию задачи.
Далее мне не интересно. Может для Вас это условие было само собой разумеющимся, но неплохо было бы его озвучить, не считаете?

Покажите мне слово ‘уникальный’ или даже ‘односвязный список’ в этом:
есть итератор, довольно сложный, который генерирует, например, длинные строки (1000 символов каждая, скажем).
Нужно определить длину цикла этого итератора. Но надо учитывать, что цикл может начинаться (и заканчиваться, соответственно) не с первого элемента, а совсем с другого. Сама же последовательность итератора может быть очень длинная.
Кроме того, я здесь давал ссылку на статью в википедии, где приводятся 2 алгоритма решение общей задачи. Ни о какой уникальности речь не шла. Вы вроде не возражали. Я так не играю. Это сильно смахивает на подгонку условия под неработающую программу. Всего хорошего!

PS: Кстати, я запрограммировал алгоритм Брента и он прекрасно находит последовательность несмотря на неуникальность элементов в начале. Попробуйте такое сделать без кэша, тогда это будет интересно.
Ed
Isem
В качестве еще одного примера списка можно посмотреть вот такой генератор:
def gen1():
a = 23
while True:
a = (a*a + 4) % 856421
yield a
У списка, генерируемого этим генератором длина цикла = 28, а длина ‘головы’ списка выражается 4-х значным числом.
Хороший пример, спасибо. Хоть в общем случае кэша может и не хватить, но в этом конкретном все прекрасно работает:
time python brent.py
28 2705

real 0m0.106s
user 0m0.068s
sys 0m0.014s
Isem
Ed
С каких это пор список подразумевает уникальность элементов?
http://en.wikipedia.org/wiki/Linked_lis … ther_lists

Кроме того, не забывайте, мы с вами на каком мы с вами форуме:
Код:

>>> type()
<type ‘list’>
Список подразумевает уникальность элементов как отдельных объектов, в то же самое время сами объекты могут быть и одинаковые. На питоне, то что я имею в виду, это можно выразить так:
a=3.0
b=3.0
print( a == b ) # выдаст True
print( a is b ) # выдаст False
Список - это не тип переменной на языке Питон (хотя бы потому, что понятие списка возникло задолго до того, как появился Питон). Я виноват, что сделал акцент на том, что когда я говорил о списке, я говорил не о типе list в Питоне. Для меня это казалось естественным, так что признаю свою вину.
Ed
Кроме того, я здесь давал ссылку на статью в википедии, где приводятся 2 алгоритма решение общей задачи. Ни о какой уникальности речь не шла. Вы вроде не возражали. Я так не играю. Это сильно смахивает на подгонку условия под неработающую программу. Всего хорошего!

PS: Кстати, я запрограммировал алгоритм Брента и он прекрасно находит последовательность несмотря на неуникальность элементов в начале. Попробуйте такое сделать без кэша, тогда это будет интересно.
В этой статье (Cycle Detection) первые три предложения в переводе можно озвучить так:

“В математике поиск цикла в последовательности значений итеративной функции называется алгоритмом определения цикла.
Для любой функции &#402;, которая отображает конечное множество S на само себя, и любого начального значения x0 в S, последовательность значений итеративной функции должна в конечном счете встретить одно и то же значение дважды: должны быть числа i &#8800; j такие, что xi = xj. Как только это произойдет, последовательность должна продолжаться повторяя цикл значений от xi до xj-1. ”

Четкое определение, на котором основан и алгоритм Брента.

Ни о какой подгонке результатов у меня не было и мысли, иначе бы я сюда никогда ничего не писал. Я пришел на этот форум для того, чтобы найти единомышленников по программированию, в том числе и по программированию на Питоне (хотя последнее первично).
Интересно было с вами пообщаться.
Isem
Ed
Хороший пример, спасибо. Хоть в общем случае кэша может и не хватить, но в этом конкретном все прекрасно работает:
Код:

time python brent.py
28 2705

real 0m0.106s
user 0m0.068s
sys 0m0.014s
Точная длина головы этого списка 2677
This is a "lo-fi" version of our main content. To view the full version with more information, formatting and images, please click here.
Powered by DjangoBB