Python 如何确定一个容器是否是无限递归的,并找到其最小的唯一容器?
我正在阅读,并决定将其作为一个Python练习来采用——这是一个小函数,我偶尔会重写它,而不参考原始函数,只是为了练习。我第一次尝试这种方法时,我得到了如下结果:Python 如何确定一个容器是否是无限递归的,并找到其最小的唯一容器?,python,python-3.x,recursion,Python,Python 3.x,Recursion,我正在阅读,并决定将其作为一个Python练习来采用——这是一个小函数,我偶尔会重写它,而不参考原始函数,只是为了练习。我第一次尝试这种方法时,我得到了如下结果: def flat(iterable): try: iter(iterable) except TypeError: yield iterable else: for item in iterable: yield from flatten(
def flat(iterable):
try:
iter(iterable)
except TypeError:
yield iterable
else:
for item in iterable:
yield from flatten(item)
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)
这对于包含数字的嵌套list
s这样的基本结构很好,但字符串会使其崩溃,因为字符串的第一个元素是单个字符串,其中第一个元素是字符串本身,而第一个元素又是字符串本身,依此类推。检查上面链接的问题,我意识到这解释了字符串的检查。这给了我以下几点:
def flat(iterable):
try:
iter(iterable)
except TypeError:
yield iterable
else:
for item in iterable:
yield from flatten(item)
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)
现在它也适用于字符串。然而,我随后回忆起,列表可以包含对自身的引用
>>> lst = []
>>> lst.append(lst)
>>> lst
[[...]]
>>> lst[0][0][0][0] is lst
True
因此,字符串不是唯一可能导致此类问题的类型。在这一点上,我开始寻找一种不用显式类型检查就可以防止这个问题的方法
随后出现了以下flatter.py
flattish()
是一个只检查字符串的版本flatte_notype()
检查对象的第一个项的第一个项是否等于自身,以确定递归<代码>展平()
执行此操作,然后检查对象或其第一项的第一项是否为另一项类型的实例。Fake
类基本上只是为序列定义了一个包装器。测试每个函数的行上的注释以的形式描述结果,应该是“期望的结果”[>“不期望的实际结果”]
。如您所见,每种方法都会以不同的方式失败,例如,将伪
包装在字符串上,将伪
包装在整数、单个字符串和多个字符串的列表上
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
和flant()
尝试了以下操作:
正如您所看到的,它的失败与1('a',)bc类似,也有其特殊的方式
我读到这篇文章时认为,也许函数可以跟踪它所看到的每个对象,但这也行不通,因为我们的lst
包含一个具有匹配标识和相等的对象,字符串包含可能只有匹配相等的对象,而相等是不够的,因为有可能出现类似展平的情况([1,2],[1,2])
是否有可靠的方法(即不只是检查已知的类型,不要求递归容器及其容器都是相同的类型,等等)检查一个容器是否包含具有潜在无限递归的iterable对象,并可靠地确定最小的唯一容器?如果有,请说明如何实现,为什么可靠,以及它如何处理各种递归情况。如果没有,请说明为什么这在逻辑上是不可能的。我不认为有问题确定任意iterable是否为无穷大的可靠方法。我们能做的最好的方法是从这样一个iterable中无限地产生原语,而不耗尽堆栈,例如:
from collections import deque
def flat(iterable):
d = deque([iterable])
def _primitive(x):
return type(x) in (int, float, bool, str, unicode)
def _next():
x = d.popleft()
if _primitive(x):
return True, x
d.extend(x)
return False, None
while d:
ok, x = _next()
if ok:
yield x
xs = [1,[2], 'abc']
xs.insert(0, xs)
for p in flat(xs):
print p
“原始”的上述定义是原始的,但这肯定可以改进。这样的定义如何:
def flat(obj, used=[], old=None):
#This is to get inf. recurrences
if obj==old:
if obj not in used:
used.append(obj)
yield obj
raise StopIteration
try:
#Get strings
if isinstance(obj, str):
raise TypeError
#Try to iterate the obj
for item in obj:
yield from flat(item, used, obj)
except TypeError:
#Get non-iterable items
if obj not in used:
used.append(obj)
yield obj
经过有限数量的(递归)步骤后,列表最多包含一个iterable元素(因为我们必须在有限的多个步骤中生成它)。这就是我们用obj==old
测试的内容,其中obj
在old
元素中
列表used
跟踪所有元素,因为我们只需要每个元素一次。我们可以删除它,但我们会得到一个丑陋的(更重要的是定义不明确的)行为,元素的频率是多少。
缺点是我们将整个列表存储在使用的列表的末尾
用一些列表测试这一点似乎有效:
>> lst = [1]
>> lst.append(lst)
>> print('\nList1: ', lst)
>> print([x for x in flat(lst)])
List1: [1, [...]]
Elements: [1, [1, [...]]]
#We'd need to reset the iterator here!
>> lst2 = []
>> lst2.append(lst2)
>> lst2.append((1,'ab'))
>> lst2.append(lst)
>> lst2.append(3)
>> print('\nList2: ', lst2)
>> print([x for x in flat(lst2)])
List2: [[...], (1, 'ab'), [1, [...]], 3]
Elements: [[[...], (1, 'ab'), [1, [...]], 3], 1, 'ab', [1, [...]], 3]
注意:无限列表[…],(1,'ab'),[1,[…],[3]
和[1,[…]
被视为元素,因为这些元素实际上包含它们自己,但如果不希望这样做,可以注释掉上面代码中的第一个yield
。测试代码中存在一个与您试图解决的递归容器问题无关的问题。问题是您的伪
类是一个迭代器nd只能使用一次。在您对其所有值进行迭代后,当您再次尝试对其进行迭代时,它将始终引发StopIteration
因此,如果您在同一个false
实例上执行多个操作,那么在第一个操作消耗了迭代器之后,您不应该期望得到任何空输出。如果您在每个操作之前重新创建迭代器,您就不会有这个问题(实际上您可以尝试解决递归问题)
关于这个问题。避免无限递归的一种方法是维护一个包含当前嵌套对象的堆栈。如果您看到的下一个值已经在堆栈的某个位置上,您知道它是递归的,可以跳过它。下面是使用列表作为堆栈的实现:
def flatten(obj, stack=None):
if stack is None:
stack = []
if obj in stack:
yield obj
try:
it = iter(obj)
except TypeError:
yield obj
else:
stack.append(obj)
for item in it:
yield from flatten(item, stack)
stack.pop()
请注意,这仍然可以多次从同一容器中生成值,只要它不嵌套在自身中(例如,对于x=[1,2];y=[x,3,x];print(*flatte(y))
将打印12312
)
它也会递归到字符串中,但它只会在一个级别执行,所以flatten(“foo”)
将依次产生字母'f'
、'o'
和'o'
。如果您想避免这种情况,您可能需要函数具有类型意识,因为从迭代协议的角度来看,字符串与其字母的可编辑容器没有任何区别。它只是一个递归的单个字符串包含它们自己。您所问的场景定义非常松散。正如您问题中所定义的,逻辑上不可能“检查”
>>> list(flatten(l, keepcls=(dict, str)))
[1, 2, 5, 6, {'a': 1, 'b': 2}, 7, 'string', [1, 2, [5, 6, {'a': 1, 'b': 2}, 7, 'string'], [...]]]
>>> list(flatten([[1,2],[1,[1,2]],[1,2]]))
[1, 2, 1, 1, 2, 1, 2]