Это должно работать.
#!/usr/bin/env python3
def get_cycles(d):
cycles = []
while d:
# Keep track of keys we've iterated through in this loop
tracked = []
# Take the next available key
k = next(iter(d))
while True:
tracked.append(k)
# This key (the value of the previous key) must have already
# been deleted, meaning it was part of a cycle or lead to
# repetitions of an already discovered cycle
try:
v = d[k]
except KeyError:
# Always delete keys leading up to the current key too, as this
# same loop will happen every time we land on one of these keys
# otherwise
for t in tracked:
if t in d:
del d[t]
break
# If the value of the current key was one of the keys leading up to
# the current key, we've discovered a cycle starting from the value
# of the current key and ending at the current key
if v in tracked:
cycle = tracked[tracked.index(v):]
cycles.append(cycle)
for c in cycle:
del d[c]
break
# If the value of the current key is a key in any of the
# previously discovered cycles, we want to delete this sequence,
# as we've already discovered the cycle it leads to
if any(v in cycle for cycle in cycles):
for t in tracked:
del d[t]
break
# Set the next key
k = v
return cycles
d1 = {1: 5, 2: 14, 3: 15, 4: 3, 5: 5, 6: 5, 7: 15, 8: 6, 9: 10, 10: 15, 11: 12, 12: 15, 13: 14, 14: 8, 15: 9}
d2 = {1:6, 3:1, 6:3}
print(get_cycles(d1))
print(get_cycles(d2))
Вывод:
[[5], [15, 9, 10]]
[[1, 6, 3]]
Алгоритм отслеживает каждый ключ, через который он проходит, повторяясь, устанавливая ключ, равный его собственному значению.
Если значение ключа уже находится в списке повторяющихся ключей, цикл обнаружен от этой позиции в списке повторяющихся ключей до конца.Отслеживаемые ключи, которые приводят к обнаружению этого цикла, должны быть затем удалены из словаря, так как этот цикл уже пройден.
Если значение ключа является ключом в одном из ранее обнаруженных циклов, тотекущая последовательность пройденных ключей должна быть удалена из словаря, так как цикл, к которому ведет эта последовательность, уже обнаружен.
И если значение ключа больше не является самим ключом (поднимая KeyError, когда значение ключапроверяется как ключ), цикл прерывается, потому что этот ключ уже должен был быть удален, то есть он был частью цикла или частью последовательности, ведущей к циклу.