簡體   English   中英

計算Python字典中的沖突

[英]Counting collisions in a Python dictionary

我第一次在這里發帖,所以希望我以正確的方式問我的問題,

將元素添加到Python字典后,是否可以讓Python告訴您添加該元素是否導致了沖突? (在找到放置元素的位置之前,碰撞解決策略探測了多少個位置?)

我的問題是:我使用字典作為更大項目的一部分,經過大量的分析后,我發現代碼中最慢的部分是處理使用字典實現的稀疏距離矩陣。

我正在使用的鍵是Python對象的ID,它們是唯一的整數,所以我知道它們都散列為不同的值。 但是將它們放在字典中仍然可能導致原則上的沖突。 我不相信字典沖突會減慢我的程序速度,但我想從我的查詢中刪除它們。

因此,例如,給出以下字典:

d = {}
for i in xrange(15000):
    d[random.randint(15000000, 18000000)] = 0

你能讓Python告訴你創建它時發生了多少次沖突嗎?

我的實際代碼與應用程序糾纏在一起,但上面的代碼生成了一個與我正在使用的字典非常相似的字典。

重復:我不認為碰撞會減慢我的代碼速度,我只是想通過顯示我的字典沒有多次碰撞來消除這種可能性。

謝謝你的幫助。

編輯:實現@Winston Ewert解決方案的一些代碼:

n = 1500
global collision_count
collision_count = 0

class Foo():

    def __eq__(self, other):
        global collision_count
        collision_count += 1
        return id(self) == id(other)

    def __hash__(self):
        #return id(self) # @John Machin: yes, I know!
        return 1

objects = [Foo() for i in xrange(n)]

d = {}
for o in objects:
    d[o] = 1

print collision_count

請注意,當您在類上定義__eq__時,如果您還沒有定義__hash__函數,Python會為您提供TypeError: unhashable instance

它沒有像我預期的那樣運行。 如果你有__hash__函數return 1 ,那么你會得到大量的碰撞,正如預期的那樣(我的系統上n = 1500的1125560次碰撞)。 但是return id(self) ,有0次沖突。

任何人都知道為什么這會說0次碰撞?

編輯:我可能已經想到了這一點。

是因為__eq__僅在兩個對象的__hash__值相同時調用,而不是它們的“crunched版本”(如@John Machin所說)?

簡短回答:

您不能使用隨機整數作為dict鍵來模擬使用對象ID作為dict鍵。 它們具有不同的散列函數。

碰撞確實發生了。 “具有獨特的東西意味着沒有碰撞”對於“thingy”的幾個值是錯誤的。

你不應該擔心碰撞。

答案很長:

閱讀源代碼得出的一些解釋:

dict實現為2 ** i個條目的表,其中i是整數。

dicts不超過2/3滿。 因此對於15000個鍵,我必須是15和2 **我是32768。

當o是沒有定義__hash__()的類的任意實例時,hash(o)== id(o)不是真的 由於地址可能在低位3或4位中具有零,因此通過將地址右旋4位來構造散列。 查看源文件Objects / object.c ,函數_Py_HashPointer

如果在低位中存在大量零,那將是一個問題,因為要訪問大小為2 ** i的表(例如32768),哈希值(通常遠大於該值)必須嘎吱嘎吱地擬合,通過獲取散列值的低階i(例如15)位,可以非常簡單快速地完成這一過程。

因此, 碰撞是不可避免的

然而,這不是引起恐慌的原因。 散列值的其余位被計入下一次探測的位置計算中。 需要第三次等待探測的可能性應該相當小,特別是因為dict永遠不會超過2/3滿。 通過計算第一次和后續探針的槽的便宜成本來減輕多個探針的成本。

下面的代碼是一個簡單的實驗,說明了上述大多數討論。 它假定dict在達到其最大大小后隨機訪問。 使用Python2.7.1,它顯示了15000個對象的大約2000次沖突(13.3%)。

無論如何,最重要的是你應該把注意力轉移到其他地方。 碰撞不是你的問題,除非你已經為你的對象獲得了一些非常不正常的獲取內存的方法。 您應該看一下如何使用dicts,例如k in d使用k in d或嘗試/ except,而不是d.has_key(k) 考慮一個dict作為d[(x, y)]訪問,而不是作為d[x][y]訪問的兩個級別。 如果您需要幫助,請提出單獨的問題。

在Python 2.6上測試后更新

直到Python 2.7才引入旋轉地址; 請參閱此錯誤報告以獲得全面的討論和基准。 基本結論是恕我直言仍然有效,並可以通過“更新,如果你可以”增加。

>>> n = 15000
>>> i = 0
>>> while 2 ** i / 1.5 < n:
...    i += 1
...
>>> print i, 2 ** i, int(2 ** i / 1.5)
15 32768 21845
>>> probe_mask = 2 ** i - 1
>>> print hex(probe_mask)
0x7fff
>>> class Foo(object):
...     pass
...
>>> olist = [Foo() for j in xrange(n)]
>>> hashes = [hash(o) for o in olist]
>>> print len(set(hashes))
15000
>>> probes = [h & probe_mask for h in hashes]
>>> print len(set(probes))
12997
>>>

這個想法實際上沒有用,請參閱問題中的討論。

快速瀏覽python的C實現表明,解決沖突的代碼不會計算或存儲沖突的數量。

但是,它將調用鍵上的PyObject_RichCompareBool來檢查它們是否匹配。 這意味着每次碰撞都會調用鍵上的__eq__

所以:

用定義__eq__對象替換鍵,並在調用時遞增計數器。 由於跳轉到python進行比較所涉及的開銷,這將會變慢。 但是,它應該讓您了解發生了多少次碰撞。

確保使用不同的對象作為鍵,否則python將采用快捷方式,因為對象始終等於自身。 此外,請確保對象散列為與原始鍵相同的值。

如果你的密鑰保證是唯一的整數,並且因為Python在密鑰上使用hash() ,那么你應該保證不會發生任何沖突。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM