這是根據網上看的一篇文章,對代碼進行了修改。
原網站的地址是:https://my.oschina.net/goal/blog/200347
bitmap是很常用的數據結構,比如用於Bloom Filter中、用於無重複整數的排序等等。bitmap通常基於數組來實現,數組中每個元素可以看成是一系列二進制數,所有元素組成更大的二進制集合。對於Python來說,整數類型默認是有符號類型,所以一個整數的可用位數爲31位。
bitmap實現思路
bitmap是用於對每一位進行操作。舉例來說,一個Python數組包含4個32位有符號整型,則總共可用位爲4 * 31 = 124位。如果要在第90個二進制位上操作,則要先獲取到操作數組的第幾個元素,再獲取相應的位索引,然後執行操作。
上圖所示爲一個32位整型,在Python中默認是有符號類型,最高位爲符號位,bitmap不能使用它。左邊是高位,右邊是低位,最低位爲第0位。
初始化bitmap
首先需要初始化bitmap。拿90這個整數來說,因爲單個整型只能使用31位,所以90除以31並向上取整則可得知需要幾個數組元素。代碼如下:
#!/usr/bin/env python
#coding: utf8
class Bitmap(object):
def __init__(self, max):
self.size = int((max + 31 - 1) / 31) #向上取整
if __name__ == '__main__':
bitmap = Bitmap(90)
print '需要 %d 個元素。' % bitmap.size
$ python bitmap.py
需要 3 個元素。
計算索引
確定了數組大小後,也就可以創建這個數組了。如果要將一個整數保存進這個數組,首先需要知道保存在這個數組的第幾個元素之上,然後要知道是在這個元素的第幾位上。因此計算索引分爲:
-
計算在數組中的索引
-
計算在數組元素中的位索引
計算在數組中的索引
計算在數組中的索引其實是跟之前計算數組大小是一樣的。只不過之前是對最大數計算,現在換成任一需要存儲的整數。但是有一點不同,計算在數組中的索引是向下取整,所以需要修改calcElemIndex方法的實現。代碼改爲如下:
#!/usr/bin/env python
#coding: utf8
class Bitmap(object):
def __init__(self, max):
self.size = self.calcElemIndex(max, True)
self.array = [0 for i in range(self.size)]
def calcElemIndex(self, num, up=False):
'''up爲True則爲向上取整, 否則爲向下取整'''
if up:
return int((num + 31 - 1) / 31) #向上取整
return num / 31
if __name__ == '__main__':
bitmap = Bitmap(90)
print '數組需要 %d 個元素。' % bitmap.size
print '47 應存儲在第 %d 個數組元素上。' % bitmap.calcElemIndex(47)
$ python bitmap.py
數組需要 3 個元素。
47 應存儲在第 1 個數組元素上。
所以獲取最大整數很重要,否則有可能創建的數組容納不下某些數據。
計算在數組元素中的位索引
數組元素中的位索引可以通過取模運算來得到。令需存儲的整數跟31取模即可得到位索引。代碼改爲如下:
#!/usr/bin/env python
#coding: utf8
class Bitmap(object):
def __init__(self, max):
self.size = self.calcElemIndex(max, True)
self.array = [0 for i in range(self.size)]
def calcElemIndex(self, num, up=False):
'''up爲True則爲向上取整, 否則爲向下取整'''
if up:
return int((num + 31 - 1) / 31) #向上取整
return num / 31
def calcBitIndex(self, num):
return num % 31
if __name__ == '__main__':
bitmap = Bitmap(90)
print '數組需要 %d 個元素。' % bitmap.size
print '47 應存儲在第 %d 個數組元素上。' % bitmap.calcElemIndex(47)
print '47 應存儲在第 %d 個數組元素的第 %d 位上。' % (bitmap.calcElemIndex(47), bitmap.calcBitIndex(47),)
$ python bitmap.py
數組需要 3 個元素。
47 應存儲在第 1 個數組元素上。
47 應存儲在第 1 個數組元素的第 16 位上。
別忘了是從第0位算起哦。
置1操作
二進制位默認是0,將某位置1則表示在此位存儲了數據。代碼改爲如下:
#!/usr/bin/env python
#coding: utf8
class Bitmap(object):
def __init__(self, max):
self.size = self.calcElemIndex(max, True)
self.array = [0 for i in range(self.size)]
def calcElemIndex(self, num, up=False):
'''up爲True則爲向上取整, 否則爲向下取整'''
if up:
return int((num + 31 - 1) / 31) #向上取整
return num / 31
def calcBitIndex(self, num):
return num % 31
def set(self, num):
elemIndex = self.calcElemIndex(num)
byteIndex = self.calcBitIndex(num)
elem = self.array[elemIndex]
self.array[elemIndex] = elem | (1 << byteIndex)
if __name__ == '__main__':
bitmap = Bitmap(90)
bitmap.set(0)
print bitmap.array
$ python bitmap.py
[1, 0, 0]
因爲從第0位算起,所以如需要存儲0,則需要把第0位置1。
清0操作
將某位置0,也即丟棄已存儲的數據。代碼如下:
#!/usr/bin/env python
#coding: utf8
class Bitmap(object):
def __init__(self, max):
self.size = self.calcElemIndex(max, True)
self.array = [0 for i in range(self.size)]
def calcElemIndex(self, num, up=False):
'''up爲True則爲向上取整, 否則爲向下取整'''
if up:
return int((num + 31 - 1) / 31) #向上取整
return num / 31
def calcBitIndex(self, num):
return num % 31
def set(self, num):
elemIndex = self.calcElemIndex(num)
byteIndex = self.calcBitIndex(num)
elem = self.array[elemIndex]
self.array[elemIndex] = elem | (1 << byteIndex)
def clean(self, i):
elemIndex = self.calcElemIndex(i)
byteIndex = self.calcBitIndex(i)
elem = self.array[elemIndex]
self.array[elemIndex] = elem & (~(1 << byteIndex))
if __name__ == '__main__':
bitmap = Bitmap(87)
bitmap.set(0)
bitmap.set(34)
print bitmap.array
bitmap.clean(0)
print bitmap.array
bitmap.clean(34)
print bitmap.array
$ python bitmap.py
[1, 8, 0]
[0, 8, 0]
[0, 0, 0]
清0和置1是互反操作。
測試某位是否爲1
判斷某位是否爲1是爲了取出之前所存儲的數據。代碼如下:
#!/usr/bin/env python
#coding: utf8
class Bitmap(object):
def __init__(self, max):
self.size = self.calcElemIndex(max, True)
self.array = [0 for i in range(self.size)]
def calcElemIndex(self, num, up=False):
'''up爲True則爲向上取整, 否則爲向下取整'''
if up:
return int((num + 31 - 1) / 31) #向上取整
return num / 31
def calcBitIndex(self, num):
return num % 31
def set(self, num):
elemIndex = self.calcElemIndex(num)
byteIndex = self.calcBitIndex(num)
elem = self.array[elemIndex]
self.array[elemIndex] = elem | (1 << byteIndex)
def clean(self, i):
elemIndex = self.calcElemIndex(i)
byteIndex = self.calcBitIndex(i)
elem = self.array[elemIndex]
self.array[elemIndex] = elem & (~(1 << byteIndex))
def test(self, i):
elemIndex = self.calcElemIndex(i)
byteIndex = self.calcBitIndex(i)
if self.array[elemIndex] & (1 << byteIndex):
return True
return False
if __name__ == '__main__':
bitmap = Bitmap(90)
bitmap.set(0)
print bitmap.array
print bitmap.test(0)
bitmap.set(1)
print bitmap.test(1)
print bitmap.test(2)
bitmap.clean(1)
print bitmap.test(1)
$ python bitmap.py
[1, 0, 0]
True
True
False
False
接下來實現一個不重複數組的排序。已知一個無序非負整數數組的最大元素爲879,請對其自然排序。代碼如下:
#!/usr/bin/env python
#coding: utf8
class Bitmap(object):
def __init__(self, max):
self.size = self.calcElemIndex(max, True)
self.array = [0 for i in range(self.size)]
def calcElemIndex(self, num, up=False):
'''up爲True則爲向上取整, 否則爲向下取整'''
if up:
return int((num + 31 - 1) / 31) #向上取整
return num / 31
def calcBitIndex(self, num):
return num % 31
def set(self, num):
elemIndex = self.calcElemIndex(num)
byteIndex = self.calcBitIndex(num)
elem = self.array[elemIndex]
self.array[elemIndex] = elem | (1 << byteIndex)
def clean(self, i):
elemIndex = self.calcElemIndex(i)
byteIndex = self.calcBitIndex(i)
elem = self.array[elemIndex]
self.array[elemIndex] = elem & (~(1 << byteIndex))
def test(self, i):
elemIndex = self.calcElemIndex(i)
byteIndex = self.calcBitIndex(i)
if self.array[elemIndex] & (1 << byteIndex):
return True
return False
if __name__ == '__main__':
MAX = 879
suffle_array = [45, 2, 78, 35, 67, 90, 879, 0, 340, 123, 46]
result = []
bitmap = Bitmap(MAX)
for num in suffle_array:
bitmap.set(num)
for i in range(MAX + 1):
if bitmap.test(i):
result.append(i)
print '原始數組爲: %s' % suffle_array
print '排序後的數組爲: %s' % result
$ python bitmap.py
原始數組爲: [45, 2, 78, 35, 67, 90, 879, 0, 340, 123, 46]
排序後的數組爲:[0, 2, 35, 45, 46, 67, 78, 90, 123, 340, 879]
結束語
bitmap實現了,則利用其進行排序就非常簡單了。其它語言也同樣可以實現bitmap,但對於靜態類型語言來說,比如C/Golang這樣的語言,因爲可以直接聲明無符號整型,所以可用位就變成32位,只需將上述代碼中的31改成32即可,這點請大家注意。
我對代碼修改改了兩部分代碼
如下
class Bitmap(object):
def __init__(self, max):
self.size = self.calcElemIndex(max)
self.array = [0 for _ in range(self.size)]
def calcElemIndex(self, num):
"""
加 1 的原因是,0 數組中也可能包含 0
所以應該向上取整
:param num:
:return:
"""
# return int((num + 31 - 1) / 31) # 向上取整
return (num + 1) // 31 + 1
def calcBitIndex(self, num):
return num % 31
def set(self, num):
elemIndex = self.calcElemIndex(num) - 1
byteIndex = self.calcBitIndex(num)
print(f"self.array is {self.array}, size is {len(self.array)}")
print(f'eleIndex is {elemIndex}')
elem = self.array[elemIndex]
# 爲什麼要用 | 這個的知道,
# 這個可以表示集合的或
# 還可以表示二進制的或
self.array[elemIndex] = elem | (1 << byteIndex)
def test(self, i):
eleIndex = self.calcElemIndex(i) - 1
byteIndex = self.calcBitIndex(i)
elem = self.array[eleIndex]
return True if elem & (1 << byteIndex) == (1 << byteIndex) else False
nums = [45, 2, 78, 35, 67, 90, 879, 0, 340, 123, 46]
MAX = 879
bitmap = Bitmap(MAX)
for num in nums:
bitmap.set(num)
result = []
for i in range(MAX + 1):
if bitmap.test(i):
result.append(i)
print(result)