当我尝试在迭代其元素时更新集合时,其行为应是什么?
我在各种情况下进行了尝试,并且它不会迭代开始迭代之后添加的元素以及迭代期间删除的元素。如果我在迭代过程中删除并放回任何元素,则会考虑该元素。确切的行为是什么?它如何工作?
这将打印字符串的所有排列:
def permutations(s):
ans = []
def helper(created, remaining):
if len(created) == len(s):
ans.append(''.join(created))
return
for ch in remaining:
remaining.remove(ch)
created.append(ch)
helper(created, remaining)
remaining.add(ch)
created.pop()
helper([], set(s))
return ans
这里的行为是不可预测的,有时e
会打印出来,而有时却不会:
ab = set(['b','c','d'])
x = True
for ch in ab:
if x:
ab.remove('c')
ab.add('e')
x = False
print(ch)
在这里,我总是'c'
只看到一次。即使第一个字符是'c'
:
ab = set(['b','c','d'])
x = True
for ch in ab:
if x:
ab.remove('c')
ab.add('c')
x = False
print(ch)
还有一种实现上述功能相同目的的替代方法:
def permwdups(s):
ans = []
def helper(created, remaining):
if len(created) == len(s):
ans.append(''.join(created))
return
for ch in remaining:
if (remaining[ch]<=0):
continue
remaining[ch] -=1
created.append(ch)
helper(created, remaining)
remaining[ch] +=1
created.pop()
counts = {}
for i in range(len(s)):
if s[i] not in counts:
counts[s[i]] = 1
else:
counts[s[i]]+= 1
helper([], counts)
print(len(set(ans)))
return ans
实际上非常简单,它set
在CPython中以hash
-item
表的形式实现:
hash | item
-----------------
- | -
-----------------
- | -
...
CPython使用开放式寻址,因此并非表中的所有行都被填充,并且在发生冲突的情况下,它会根据项目的(截断的)哈希值来确定元素的位置,并使用“伪随机”位置确定。在这个答案中,我将忽略截断的哈希冲突。
我还将忽略哈希截断的细节,而只使用整数,所有整数(有些例外)将其哈希映射到实际值:
>>> hash(1)
1
>>> hash(2)
2
>>> hash(20)
20
因此,当您创建set
具有值1、2和3的a时,将具有(大约)下表:
hash | item
-----------------
- | -
-----------------
1 | 1
-----------------
2 | 2
-----------------
3 | 3
...
从表的顶部到表的末尾迭代该集合,并且忽略空的“行”。因此,如果您不修改就迭代该集合,则会得到数字1、2和3:
>>> for item in {1, 2, 3}: print(item)
1
2
3
基本上,迭代器从第0行开始,然后看到该行为空,然后转到包含该项的第1行1
。该项目由迭代器返回。下一次迭代将在第2行中,并在其中返回值2
,然后转到第3行并返回将3
其存储在其中的值。在下面的迭代中,迭代器位于第4行,该行为空,因此将其移至第5行,该行也为空,然后到达第6行,直到到达表的末尾并引发StopIteration
异常,该信号表明迭代器完成。
但是,如果您要在迭代时更改集合,则会记住该迭代器的当前位置(行)。这意味着,如果您在前一行中添加项目,则迭代器将不会返回该项目,如果在后一行中添加该项目,则将返回该项目(至少,如果在迭代器出现之前没有再次将其删除)。
假设您总是删除当前项,item + 1
并向集合中添加一个整数。像这样:
s = {1}
for item in s:
print(item)
s.discard(item)
s.add(item+1)
迭代之前的集合如下所示:
hash | item
-----------------
- | -
-----------------
1 | 1
-----------------
- | -
...
迭代器将从第0行开始,发现它为空,然后转到第1行,其中包含该值1
,然后将其返回并打印。如果箭头指示迭代器的位置,则在第一次迭代中将如下所示:
hash | item
-----------------
- | -
-----------------
1 | 1 <----------
-----------------
- | -
然后将1
删除并添加2:
hash | item
-----------------
- | -
-----------------
- | - <----------
-----------------
2 | 2
因此,在下一次迭代中,迭代器找到该值2
并将其返回。然后添加两个,并添加一个3:
hash | item
-----------------
- | -
-----------------
- | -
-----------------
- | - <----------
-----------------
3 | 3
等等。
直到到达7
。到那时,发生了一些有趣的事情:截断的hash8
表示8
将把放在第0行,但是第0行已经被迭代,因此它将以停止7
。实际值可能取决于Python版本和集合的添加/删除历史记录,例如,仅更改set.add
和set.discard
操作的顺序将得到不同的结果(由于调整了集合的大小,最多可获取15个结果)。
出于相同的原因,迭代器仅在每次迭代中1
都添加时才会显示item - 1
,因为0
(由于哈希0)将其添加到第一行:
s = {1}
for item in s:
print(item)
s.discard(item)
s.add(item-1)
hash | item
-----------------
- | -
-----------------
1 | 1 <----------
-----------------
- | -
hash | item
-----------------
0 | 0
-----------------
- | -
-----------------
- | - <----------
用简单的GIF可视化:
请注意,这些示例非常简单,如果set
在迭代过程中调整大小,它将根据“新的”截断的哈希值重新分配存储的项目,并且还将删除从集合中删除项目时留下的虚拟对象。在那种情况下,您仍然可以(大致)预测会发生什么,但是它将变得更加复杂。
另外一个非常重要的事实是,Python(自Python
3.4起)对每个解释器随机化字符串的哈希值。这意味着每个Python会话都会为字符串产生不同的哈希值。因此,如果在迭代时添加/删除字符串,则行为也是随机的。
假设您有以下脚本:
s = {'a'}
for item in s:
print(item)
s.discard(item)
s.add(item*2)
并且您从命令行多次运行它,结果将有所不同。
例如我的第一次跑步:
'a'
'aa'
我的第二/第三/第四次跑步:
'a'
我的第五次跑步:
'a'
'aa'
这是因为命令行中的脚本始终会启动新的解释器会话。如果您在同一会话中多次运行脚本,结果将不会有所不同。例如:
>>> def fun():
... s = {'a'}
... for item in s:
... print(item)
... s.discard(item)
... s.add(item*2)
>>> for i in range(10):
... fun()
产生:
a
aa
a
aa
a
aa
a
aa
a
aa
a
aa
a
aa
a
aa
a
aa
a
aa
但是它也可以给出10倍a
或10倍a
,aa
并且aaaa
,…
总结一下:
如果将项目放置在尚未迭代的位置,则将显示在迭代过程中添加到集合的值。位置取决于项目的截断的哈希值和碰撞策略。
哈希的截断取决于集合的大小,而该大小取决于集合的添加/删除历史记录。
使用字符串时,无法预测位置,因为在最近的Python版本中,它们的哈希值是基于每个会话随机分配的。
最重要的是: 在遍历set / list / dict / …时,避免对其进行修改 。它几乎总是会导致问题,即使不是这样,也会使任何人都感到困惑!尽管在极少数情况下,在列表上进行迭代时将元素添加到列表是有意义的。那将需要非常具体的注释,否则看起来像是一个错误!特别是对于集合和字典,您将依赖可能随时更改的实现细节!
万一您好奇,我使用Jupyter Notebook中的Cython内省检查了该集合的内部(有些脆弱,可能仅在Python
3.6上工作,并且绝对不能在生产代码中使用):
%load_ext Cython
%%cython
from cpython cimport PyObject, PyTypeObject
cimport cython
cdef extern from "Python.h":
ctypedef Py_ssize_t Py_hash_t
struct setentry:
PyObject *key
Py_hash_t hash
ctypedef struct PySetObject:
Py_ssize_t ob_refcnt
PyTypeObject *ob_type
Py_ssize_t fill
Py_ssize_t used
Py_ssize_t mask
setentry *table
Py_hash_t hash
Py_ssize_t finger
setentry smalltable[8]
PyObject *weakreflist
cpdef print_set_table(set inp):
cdef PySetObject* innerset = <PySetObject *>inp
for idx in range(innerset.mask+1):
if (innerset.table[idx].key == NULL):
print(idx, '<EMPTY>')
else:
print(idx, innerset.table[idx].hash, <object>innerset.table[idx].key)
它将在集合内打印键值表:
a = {1}
print_set_table(a)
for idx, item in enumerate(a):
print('\nidx', idx)
a.discard(item)
a.add(item+1)
print_set_table(a)
请注意,输出将包含虚拟对象(已删除集合项目的剩余内容),并且有时会消失(当集合获取的内容太满 或 调整大小时)。
我有客户对象的列表。我想迭代列表并按1递增顺序。 我尝试了每个列表,但在这里我必须创建新列表并在其中添加值。 有没有更好的方法?我尝试使用streams,但它只是映射订单
问题 你想在多个对象执行相同的操作,但是这些对象在不同的容器中,你希望代码在不失可读性的情况下避免写重复的循环。 解决方案 itertools.chain() 方法可以用来简化这个任务。 它接受一个可迭代对象列表作为输入,并返回一个迭代器,有效的屏蔽掉在多个容器中迭代细节。 为了演示清楚,考虑下面这个例子: >>> from itertools import chain >>> a = [1, 2
AFAIK,有两种方法: 迭代集合的副本 使用实际集合的迭代器 例如, 而且 有没有理由偏爱一种方法而不是另一种方法(例如,由于可读性的简单原因而偏爱第一种方法)?
问题内容: 我正在对JRE 进行迭代,该JRE 强制执行快速失败迭代器概念,因此如果在迭代时修改了,则会抛出a ,而不是使用method。但是,如果对象满足条件,则需要删除该对象的“逻辑伙伴”。从而阻止伙伴也被处理。我怎样才能做到这一点?也许为此使用更好的收集类型? 例。 谢谢。 问题答案: 您要从列表中删除项目,然后继续在同一列表中进行迭代。您是否可以实施两步解决方案,即在步骤1中将要删除的项目
问题内容: AFAIK有两种方法: 遍历集合的副本 使用实际集合的迭代器 例如, 和 是否有任何理由偏爱一种方法(例如,出于可读性的简单原因而偏爱第一种方法)? 问题答案: 让我举几个例子,并提出一些避免方案。 假设我们有以下藏书 收集并删除 第一种技术是收集所有要删除的对象(例如,使用增强的for循环),并在完成迭代后删除所有找到的对象。 假设你要执行的操作是“删除”。 如果要“添加”此方法也可
我想在字符串与给定值匹配时更新该值 有没有人可以使用Java streams API来实现上述功能
Iterator(迭代器)是一个接口,它的作用就是遍历容器的所有元素,也是 Java 集合框架的成员,但它与 Collection 和 Map 系列的集合不一样,Collection 和 Map 系列集合主要用于盛装其他对象,而 Iterator 则主要用于遍历(即迭代访问)Collection 集合中的元素。 Iterator 接口隐藏了各种 Collection 实现类的底层细节,向应用程序提
问题内容: 在迭代集合时是否可以向集合中添加元素? 更具体地说,我想遍历一个集合,如果一个元素满足特定条件,我想向集合中添加一些其他元素,并确保也对这些添加的元素进行遍历。(我意识到这可能会导致循环终止,但是我很确定不会发生这种情况。) Sun 的Java教程建议不可能:“请注意,这是在迭代过程中修改集合的唯一安全方法;如果在迭代进行过程中对基础集合进行了其他任何修改,则行为未指定。” 因此,如果