-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmet.py
148 lines (114 loc) · 3.73 KB
/
met.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
import itertools
from abc import ABC, abstractmethod
from collections.abc import Callable, Collection, Iterable
import numpy as np
import hashing
np_type = np.int64
class InvertibleBloomFilterAPI(ABC):
@abstractmethod
def insert(self, x):
pass
def insert_from(self, s: Iterable):
"""
Helper method.
:param s: set of elements to be inserted.
"""
for x in s:
self.insert(x)
@abstractmethod
def delete(self, x):
pass
@abstractmethod
def peel(self) -> Collection:
"""
Return the elements that were inserted to the IBF.
Notes:
- The collection can be partial.
- The operation is destructive; returned elements are deleted.
"""
pass
@abstractmethod
def __bool__(self):
pass
@property
@abstractmethod
def m(self):
pass
class IBF(InvertibleBloomFilterAPI):
def __init__(self, m: int, hasher: Callable[[int], Iterable[int]]):
self.T = np.zeros((2, m), dtype=np_type)
self.hasher = hasher
self.n = 0
@staticmethod
def create_irregular(m: int, key2deg: Callable[[int], int]):
return IBF(m, lambda x: hashing.hash_sample(x, range(m), key2deg(x)))
@staticmethod
def create(m, k):
return IBF.create_irregular(m, lambda x: k)
@property
def m(self):
return self.T.shape[1]
def _indel(self, x, c):
idxs = self.hasher(x)
if len(idxs) == 0:
return
self.n += c
self.T[0, idxs] += np_type(c)
self.T[1, idxs] ^= np_type(x)
return idxs
def insert(self, x):
self._indel(x, 1)
def delete(self, x):
self._indel(x, -1)
def peel(self) -> Collection:
pure_cells = {i for i, (count, xor) in enumerate(self.T.T) if count == 1}
res = []
while pure_cells:
i_pure = pure_cells.pop()
x = int(self.T[1, i_pure])
res.append(x)
idxs = self._indel(x, -1)
for i in idxs:
if self.T[0, i] == 1:
pure_cells.add(i)
if self.T[0, i] == 0:
pure_cells.discard(i)
return res
def __bool__(self):
return bool(self.T.any())
class METIBF(InvertibleBloomFilterAPI):
def __init__(self, deg_matrix: np.ndarray, m_cells: np.ndarray, key2type: Callable[[int], int]):
self.D = deg_matrix
self.M = m_cells
self.key2type = key2type
self.tables: list[InvertibleBloomFilterAPI] = [
IBF(m=m, hasher=self.create_table_hahser(cell_type))
for cell_type, m in enumerate(self.M)
]
def create_table_hahser(self, cell_type):
return lambda x: hashing.hash_sample(x + cell_type,
range(self.M[cell_type]),
self.D[cell_type, self.key2type(x)])
def insert(self, x):
for table in self.tables:
table.insert(x)
def delete(self, x):
for table in self.tables:
table.delete(x)
def _peel_once(self):
removed_per_table = [set(table.peel()) for table in self.tables]
removed_total = set(itertools.chain.from_iterable(removed_per_table))
for removed_already, table in zip(removed_per_table, self.tables):
for x in removed_total - removed_already:
table.delete(x)
return removed_total
def peel(self) -> Collection:
res = []
while peels := self._peel_once():
res.extend(peels)
return res
def __bool__(self):
return any(map(bool, self.tables))
@property
def m(self):
return self.M.sum()