Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/apache-flex/4.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Python 在迭代集合元素时更新集合_Python_Set_Iteration_Python Internals - Fatal编程技术网

Python 在迭代集合元素时更新集合

Python 在迭代集合元素时更新集合,python,set,iteration,python-internals,Python,Set,Iteration,Python Internals,当我在迭代集合的元素时尝试更新集合时,它的行为应该是什么 我在不同的场景中尝试了它,它不会迭代迭代开始后添加的元素,也不会迭代迭代期间删除的元素。若我在迭代过程中移除并放回任何元素,那个么该元素将被考虑。确切的行为是什么?它是如何工作的 这将打印字符串的所有排列: def permutations(s): ans = [] def helper(created, remaining): if len(created) == len(s):

当我在迭代集合的元素时尝试更新集合时,它的行为应该是什么

我在不同的场景中尝试了它,它不会迭代迭代开始后添加的元素,也不会迭代迭代期间删除的元素。若我在迭代过程中移除并放回任何元素,那个么该元素将被考虑。确切的行为是什么?它是如何工作的

这将打印字符串的所有排列:

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
def permwdups:
ans=[]
def辅助程序(已创建,剩余):
如果len(已创建)=len(s):
ans.append(“”.join(已创建))
返回
对于剩余的ch in:

如果(剩余[ch]实际上非常简单,
set
s作为
hash
-
表在CPython中实现:

  hash  |  item  
-----------------
    -   |    -
-----------------
    -   |    -
       ...
CPython使用开放寻址,因此表中并非所有的行都被填充,并且在发生冲突的情况下,它通过“伪随机”位置确定基于项的(截断的)散列来确定元素的位置。我将在这个答案中忽略截断的散列冲突

我还将忽略散列截断的细节,只处理整数,所有整数(除某些例外)都将其散列映射到实际值:

>>> hash(1)
1
>>> hash(2)
2
>>> hash(20)
20
因此,当您创建一个值为1、2和3的
集合时,您将(大致)得到下表:

  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
-----------------
    -   |    -
       ...
迭代之前的集合如下所示:

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      <----------
-----------------
    -   |    -
因此,在下一次迭代中,迭代器将找到值
2
,并返回它。然后将这两个值相加,并添加一个3:

  hash  |  item  
-----------------
    -   |    -
-----------------
    -   |    -      <----------
-----------------
    2   |    2
  hash  |  item  
-----------------
    -   |    -
-----------------
    -   |    -
-----------------
    -   |    -      <----------
-----------------
    3   |    3
使用简单的GIF进行可视化:

请注意,这些示例非常简单,如果
集合
在迭代过程中调整大小,它将根据“新的”截断散列重新分发存储的项,并且还将删除从集合中删除项时留下的假人。在这种情况下,您仍然可以(大致)预测将会发生什么,但它将变得更加复杂

另一个但非常重要的事实是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/…它几乎总是会导致问题,即使没有问题,也会让阅读它的人感到困惑!尽管在少数非常罕见的情况下,在迭代过程中向列表添加元素是有意义的。这需要非常详细的说明ic注释在它旁边,否则它看起来像一个错误!尤其是对于集合和dict,您将依赖于可能在任何时候更改的实现细节


为了防止您好奇,我在Jupyter笔记本中使用了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)
请注意