datasketch 1.6.3__tar.gz → 1.6.5__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {datasketch-1.6.3 → datasketch-1.6.5}/PKG-INFO +5 -5
- {datasketch-1.6.3 → datasketch-1.6.5}/README.rst +4 -4
- {datasketch-1.6.3 → datasketch-1.6.5}/datasketch/hnsw.py +14 -29
- {datasketch-1.6.3 → datasketch-1.6.5}/datasketch/lsh.py +55 -0
- {datasketch-1.6.3 → datasketch-1.6.5}/datasketch/lshforest.py +25 -0
- datasketch-1.6.5/datasketch/version.py +1 -0
- {datasketch-1.6.3 → datasketch-1.6.5}/datasketch.egg-info/PKG-INFO +5 -5
- {datasketch-1.6.3 → datasketch-1.6.5}/test/test_hnsw.py +4 -1
- {datasketch-1.6.3 → datasketch-1.6.5}/test/test_lsh.py +111 -0
- {datasketch-1.6.3 → datasketch-1.6.5}/test/test_lshforest.py +12 -0
- datasketch-1.6.3/datasketch/version.py +0 -1
- {datasketch-1.6.3 → datasketch-1.6.5}/LICENSE +0 -0
- {datasketch-1.6.3 → datasketch-1.6.5}/datasketch/__init__.py +0 -0
- {datasketch-1.6.3 → datasketch-1.6.5}/datasketch/b_bit_minhash.py +0 -0
- {datasketch-1.6.3 → datasketch-1.6.5}/datasketch/experimental/__init__.py +0 -0
- {datasketch-1.6.3 → datasketch-1.6.5}/datasketch/experimental/aio/__init__.py +0 -0
- {datasketch-1.6.3 → datasketch-1.6.5}/datasketch/experimental/aio/lsh.py +0 -0
- {datasketch-1.6.3 → datasketch-1.6.5}/datasketch/experimental/aio/storage.py +0 -0
- {datasketch-1.6.3 → datasketch-1.6.5}/datasketch/hashfunc.py +0 -0
- {datasketch-1.6.3 → datasketch-1.6.5}/datasketch/hyperloglog.py +0 -0
- {datasketch-1.6.3 → datasketch-1.6.5}/datasketch/hyperloglog_const.py +0 -0
- {datasketch-1.6.3 → datasketch-1.6.5}/datasketch/lean_minhash.py +0 -0
- {datasketch-1.6.3 → datasketch-1.6.5}/datasketch/lshensemble.py +0 -0
- {datasketch-1.6.3 → datasketch-1.6.5}/datasketch/lshensemble_partition.py +0 -0
- {datasketch-1.6.3 → datasketch-1.6.5}/datasketch/minhash.py +0 -0
- {datasketch-1.6.3 → datasketch-1.6.5}/datasketch/storage.py +0 -0
- {datasketch-1.6.3 → datasketch-1.6.5}/datasketch/weighted_minhash.py +0 -0
- {datasketch-1.6.3 → datasketch-1.6.5}/datasketch.egg-info/SOURCES.txt +0 -0
- {datasketch-1.6.3 → datasketch-1.6.5}/datasketch.egg-info/dependency_links.txt +0 -0
- {datasketch-1.6.3 → datasketch-1.6.5}/datasketch.egg-info/requires.txt +0 -0
- {datasketch-1.6.3 → datasketch-1.6.5}/datasketch.egg-info/top_level.txt +0 -0
- {datasketch-1.6.3 → datasketch-1.6.5}/setup.cfg +0 -0
- {datasketch-1.6.3 → datasketch-1.6.5}/setup.py +0 -0
- {datasketch-1.6.3 → datasketch-1.6.5}/test/test_hyperloglog.py +0 -0
- {datasketch-1.6.3 → datasketch-1.6.5}/test/test_lean_minhash.py +0 -0
- {datasketch-1.6.3 → datasketch-1.6.5}/test/test_lsh_cassandra.py +0 -0
- {datasketch-1.6.3 → datasketch-1.6.5}/test/test_lshensemble.py +0 -0
- {datasketch-1.6.3 → datasketch-1.6.5}/test/test_minhash.py +0 -0
- {datasketch-1.6.3 → datasketch-1.6.5}/test/test_weighted_minhash.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: datasketch
|
|
3
|
-
Version: 1.6.
|
|
3
|
+
Version: 1.6.5
|
|
4
4
|
Summary: Probabilistic data structures for processing and searching very large datasets
|
|
5
5
|
Home-page: https://ekzhu.github.io/datasketch
|
|
6
6
|
Author: ekzhu
|
|
@@ -51,11 +51,11 @@ Requires-Dist: motor; python_version >= "3.6" and extra == "experimental-aio"
|
|
|
51
51
|
datasketch: Big Data Looks Small
|
|
52
52
|
================================
|
|
53
53
|
|
|
54
|
-
.. image:: https://
|
|
55
|
-
:target: https://
|
|
54
|
+
.. image:: https://static.pepy.tech/badge/datasketch/month
|
|
55
|
+
:target: https://pepy.tech/project/datasketch
|
|
56
56
|
|
|
57
|
-
.. image:: https://zenodo.org/badge/DOI/10.5281/zenodo.
|
|
58
|
-
:target: https://
|
|
57
|
+
.. image:: https://zenodo.org/badge/DOI/10.5281/zenodo.598238.svg
|
|
58
|
+
:target: https://zenodo.org/doi/10.5281/zenodo.598238
|
|
59
59
|
|
|
60
60
|
datasketch gives you probabilistic data structures that can process and
|
|
61
61
|
search very large amount of data super fast, with little loss of
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
datasketch: Big Data Looks Small
|
|
2
2
|
================================
|
|
3
3
|
|
|
4
|
-
.. image:: https://
|
|
5
|
-
:target: https://
|
|
4
|
+
.. image:: https://static.pepy.tech/badge/datasketch/month
|
|
5
|
+
:target: https://pepy.tech/project/datasketch
|
|
6
6
|
|
|
7
|
-
.. image:: https://zenodo.org/badge/DOI/10.5281/zenodo.
|
|
8
|
-
:target: https://
|
|
7
|
+
.. image:: https://zenodo.org/badge/DOI/10.5281/zenodo.598238.svg
|
|
8
|
+
:target: https://zenodo.org/doi/10.5281/zenodo.598238
|
|
9
9
|
|
|
10
10
|
datasketch gives you probabilistic data structures that can process and
|
|
11
11
|
search very large amount of data super fast, with little loss of
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
from collections import OrderedDict
|
|
3
3
|
import heapq
|
|
4
|
-
from itertools import dropwhile
|
|
5
4
|
from typing import (
|
|
6
5
|
Hashable,
|
|
7
6
|
Callable,
|
|
@@ -59,7 +58,7 @@ class _Layer(object):
|
|
|
59
58
|
def copy(self) -> _Layer:
|
|
60
59
|
"""Create a copy of the layer."""
|
|
61
60
|
new_layer = _Layer(None)
|
|
62
|
-
new_layer._graph = {k: v
|
|
61
|
+
new_layer._graph = {k: dict(v) for k, v in self._graph.items()}
|
|
63
62
|
return new_layer
|
|
64
63
|
|
|
65
64
|
def get_reverse_edges(self, key: Hashable) -> Set[Hashable]:
|
|
@@ -91,6 +90,8 @@ class _LayerWithReversedEdges(_Layer):
|
|
|
91
90
|
self._reverse_edges[neighbor].discard(key)
|
|
92
91
|
for neighbor in value:
|
|
93
92
|
self._reverse_edges.setdefault(neighbor, set()).add(key)
|
|
93
|
+
if key not in self._reverse_edges:
|
|
94
|
+
self._reverse_edges[key] = set()
|
|
94
95
|
|
|
95
96
|
def __delitem__(self, key: Hashable) -> None:
|
|
96
97
|
old_neighbors = self._graph.get(key, {})
|
|
@@ -115,8 +116,8 @@ class _LayerWithReversedEdges(_Layer):
|
|
|
115
116
|
def copy(self) -> _LayerWithReversedEdges:
|
|
116
117
|
"""Create a copy of the layer."""
|
|
117
118
|
new_layer = _LayerWithReversedEdges(None)
|
|
118
|
-
new_layer._graph = {k: v
|
|
119
|
-
new_layer._reverse_edges = self._reverse_edges.
|
|
119
|
+
new_layer._graph = {k: dict(v) for k, v in self._graph.items()}
|
|
120
|
+
new_layer._reverse_edges = {k: set(v) for k, v in self._reverse_edges.items()}
|
|
120
121
|
return new_layer
|
|
121
122
|
|
|
122
123
|
def get_reverse_edges(self, key: Hashable) -> Set[Hashable]:
|
|
@@ -169,6 +170,9 @@ class HNSW(MutableMapping):
|
|
|
169
170
|
the 0th level. If None, defaults to 2 * m.
|
|
170
171
|
seed (Optional[int]): The random seed to use for the random number
|
|
171
172
|
generator.
|
|
173
|
+
reverse_edges (bool): Whether to maintain reverse edges in the graph.
|
|
174
|
+
This speeds up hard remove (:meth:`remove`) but increases memory
|
|
175
|
+
usage and slows down :meth:`insert`.
|
|
172
176
|
|
|
173
177
|
Examples:
|
|
174
178
|
|
|
@@ -400,7 +404,9 @@ class HNSW(MutableMapping):
|
|
|
400
404
|
ef_construction=self._ef_construction,
|
|
401
405
|
m0=self._m0,
|
|
402
406
|
)
|
|
403
|
-
new_index._nodes =
|
|
407
|
+
new_index._nodes = OrderedDict(
|
|
408
|
+
(key, node.copy()) for key, node in self._nodes.items()
|
|
409
|
+
)
|
|
404
410
|
new_index._graphs = [layer.copy() for layer in self._graphs]
|
|
405
411
|
new_index._entry_point = self._entry_point
|
|
406
412
|
new_index._random.set_state(self._random.get_state())
|
|
@@ -608,6 +614,7 @@ class HNSW(MutableMapping):
|
|
|
608
614
|
entry_point,
|
|
609
615
|
entry_point_dist,
|
|
610
616
|
layer,
|
|
617
|
+
# We allow soft-deleted points to be returned and used as entry point.
|
|
611
618
|
allow_soft_deleted=True,
|
|
612
619
|
key_to_hard_delete=key_to_delete,
|
|
613
620
|
)
|
|
@@ -620,6 +627,8 @@ class HNSW(MutableMapping):
|
|
|
620
627
|
entry_points,
|
|
621
628
|
layer,
|
|
622
629
|
ef + 1, # We add 1 to ef to account for the point itself.
|
|
630
|
+
# We allow soft-deleted points to be returned and used as entry point
|
|
631
|
+
# and neighbor candidates.
|
|
623
632
|
allow_soft_deleted=True,
|
|
624
633
|
key_to_hard_delete=key_to_delete,
|
|
625
634
|
)
|
|
@@ -1045,27 +1054,3 @@ class HNSW(MutableMapping):
|
|
|
1045
1054
|
new_index = self.copy()
|
|
1046
1055
|
new_index.update(other)
|
|
1047
1056
|
return new_index
|
|
1048
|
-
|
|
1049
|
-
def get_non_reachable_keys(self, ef: Optional[int] = None) -> List[Hashable]:
|
|
1050
|
-
"""Return a list of keys of points that are not reachable from the entry
|
|
1051
|
-
point using the given ``ef`` value.
|
|
1052
|
-
|
|
1053
|
-
Args:
|
|
1054
|
-
ef (Optional[int]): The number of neighbors to consider during
|
|
1055
|
-
search. If None, use the construction ef.
|
|
1056
|
-
|
|
1057
|
-
Returns:
|
|
1058
|
-
List[Hashable]: A list of keys of points that are not reachable.
|
|
1059
|
-
"""
|
|
1060
|
-
if ef is None:
|
|
1061
|
-
ef = self._ef_construction
|
|
1062
|
-
non_reachable = []
|
|
1063
|
-
if self._entry_point is None:
|
|
1064
|
-
return non_reachable
|
|
1065
|
-
for key, node in self._nodes.items():
|
|
1066
|
-
if node.is_deleted:
|
|
1067
|
-
continue
|
|
1068
|
-
neighbors = self.query(node.point, ef=ef)
|
|
1069
|
-
if key not in [k for k, _ in neighbors]:
|
|
1070
|
-
non_reachable.append(key)
|
|
1071
|
-
return non_reachable
|
|
@@ -226,6 +226,29 @@ class MinHashLSH(object):
|
|
|
226
226
|
"""
|
|
227
227
|
self._insert(key, minhash, check_duplication=check_duplication, buffer=False)
|
|
228
228
|
|
|
229
|
+
def merge(
|
|
230
|
+
self,
|
|
231
|
+
other: MinHashLSH,
|
|
232
|
+
check_overlap: bool = False
|
|
233
|
+
):
|
|
234
|
+
"""Merge the other MinHashLSH with this one, making this one the union
|
|
235
|
+
of both.
|
|
236
|
+
|
|
237
|
+
Note:
|
|
238
|
+
Only num_perm, number of bands and sizes of each band is checked for equivalency of two MinHashLSH indexes.
|
|
239
|
+
Other initialization parameters threshold, weights, storage_config, prepickle and hash_func are not checked.
|
|
240
|
+
|
|
241
|
+
Args:
|
|
242
|
+
other (MinHashLSH): The other MinHashLSH.
|
|
243
|
+
check_overlap (bool): Check if there are any overlapping keys before merging and raise if there are any.
|
|
244
|
+
(`default=False`)
|
|
245
|
+
|
|
246
|
+
Raises:
|
|
247
|
+
ValueError: If the two MinHashLSH have different initialization
|
|
248
|
+
parameters, or if `check_overlap` is `True` and there are overlapping keys.
|
|
249
|
+
"""
|
|
250
|
+
self._merge(other, check_overlap=check_overlap, buffer=False)
|
|
251
|
+
|
|
229
252
|
def insertion_session(self, buffer_size: int = 50000) -> MinHashLSHInsertionSession:
|
|
230
253
|
"""
|
|
231
254
|
Create a context manager for fast insertion into this index.
|
|
@@ -282,6 +305,38 @@ class MinHashLSH(object):
|
|
|
282
305
|
for H, hashtable in zip(Hs, self.hashtables):
|
|
283
306
|
hashtable.insert(H, key, buffer=buffer)
|
|
284
307
|
|
|
308
|
+
def __equivalent(self, other:MinHashLSH) -> bool:
|
|
309
|
+
"""
|
|
310
|
+
Returns:
|
|
311
|
+
bool: If the two MinHashLSH have equal num_perm, number of bands, size of each band then two are equivalent.
|
|
312
|
+
"""
|
|
313
|
+
return (
|
|
314
|
+
type(self) is type(other) and
|
|
315
|
+
self.h == other.h and
|
|
316
|
+
self.b == other.b and
|
|
317
|
+
self.r == other.r
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
def _merge(
|
|
321
|
+
self,
|
|
322
|
+
other: MinHashLSH,
|
|
323
|
+
check_overlap: bool = False,
|
|
324
|
+
buffer: bool = False
|
|
325
|
+
) -> MinHashLSH:
|
|
326
|
+
if self.__equivalent(other):
|
|
327
|
+
if check_overlap and set(self.keys).intersection(set(other.keys)):
|
|
328
|
+
raise ValueError("The keys are overlapping, duplicate key exists.")
|
|
329
|
+
for key in other.keys:
|
|
330
|
+
Hs = other.keys.get(key)
|
|
331
|
+
self.keys.insert(key, *Hs, buffer=buffer)
|
|
332
|
+
for H, hashtable in zip(Hs, self.hashtables):
|
|
333
|
+
hashtable.insert(H, key, buffer=buffer)
|
|
334
|
+
else:
|
|
335
|
+
if type(self) is not type(other):
|
|
336
|
+
raise ValueError(f"Cannot merge type MinHashLSH and type {type(other).__name__}.")
|
|
337
|
+
raise ValueError(
|
|
338
|
+
"Cannot merge MinHashLSH with different initialization parameters.")
|
|
339
|
+
|
|
285
340
|
def query(self, minhash) -> List[Hashable]:
|
|
286
341
|
"""
|
|
287
342
|
Giving the MinHash of the query set, retrieve
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from collections import defaultdict
|
|
2
2
|
from typing import Hashable, List
|
|
3
|
+
import numpy as np
|
|
3
4
|
|
|
4
5
|
from datasketch.minhash import MinHash
|
|
5
6
|
|
|
@@ -128,6 +129,30 @@ class MinHashLSHForest(object):
|
|
|
128
129
|
r -= 1
|
|
129
130
|
return list(results)
|
|
130
131
|
|
|
132
|
+
def get_minhash_hashvalues(self, key: Hashable) -> np.ndarray:
|
|
133
|
+
"""
|
|
134
|
+
Returns the hashvalues from the MinHash object that corresponds to the given key in the LSHForest,
|
|
135
|
+
if it exists. This is useful for when we want to reconstruct the original MinHash
|
|
136
|
+
object to manually check the Jaccard Similarity for the top-k results from a query.
|
|
137
|
+
|
|
138
|
+
Args:
|
|
139
|
+
key (Hashable): The key whose MinHash hashvalues we want to retrieve.
|
|
140
|
+
|
|
141
|
+
Returns:
|
|
142
|
+
hashvalues: The hashvalues for the MinHash object corresponding to the given key.
|
|
143
|
+
"""
|
|
144
|
+
byteslist = self.keys.get(key, None)
|
|
145
|
+
if byteslist is None:
|
|
146
|
+
raise KeyError(f"The provided key does not exist in the LSHForest: {key}")
|
|
147
|
+
hashvalue_byte_size = len(byteslist[0])//8
|
|
148
|
+
hashvalues = np.empty(len(byteslist)*hashvalue_byte_size, dtype=np.uint64)
|
|
149
|
+
for index, item in enumerate(byteslist):
|
|
150
|
+
# unswap the bytes, as their representation is flipped during storage
|
|
151
|
+
hv_segment = np.frombuffer(item, dtype=np.uint64).byteswap()
|
|
152
|
+
curr_index = index*hashvalue_byte_size
|
|
153
|
+
hashvalues[curr_index:curr_index+hashvalue_byte_size] = hv_segment
|
|
154
|
+
return hashvalues
|
|
155
|
+
|
|
131
156
|
def _binary_search(self, n, func):
|
|
132
157
|
"""
|
|
133
158
|
https://golang.org/src/sort/search.go?s=2247:2287#L49
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "1.6.5"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: datasketch
|
|
3
|
-
Version: 1.6.
|
|
3
|
+
Version: 1.6.5
|
|
4
4
|
Summary: Probabilistic data structures for processing and searching very large datasets
|
|
5
5
|
Home-page: https://ekzhu.github.io/datasketch
|
|
6
6
|
Author: ekzhu
|
|
@@ -51,11 +51,11 @@ Requires-Dist: motor; python_version >= "3.6" and extra == "experimental-aio"
|
|
|
51
51
|
datasketch: Big Data Looks Small
|
|
52
52
|
================================
|
|
53
53
|
|
|
54
|
-
.. image:: https://
|
|
55
|
-
:target: https://
|
|
54
|
+
.. image:: https://static.pepy.tech/badge/datasketch/month
|
|
55
|
+
:target: https://pepy.tech/project/datasketch
|
|
56
56
|
|
|
57
|
-
.. image:: https://zenodo.org/badge/DOI/10.5281/zenodo.
|
|
58
|
-
:target: https://
|
|
57
|
+
.. image:: https://zenodo.org/badge/DOI/10.5281/zenodo.598238.svg
|
|
58
|
+
:target: https://zenodo.org/doi/10.5281/zenodo.598238
|
|
59
59
|
|
|
60
60
|
datasketch gives you probabilistic data structures that can process and
|
|
61
61
|
search very large amount of data super fast, with little loss of
|
|
@@ -133,6 +133,10 @@ class TestHNSW(unittest.TestCase):
|
|
|
133
133
|
hnsw2 = hnsw.copy()
|
|
134
134
|
self.assertEqual(hnsw, hnsw2)
|
|
135
135
|
|
|
136
|
+
hnsw.remove(0)
|
|
137
|
+
self.assertTrue(0 not in hnsw)
|
|
138
|
+
self.assertTrue(0 in hnsw2)
|
|
139
|
+
|
|
136
140
|
def test_soft_remove_and_pop_and_clean(self):
|
|
137
141
|
data = self._create_random_points()
|
|
138
142
|
hnsw = self._create_index(data)
|
|
@@ -162,7 +166,6 @@ class TestHNSW(unittest.TestCase):
|
|
|
162
166
|
"Potential graph connectivity issue."
|
|
163
167
|
)
|
|
164
168
|
# NOTE: we are not getting the expected number of results.
|
|
165
|
-
# This may be because the graph is not connected anymore.
|
|
166
169
|
# Try hard remove all previous soft removed points.
|
|
167
170
|
hnsw.clean()
|
|
168
171
|
results = hnsw.query(data[i], 10)
|
|
@@ -240,6 +240,117 @@ class TestMinHashLSH(unittest.TestCase):
|
|
|
240
240
|
for table in counts:
|
|
241
241
|
self.assertEqual(sum(table.values()), 2)
|
|
242
242
|
|
|
243
|
+
def test_merge(self):
|
|
244
|
+
lsh1 = MinHashLSH(threshold=0.5, num_perm=16)
|
|
245
|
+
m1 = MinHash(16)
|
|
246
|
+
m1.update("a".encode("utf-8"))
|
|
247
|
+
m2 = MinHash(16)
|
|
248
|
+
m2.update("b".encode("utf-8"))
|
|
249
|
+
lsh1.insert("a",m1)
|
|
250
|
+
lsh1.insert("b",m2)
|
|
251
|
+
|
|
252
|
+
lsh2 = MinHashLSH(threshold=0.5, num_perm=16)
|
|
253
|
+
m3 = MinHash(16)
|
|
254
|
+
m3.update("c".encode("utf-8"))
|
|
255
|
+
m4 = MinHash(16)
|
|
256
|
+
m4.update("d".encode("utf-8"))
|
|
257
|
+
lsh2.insert("c",m1)
|
|
258
|
+
lsh2.insert("d",m2)
|
|
259
|
+
|
|
260
|
+
lsh1.merge(lsh2)
|
|
261
|
+
for t in lsh1.hashtables:
|
|
262
|
+
self.assertTrue(len(t) >= 1)
|
|
263
|
+
items = []
|
|
264
|
+
for H in t:
|
|
265
|
+
items.extend(t[H])
|
|
266
|
+
self.assertTrue("c" in items)
|
|
267
|
+
self.assertTrue("d" in items)
|
|
268
|
+
self.assertTrue("a" in lsh1)
|
|
269
|
+
self.assertTrue("b" in lsh1)
|
|
270
|
+
self.assertTrue("c" in lsh1)
|
|
271
|
+
self.assertTrue("d" in lsh1)
|
|
272
|
+
for i, H in enumerate(lsh1.keys["c"]):
|
|
273
|
+
self.assertTrue("c" in lsh1.hashtables[i][H])
|
|
274
|
+
|
|
275
|
+
self.assertTrue(lsh1.merge, lsh2)
|
|
276
|
+
self.assertRaises(ValueError, lsh1.merge, lsh2, check_overlap=True)
|
|
277
|
+
|
|
278
|
+
m5 = MinHash(16)
|
|
279
|
+
m5.update("e".encode("utf-8"))
|
|
280
|
+
lsh3 = MinHashLSH(threshold=0.5, num_perm=16)
|
|
281
|
+
lsh3.insert("a",m5)
|
|
282
|
+
|
|
283
|
+
self.assertRaises(ValueError, lsh1.merge, lsh3, check_overlap=True)
|
|
284
|
+
|
|
285
|
+
lsh1.merge(lsh3)
|
|
286
|
+
|
|
287
|
+
m6 = MinHash(16)
|
|
288
|
+
m6.update("e".encode("utf-8"))
|
|
289
|
+
lsh4 = MinHashLSH(threshold=0.5, num_perm=16)
|
|
290
|
+
lsh4.insert("a",m6)
|
|
291
|
+
|
|
292
|
+
lsh1.merge(lsh4, check_overlap=False)
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def test_merge_redis(self):
|
|
296
|
+
with patch('redis.Redis', fake_redis) as mock_redis:
|
|
297
|
+
lsh1 = MinHashLSH(threshold=0.5, num_perm=16, storage_config={
|
|
298
|
+
'type': 'redis', 'redis': {'host': 'localhost', 'port': 6379}
|
|
299
|
+
})
|
|
300
|
+
lsh2 = MinHashLSH(threshold=0.5, num_perm=16, storage_config={
|
|
301
|
+
'type': 'redis', 'redis': {'host': 'localhost', 'port': 6379}
|
|
302
|
+
})
|
|
303
|
+
|
|
304
|
+
m1 = MinHash(16)
|
|
305
|
+
m1.update("a".encode("utf8"))
|
|
306
|
+
m2 = MinHash(16)
|
|
307
|
+
m2.update("b".encode("utf8"))
|
|
308
|
+
lsh1.insert("a", m1)
|
|
309
|
+
lsh1.insert("b", m2)
|
|
310
|
+
|
|
311
|
+
m3 = MinHash(16)
|
|
312
|
+
m3.update("c".encode("utf8"))
|
|
313
|
+
m4 = MinHash(16)
|
|
314
|
+
m4.update("d".encode("utf8"))
|
|
315
|
+
lsh2.insert("c", m3)
|
|
316
|
+
lsh2.insert("d", m4)
|
|
317
|
+
|
|
318
|
+
lsh1.merge(lsh2)
|
|
319
|
+
for t in lsh1.hashtables:
|
|
320
|
+
self.assertTrue(len(t) >= 1)
|
|
321
|
+
items = []
|
|
322
|
+
for H in t:
|
|
323
|
+
items.extend(t[H])
|
|
324
|
+
self.assertTrue(pickle.dumps("c") in items)
|
|
325
|
+
self.assertTrue(pickle.dumps("d") in items)
|
|
326
|
+
self.assertTrue("a" in lsh1)
|
|
327
|
+
self.assertTrue("b" in lsh1)
|
|
328
|
+
self.assertTrue("c" in lsh1)
|
|
329
|
+
self.assertTrue("d" in lsh1)
|
|
330
|
+
for i, H in enumerate(lsh1.keys[pickle.dumps("c")]):
|
|
331
|
+
self.assertTrue(pickle.dumps("c") in lsh1.hashtables[i][H])
|
|
332
|
+
|
|
333
|
+
self.assertTrue(lsh1.merge, lsh2)
|
|
334
|
+
self.assertRaises(ValueError, lsh1.merge, lsh2, check_overlap=True)
|
|
335
|
+
|
|
336
|
+
m5 = MinHash(16)
|
|
337
|
+
m5.update("e".encode("utf-8"))
|
|
338
|
+
lsh3 = MinHashLSH(threshold=0.5, num_perm=16, storage_config={
|
|
339
|
+
'type': 'redis', 'redis': {'host': 'localhost', 'port': 6379}
|
|
340
|
+
})
|
|
341
|
+
lsh3.insert("a",m5)
|
|
342
|
+
|
|
343
|
+
self.assertRaises(ValueError, lsh1.merge, lsh3, check_overlap=True)
|
|
344
|
+
|
|
345
|
+
m6 = MinHash(16)
|
|
346
|
+
m6.update("e".encode("utf-8"))
|
|
347
|
+
lsh4 = MinHashLSH(threshold=0.5, num_perm=16, storage_config={
|
|
348
|
+
'type': 'redis', 'redis': {'host': 'localhost', 'port': 6379}
|
|
349
|
+
})
|
|
350
|
+
lsh4.insert("a",m6)
|
|
351
|
+
|
|
352
|
+
lsh1.merge(lsh4, check_overlap=False)
|
|
353
|
+
|
|
243
354
|
|
|
244
355
|
class TestWeightedMinHashLSH(unittest.TestCase):
|
|
245
356
|
|
|
@@ -62,6 +62,18 @@ class TestMinHashLSHForest(unittest.TestCase):
|
|
|
62
62
|
results = forest.query(data[key], 10)
|
|
63
63
|
self.assertIn(key, results)
|
|
64
64
|
|
|
65
|
+
def test_get_minhash_hashvalues(self):
|
|
66
|
+
forest, data = self._setup()
|
|
67
|
+
for key in data:
|
|
68
|
+
minhash_ori = data[key]
|
|
69
|
+
hashvalues = forest.get_minhash_hashvalues(key)
|
|
70
|
+
minhash_retrieved = MinHash(hashvalues=hashvalues)
|
|
71
|
+
retrieved_hashvalues = minhash_retrieved.hashvalues
|
|
72
|
+
self.assertEqual(len(hashvalues), len(retrieved_hashvalues))
|
|
73
|
+
self.assertEqual(minhash_retrieved.jaccard(minhash_ori), 1.0)
|
|
74
|
+
for i in range(len(retrieved_hashvalues)):
|
|
75
|
+
self.assertEqual(hashvalues[i], retrieved_hashvalues[i])
|
|
76
|
+
|
|
65
77
|
def test_pickle(self):
|
|
66
78
|
forest, _ = self._setup()
|
|
67
79
|
forest2 = pickle.loads(pickle.dumps(forest))
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "1.6.3"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|