Я читал Сгладить (нерегулярный) список списков и решил принять его как упражнение на Python - небольшую функцию, которую я иногда переписываю, не ссылаясь на оригинал, только для практики. В первый раз, когда я попробовал это, у меня было что-то вроде следующего:
def flat(iterable):
try:
iter(iterable)
except TypeError:
yield iterable
else:
for item in iterable:
yield from flatten(item)
Это отлично работает для базовых структур, таких как вложенные list, содержащие числа, но строки разбивают его, потому что первый элемент строки является односимвольной строкой, первый элемент которой сам по себе, первый элемент которого сам по себе снова и так далее. Проверяя вопрос, связанный выше, я понял, что это объясняет проверку строк. Это дало мне следующее:
def flatter(iterable):
try:
iter(iterable)
if isinstance(iterable, str):
raise TypeError
except TypeError:
yield iterable
else:
for item in iterable:
yield from flatten(item)
Теперь он работает и для строк. Тем не менее, я тогда вспомнил, что a list может содержать ссылки на себя.
>>> lst = []
>>> lst.append(lst)
>>> lst
[[...]]
>>> lst[0][0][0][0] is lst
True
Таким образом, строка не является единственным типом, который может вызвать такую проблему. На этом этапе я начал искать способ защитить эту проблему без явной проверки типов.
Следующее flattener.py последовало. flattish() - это версия, которая просто проверяет строки. flatten_notype() проверяет, соответствует ли первый элемент первого элемента объекта самому себе для определения рекурсии. flatten() делает это, а затем проверяет, является ли объект или первый элемент первого элемента экземпляром другого типа. Класс Fake в основном просто определяет оболочку для последовательностей. Комментарии по строкам, которые проверяют каждую функцию, описывают результаты в форме should be `desired_result` [> `undesired_actual_result`]. Как вы можете видеть, каждый из них терпит неудачу различными способами Fake, обернутыми вокруг строки, Fake, обернутыми вокруг целых чисел list, односимвольных строк и многосимвольных строк.
def flattish(*i):
for item in i:
try: iter(item)
except: yield item
else:
if isinstance(item, str): yield item
else: yield from flattish(*item)
class Fake:
def __init__(self, l):
self.l = l
self.index = 0
def __iter__(self):
return self
def __next__(self):
if self.index >= len(self.l):
raise StopIteration
else:
self.index +=1
return self.l[self.index-1]
def __str__(self):
return str(self.l)
def flatten_notype(*i):
for item in i:
try:
n = next(iter(item))
try:
n2 = next(iter(n))
recur = n == n2
except TypeError:
yield from flatten(*item)
else:
if recur:
yield item
else:
yield from flatten(*item)
except TypeError:
yield item
def flatten(*i):
for item in i:
try:
n = next(iter(item))
try:
n2 = next(iter(n))
recur = n == n2
except TypeError:
yield from flatten(*item)
else:
if recur:
yield item if isinstance(n2, type(item)) or isinstance(item, type(n2)) else n2
else:
yield from flatten(*item)
except TypeError:
yield item
f = Fake('abc')
print(*flattish(f)) # should be `abc`
print(*flattish((f,))) # should be `abc` > ``
print(*flattish(1, ('a',), ('bc',))) # should be `1 a bc`
f = Fake([1, 2, 3])
print(*flattish(f)) # should be `1 2 3`
print(*flattish((f,))) # should be `1 2 3` > ``
print(*flattish(1, ('a',), ('bc',))) # should be `1 a bc`
f = Fake('abc')
print(*flatten_notype(f)) # should be `abc`
print(*flatten_notype((f,))) # should be `abc` > `c`
print(*flatten_notype(1, ('a',), ('bc',))) # should be `1 a bc` > `1 ('a',) bc`
f = Fake([1, 2, 3])
print(*flatten_notype(f)) # should be `1 2 3` > `2 3`
print(*flatten_notype((f,))) # should be `1 2 3` > ``
print(*flatten_notype(1, ('a',), ('bc',))) # should be `1 a bc` > `1 ('a',) bc`
f = Fake('abc')
print(*flatten(f)) # should be `abc` > `a`
print(*flatten((f,))) # should be `abc` > `c`
print(*flatten(1, ('a',), ('bc',))) # should be `1 a bc`
f = Fake([1, 2, 3])
print(*flatten(f)) # should be `1 2 3` > `2 3`
print(*flatten((f,))) # should be `1 2 3` > ``
print(*flatten(1, ('a',), ('bc',))) # should be `1 a bc`
Я также пробовал следующее с рекурсивным lst, определенным выше, и flatten():
>>> print(*flatten(lst))
[[...]]
>>> lst.append(0)
>>> print(*flatten(lst))
[[...], 0]
>>> print(*list(flatten(lst))[0])
[[...], 0] 0
Как вы можете видеть, он работает аналогично 1 ('a',) bc, а также по-своему.
Я читаю как функция python может получить доступ к своим собственным атрибутам? думает, что, возможно, функция может отслеживать каждый объект, который он видел, но это не сработало бы потому что наш lst содержит объект с совпадающим идентификатором и равенством, строки содержат объекты, которые могут иметь только совпадающее равенство, а равенства недостаточно из-за возможности чего-то вроде flatten([1, 2], [1, 2]).
Есть ли какой-либо надежный способ (т.е. не просто проверять известные типы, не требует, чтобы рекурсивный контейнер и его контейнеры были одного типа и т.д.), чтобы проверить, содержит ли контейнер итерируемые объекты с потенциальным бесконечным рекурсии и надежно определить самый маленький уникальный контейнер? Если есть, объясните, как это можно сделать, почему оно надежное и как оно обрабатывает различные рекурсивные обстоятельства. Если нет, объясните, почему это логически невозможно.