c++ set 遍历_47. Set 是如何工作的(3) 遍历顺序是如何确定的?
Set 是無序容器,它的插入順序與迭代(或者 print)輸出的順序不保證與插入順序一致,與 Dict 類似的問題,Set 的輸出順序是如何決定的呢?
首先我們從 Set 的輸出開始尋找蛛絲馬跡,在 Dict 的研究中,我們知道對象的輸出由 *_repr 函數實現,對于 SetObject 這個函數是 set_repr:
// Objects/setobject.c:579set_repr(PySetObject *so) {// ··· ···// Set 轉換為 Listkeys = PySequence_List((PyObject *)so);// ··· ···// 獲得 List 的字符串listrepr = PyObject_Repr(keys);// 截掉 List 輸出左右兩端的 "[" 和 "]"tmp = PyUnicode_Substring(listrepr, 1, PyUnicode_GET_LENGTH(listrepr)-1);// ··· ···// 拼接輸出// ··· ···return result; }Set 的輸出經歷了以下步驟:
- Set 轉換為 List;
- List 轉換為 String;
- 舍棄 List 輸出左右兩端的方括號,替換為小括號 ();
我們又一次見到了熟悉的面孔 PySequence_List,該函數的作用是將可迭代對象轉換為 List 對象。Set 也是可迭代對象,具體的迭代操作由:
// Objects/setobject.c:867static PyObject *setiter_iternext(setiterobject *si){// ··· ··· }該函數的代碼表明 Set 對象的遍歷,實際上是對 Set->table 的遍歷,但是會忽略 NULL 和 dummy 元素。Set 的遍歷順序由其 table 中 entry 的實際排布決定。Set 元素的插入方式,可以參看上一節的介紹,其中包含了 Set 的 lookup 邏輯。
驗證
下面以一個簡單的例子驗證我們的理解。首先創建一個空的 Set,命名為 a。為了方便,依然使用整數作為 Key,原因是整數的 hash 就是它們自身。
a = set()此時 a 的 table 長度為初始值 8。依次插入 1~4,會分別插入 table[1~4]。當插入 5 的時候,5/8 > 3/5,table 會伸縮到 4 倍有效元素數量 5 * 4 = 20,在實際分配的時候,內存尺寸會對 2^n 向上取整, 實際伸縮 table 長度到 32。
a.update([1,2,3,4])此時 table 的樣子:
下面插入 5:
# resize the table firstly, then insert. a.add(5)經過 resize 的 table 看起來如此:
如果將 a 打印出來,輸出的順序應該就是 1~5,#0 處的 NULL 會被忽略掉。
print(a)輸出:
{1, 2, 3, 4, 5}注意,此時 table 的長度已經是 32 了,下面插入一個較大的 Key = 32,對 32 求模剛好為 0,那么它會被插入到 #0:
a.add(32) print(a)上面的輸出也可以印證這一點,輸出:
{32, 1, 2, 3, 4, 5}新插入的 32 打破了插入順序和 Key 的大小順序。 同理:
- 如果繼續插入 Key = 39,它會出現在 #7 的位置;
- 再插入 Key = 70,則會出現在 #6 的位置;
我們通過代碼來驗證
a.add(39) a.add(70) print(a)輸出:
{32, 1, 2, 3, 4, 5, 70, 39}哈! 與預期完全一致!
關注我,了解程序員的燒腦日常,還有開源 Python 教程。
總結
以上是生活随笔為你收集整理的c++ set 遍历_47. Set 是如何工作的(3) 遍历顺序是如何确定的?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: sas sql中有类似mysql的 g_
- 下一篇: 补脑汁的功效与作用、禁忌和食用方法