sublinear 0.1.0__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.
- sublinear-0.1.0/LICENSE +21 -0
- sublinear-0.1.0/PKG-INFO +67 -0
- sublinear-0.1.0/README.md +55 -0
- sublinear-0.1.0/setup.cfg +4 -0
- sublinear-0.1.0/setup.py +25 -0
- sublinear-0.1.0/sublinear/__init__.py +0 -0
- sublinear-0.1.0/sublinear/count/__init__.py +0 -0
- sublinear-0.1.0/sublinear/count/morris/__init__.py +0 -0
- sublinear-0.1.0/sublinear/count/morris/experiments/__init__.py +0 -0
- sublinear-0.1.0/sublinear/count/morris/experiments/experiment1.py +32 -0
- sublinear-0.1.0/sublinear/count/morris/morris_basic.py +49 -0
- sublinear-0.1.0/sublinear/count/morris/morris_plus.py +53 -0
- sublinear-0.1.0/sublinear/count/morris/morris_plus_plus.py +61 -0
- sublinear-0.1.0/sublinear/distinct_elements/__init__.py +0 -0
- sublinear-0.1.0/sublinear/distinct_elements/bjkst/__init__.py +0 -0
- sublinear-0.1.0/sublinear/distinct_elements/bjkst/bjkst_plus.py +71 -0
- sublinear-0.1.0/sublinear/distinct_elements/bjkst/bjkst_sketch.py +82 -0
- sublinear-0.1.0/sublinear/distinct_elements/bjkst/experiments/__init__.py +0 -0
- sublinear-0.1.0/sublinear/distinct_elements/bjkst/experiments/experiment1.py +21 -0
- sublinear-0.1.0/sublinear/distinct_elements/bjkst/experiments/experiment2.py +22 -0
- sublinear-0.1.0/sublinear/distinct_elements/hyperloglog/__init__.py +0 -0
- sublinear-0.1.0/sublinear/distinct_elements/hyperloglog/experiments/__init__.py +0 -0
- sublinear-0.1.0/sublinear/distinct_elements/hyperloglog/experiments/experiment1.py +20 -0
- sublinear-0.1.0/sublinear/distinct_elements/hyperloglog/hyper_log_log.py +135 -0
- sublinear-0.1.0/sublinear/distinct_elements/sketch_switching/__init__.py +0 -0
- sublinear-0.1.0/sublinear/distinct_elements/sketch_switching/experiments/__init__.py +0 -0
- sublinear-0.1.0/sublinear/distinct_elements/sketch_switching/experiments/experiment1.py +18 -0
- sublinear-0.1.0/sublinear/distinct_elements/sketch_switching/sketch_switch.py +76 -0
- sublinear-0.1.0/sublinear/frequency_table/__init__.py +0 -0
- sublinear-0.1.0/sublinear/frequency_table/cms/__init__.py +0 -0
- sublinear-0.1.0/sublinear/frequency_table/cms/count_min_sketch.py +112 -0
- sublinear-0.1.0/sublinear/frequency_table/cms/experiments/__init__.py +0 -0
- sublinear-0.1.0/sublinear/frequency_table/cms/experiments/cms_text.py +140 -0
- sublinear-0.1.0/sublinear/frequency_table/cms/experiments/experiment1.py +41 -0
- sublinear-0.1.0/sublinear/frequency_table/cms/experiments/experiment2.py +50 -0
- sublinear-0.1.0/sublinear/heavy_hitters/__init__.py +0 -0
- sublinear-0.1.0/sublinear/heavy_hitters/mgs/__init__.py +0 -0
- sublinear-0.1.0/sublinear/heavy_hitters/mgs/experiments/__init__.py +0 -0
- sublinear-0.1.0/sublinear/heavy_hitters/mgs/experiments/experiment1.py +19 -0
- sublinear-0.1.0/sublinear/heavy_hitters/mgs/misra_gries_sketch.py +71 -0
- sublinear-0.1.0/sublinear/second_moment/__init__.py +0 -0
- sublinear-0.1.0/sublinear/second_moment/ams/__init__.py +0 -0
- sublinear-0.1.0/sublinear/second_moment/ams/ams_sketch_basic.py +53 -0
- sublinear-0.1.0/sublinear/second_moment/ams/ams_sketch_plus.py +57 -0
- sublinear-0.1.0/sublinear/second_moment/ams/ams_sketch_plus_plus.py +59 -0
- sublinear-0.1.0/sublinear/second_moment/ams/experiments/__init__.py +0 -0
- sublinear-0.1.0/sublinear/second_moment/ams/experiments/experiment1.py +25 -0
- sublinear-0.1.0/sublinear/second_moment/ams/experiments/experiment2.py +26 -0
- sublinear-0.1.0/sublinear/second_moment/ams/experiments/experiment3.py +27 -0
- sublinear-0.1.0/sublinear/utils/__init__.py +0 -0
- sublinear-0.1.0/sublinear/utils/experiments/__init__.py +0 -0
- sublinear-0.1.0/sublinear/utils/experiments/experiment1.py +12 -0
- sublinear-0.1.0/sublinear/utils/hash_generator.py +118 -0
- sublinear-0.1.0/sublinear/utils/zeros.py +22 -0
- sublinear-0.1.0/sublinear.egg-info/PKG-INFO +67 -0
- sublinear-0.1.0/sublinear.egg-info/SOURCES.txt +57 -0
- sublinear-0.1.0/sublinear.egg-info/dependency_links.txt +1 -0
- sublinear-0.1.0/sublinear.egg-info/requires.txt +2 -0
- sublinear-0.1.0/sublinear.egg-info/top_level.txt +1 -0
sublinear-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 Ivan Nikitovic
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
sublinear-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: sublinear
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python library implementing a subset of streaming algorithms. Includes variations of these algorithms (e.g. adversarially robust), as well as support for multiple data types.
|
|
5
|
+
Home-page: https://github.com/ivannikitovic/sublinear
|
|
6
|
+
Author: Ivan Nikitovic
|
|
7
|
+
Author-email: ivan.bnikitovic@gmail.com
|
|
8
|
+
License: MIT
|
|
9
|
+
Requires-Python: >=3.7
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
|
|
13
|
+
# sublinear
|
|
14
|
+
|
|
15
|
+
Python library implementing a subset of streaming algorithms. Includes variations of these algorithms (e.g. adversarially robust), as well as support for multiple data types.
|
|
16
|
+
|
|
17
|
+
## :zap: Algorithms
|
|
18
|
+
|
|
19
|
+
Here is the list of the currently implemented streaming algorithms.
|
|
20
|
+
|
|
21
|
+
### F0 Estimation (Count of Distinct Elements)
|
|
22
|
+
|
|
23
|
+
- BJKST Sketch (basic, plus, adversarially robust) [1]
|
|
24
|
+
|
|
25
|
+
- HyperLogLog [2]
|
|
26
|
+
|
|
27
|
+
### F1 Estimation (Length of Stream)
|
|
28
|
+
|
|
29
|
+
- Morris (basic, plus, plus plus) [3][4]
|
|
30
|
+
|
|
31
|
+
### F2 Estimation (Estimate of Second Moment)
|
|
32
|
+
|
|
33
|
+
- AMS Sketch (basic, plus, plus plus) [5]
|
|
34
|
+
|
|
35
|
+
### Frequency Table Estimation
|
|
36
|
+
|
|
37
|
+
- Count Min Sketch [6]
|
|
38
|
+
|
|
39
|
+
### Heavy Hitters
|
|
40
|
+
|
|
41
|
+
- Misra-Gries Sketch [7]
|
|
42
|
+
|
|
43
|
+
### Other
|
|
44
|
+
|
|
45
|
+
- K Independent Hash Function [8]
|
|
46
|
+
|
|
47
|
+
## :book: Bibliography
|
|
48
|
+
|
|
49
|
+
[1] Bar-Yossef, Ziv, et al. "Counting distinct elements in a data stream." Randomization and Approximation Techniques in Computer Science. Springer Berlin Heidelberg, 2002.
|
|
50
|
+
|
|
51
|
+
[2] Flajolet, Philippe, et al. "HyperLogLog: the analysis of a near-optimal cardinality estimation algorithm." Conference on Analysis of Algorithms. Springer Berlin Heidelberg, 2007.
|
|
52
|
+
|
|
53
|
+
[3] Morris, R. "Counting large numbers of events in small registers". Communications of the ACM 21, 10, 1978.
|
|
54
|
+
|
|
55
|
+
[4] Flajolet, P. "Approximate Counting: A Detailed Analysis". BIT 25, 1985.
|
|
56
|
+
|
|
57
|
+
[5] Noga Alon, Yossi Matias, Mario Szegedy,
|
|
58
|
+
"The Space Complexity of Approximating the Frequency Moments".
|
|
59
|
+
Journal of Computer and System Sciences,
|
|
60
|
+
Volume 58, Issue 1,
|
|
61
|
+
1999.
|
|
62
|
+
|
|
63
|
+
[6] Cormode, Graham; S. Muthukrishnan. "An Improved Data Stream Summary: The Count-Min Sketch and its Applications". 2005.
|
|
64
|
+
|
|
65
|
+
[7] Misra, J.; Gries, David. "Finding repeated elements". Science of Computer Programming. 1982
|
|
66
|
+
|
|
67
|
+
[8] Wegman, Mark N., et al. "New Hash Functions and Their Use in Authentication and Set Equality". Journal of Computer and System Sciences. 1981.
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# sublinear
|
|
2
|
+
|
|
3
|
+
Python library implementing a subset of streaming algorithms. Includes variations of these algorithms (e.g. adversarially robust), as well as support for multiple data types.
|
|
4
|
+
|
|
5
|
+
## :zap: Algorithms
|
|
6
|
+
|
|
7
|
+
Here is the list of the currently implemented streaming algorithms.
|
|
8
|
+
|
|
9
|
+
### F0 Estimation (Count of Distinct Elements)
|
|
10
|
+
|
|
11
|
+
- BJKST Sketch (basic, plus, adversarially robust) [1]
|
|
12
|
+
|
|
13
|
+
- HyperLogLog [2]
|
|
14
|
+
|
|
15
|
+
### F1 Estimation (Length of Stream)
|
|
16
|
+
|
|
17
|
+
- Morris (basic, plus, plus plus) [3][4]
|
|
18
|
+
|
|
19
|
+
### F2 Estimation (Estimate of Second Moment)
|
|
20
|
+
|
|
21
|
+
- AMS Sketch (basic, plus, plus plus) [5]
|
|
22
|
+
|
|
23
|
+
### Frequency Table Estimation
|
|
24
|
+
|
|
25
|
+
- Count Min Sketch [6]
|
|
26
|
+
|
|
27
|
+
### Heavy Hitters
|
|
28
|
+
|
|
29
|
+
- Misra-Gries Sketch [7]
|
|
30
|
+
|
|
31
|
+
### Other
|
|
32
|
+
|
|
33
|
+
- K Independent Hash Function [8]
|
|
34
|
+
|
|
35
|
+
## :book: Bibliography
|
|
36
|
+
|
|
37
|
+
[1] Bar-Yossef, Ziv, et al. "Counting distinct elements in a data stream." Randomization and Approximation Techniques in Computer Science. Springer Berlin Heidelberg, 2002.
|
|
38
|
+
|
|
39
|
+
[2] Flajolet, Philippe, et al. "HyperLogLog: the analysis of a near-optimal cardinality estimation algorithm." Conference on Analysis of Algorithms. Springer Berlin Heidelberg, 2007.
|
|
40
|
+
|
|
41
|
+
[3] Morris, R. "Counting large numbers of events in small registers". Communications of the ACM 21, 10, 1978.
|
|
42
|
+
|
|
43
|
+
[4] Flajolet, P. "Approximate Counting: A Detailed Analysis". BIT 25, 1985.
|
|
44
|
+
|
|
45
|
+
[5] Noga Alon, Yossi Matias, Mario Szegedy,
|
|
46
|
+
"The Space Complexity of Approximating the Frequency Moments".
|
|
47
|
+
Journal of Computer and System Sciences,
|
|
48
|
+
Volume 58, Issue 1,
|
|
49
|
+
1999.
|
|
50
|
+
|
|
51
|
+
[6] Cormode, Graham; S. Muthukrishnan. "An Improved Data Stream Summary: The Count-Min Sketch and its Applications". 2005.
|
|
52
|
+
|
|
53
|
+
[7] Misra, J.; Gries, David. "Finding repeated elements". Science of Computer Programming. 1982
|
|
54
|
+
|
|
55
|
+
[8] Wegman, Mark N., et al. "New Hash Functions and Their Use in Authentication and Set Equality". Journal of Computer and System Sciences. 1981.
|
sublinear-0.1.0/setup.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from setuptools import setup, find_packages
|
|
2
|
+
|
|
3
|
+
# read the contents of your README file
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
this_directory = Path(__file__).parent
|
|
6
|
+
long_description = (this_directory / "README.md").read_text()
|
|
7
|
+
|
|
8
|
+
setup(
|
|
9
|
+
name="sublinear",
|
|
10
|
+
version="0.1.0",
|
|
11
|
+
description="Python library implementing a subset of streaming algorithms. Includes variations of these algorithms (e.g. adversarially robust), as well as support for multiple data types.",
|
|
12
|
+
long_description_content_type='text/markdown',
|
|
13
|
+
long_description=long_description,
|
|
14
|
+
license="MIT",
|
|
15
|
+
author="Ivan Nikitovic",
|
|
16
|
+
author_email="ivan.bnikitovic@gmail.com",
|
|
17
|
+
url="https://github.com/ivannikitovic/sublinear",
|
|
18
|
+
packages=find_packages(),
|
|
19
|
+
python_requires=">=3.7",
|
|
20
|
+
include_package_data=True,
|
|
21
|
+
install_requires=[
|
|
22
|
+
'numpy',
|
|
23
|
+
'matplotlib',
|
|
24
|
+
],
|
|
25
|
+
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
from ..morris_plus_plus import MorrisPlusPlusCounter
|
|
2
|
+
from ..morris_plus import MorrisPlusCounter
|
|
3
|
+
from ..morris_basic import MorrisCounter
|
|
4
|
+
|
|
5
|
+
def test_morris():
|
|
6
|
+
morris = MorrisCounter()
|
|
7
|
+
test_stream = [i for i in range(100)]
|
|
8
|
+
|
|
9
|
+
morris.process_stream(test_stream)
|
|
10
|
+
estimate = morris.estimate_count()
|
|
11
|
+
|
|
12
|
+
print(f"Morris: Estimated count of elements: {estimate}")
|
|
13
|
+
|
|
14
|
+
s = 10
|
|
15
|
+
morris_plus = MorrisPlusCounter(s)
|
|
16
|
+
|
|
17
|
+
morris_plus.process_stream(test_stream)
|
|
18
|
+
estimate = morris_plus.estimate_count()
|
|
19
|
+
|
|
20
|
+
print(f"Morris+: Estimated count of elements: {estimate}")
|
|
21
|
+
|
|
22
|
+
epsilon = 0.1
|
|
23
|
+
delta = 0.1
|
|
24
|
+
morris_plus_plus = MorrisPlusPlusCounter(epsilon, delta)
|
|
25
|
+
|
|
26
|
+
morris_plus_plus.process_stream(test_stream)
|
|
27
|
+
estimate = morris_plus_plus.estimate_count()
|
|
28
|
+
|
|
29
|
+
print(f"Morris++: Estimated count of elements: {estimate}")
|
|
30
|
+
|
|
31
|
+
print(f"Actual count of elements: {len(test_stream)}")
|
|
32
|
+
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import random
|
|
2
|
+
from typing import List
|
|
3
|
+
|
|
4
|
+
class MorrisCounter:
|
|
5
|
+
"""
|
|
6
|
+
Morris Counter is a probabilistic counter for counting elements in a stream
|
|
7
|
+
using logarithmic space. The algorithm was proposed by Robert Morris in 1978.
|
|
8
|
+
|
|
9
|
+
Attributes
|
|
10
|
+
----------
|
|
11
|
+
c : int
|
|
12
|
+
Counter value.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
def __init__(self):
|
|
16
|
+
"""
|
|
17
|
+
Initializes Morris Counter.
|
|
18
|
+
"""
|
|
19
|
+
self.c = 0
|
|
20
|
+
|
|
21
|
+
def process_stream(self, stream: List[int]) -> None:
|
|
22
|
+
"""
|
|
23
|
+
Processes the input data stream with the Morris Counter algorithm.
|
|
24
|
+
|
|
25
|
+
Parameters
|
|
26
|
+
----------
|
|
27
|
+
stream : List[int]
|
|
28
|
+
Data stream represented as a list of integers.
|
|
29
|
+
"""
|
|
30
|
+
for item in stream:
|
|
31
|
+
self.process()
|
|
32
|
+
|
|
33
|
+
def process(self) -> None:
|
|
34
|
+
"""
|
|
35
|
+
Updates the counter value with probability 1/(2^c).
|
|
36
|
+
"""
|
|
37
|
+
if random.random() < 1 / (2 ** self.c):
|
|
38
|
+
self.c += 1
|
|
39
|
+
|
|
40
|
+
def estimate_count(self) -> int:
|
|
41
|
+
"""
|
|
42
|
+
Estimates the count of the elements in the data stream.
|
|
43
|
+
|
|
44
|
+
Returns
|
|
45
|
+
-------
|
|
46
|
+
int
|
|
47
|
+
Estimated count of elements in the data stream.
|
|
48
|
+
"""
|
|
49
|
+
return (2 ** self.c) - 1
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
from typing import List
|
|
2
|
+
from .morris_basic import MorrisCounter
|
|
3
|
+
|
|
4
|
+
class MorrisPlusCounter:
|
|
5
|
+
"""
|
|
6
|
+
Morris Plus Counter is an extension of the Morris Counter that runs multiple
|
|
7
|
+
Morris Counters in parallel and outputs the average of the estimates. This
|
|
8
|
+
improves the accuracy of the count estimate.
|
|
9
|
+
|
|
10
|
+
Attributes
|
|
11
|
+
----------
|
|
12
|
+
s : int
|
|
13
|
+
Number of Morris Counters.
|
|
14
|
+
counters : List[MorrisCounter]
|
|
15
|
+
List of Morris Counters.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def __init__(self, s: int):
|
|
19
|
+
"""
|
|
20
|
+
Initializes Morris Plus Counter.
|
|
21
|
+
|
|
22
|
+
Parameters
|
|
23
|
+
----------
|
|
24
|
+
s : int
|
|
25
|
+
Number of Morris Counters.
|
|
26
|
+
"""
|
|
27
|
+
self.s = s
|
|
28
|
+
self.counters = [MorrisCounter() for _ in range(s)]
|
|
29
|
+
|
|
30
|
+
def process_stream(self, stream: List[int]) -> None:
|
|
31
|
+
"""
|
|
32
|
+
Processes the input data stream with the Morris Plus Counter algorithm.
|
|
33
|
+
|
|
34
|
+
Parameters
|
|
35
|
+
----------
|
|
36
|
+
stream : List[int]
|
|
37
|
+
Data stream represented as a list of integers.
|
|
38
|
+
"""
|
|
39
|
+
for item in stream:
|
|
40
|
+
for counter in self.counters:
|
|
41
|
+
counter.process()
|
|
42
|
+
|
|
43
|
+
def estimate_count(self) -> int:
|
|
44
|
+
"""
|
|
45
|
+
Estimates the count of the elements in the data stream.
|
|
46
|
+
|
|
47
|
+
Returns
|
|
48
|
+
-------
|
|
49
|
+
int
|
|
50
|
+
Estimated count of elements in the data stream.
|
|
51
|
+
"""
|
|
52
|
+
estimates = [counter.estimate_count() for counter in self.counters]
|
|
53
|
+
return int(sum(estimates) / self.s)
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import math
|
|
2
|
+
import numpy as np
|
|
3
|
+
from typing import List
|
|
4
|
+
|
|
5
|
+
from .morris_plus import MorrisPlusCounter
|
|
6
|
+
|
|
7
|
+
class MorrisPlusPlusCounter:
|
|
8
|
+
"""
|
|
9
|
+
Morris Plus Plus Counter is an extension of the Morris Plus Counter that
|
|
10
|
+
runs multiple Morris Plus Counters in parallel and outputs the median of
|
|
11
|
+
the estimates. This improves the accuracy of the count estimate further.
|
|
12
|
+
|
|
13
|
+
Attributes
|
|
14
|
+
----------
|
|
15
|
+
s : int
|
|
16
|
+
Number of Morris Counters in each Morris Plus Counter.
|
|
17
|
+
t : int
|
|
18
|
+
Number of Morris Plus Counters.
|
|
19
|
+
counters : List[MorrisPlusCounter]
|
|
20
|
+
List of Morris Plus Counters.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def __init__(self, epsilon: float, delta: float):
|
|
24
|
+
"""
|
|
25
|
+
Initializes Morris Plus Plus Counter.
|
|
26
|
+
|
|
27
|
+
Parameters
|
|
28
|
+
----------
|
|
29
|
+
epsilon : float
|
|
30
|
+
Error factor.
|
|
31
|
+
delta : float
|
|
32
|
+
Confidence factor.
|
|
33
|
+
"""
|
|
34
|
+
self.s = int(math.ceil(1 / (epsilon ** 2)))
|
|
35
|
+
self.t = int(math.ceil(math.log(1 / delta)))
|
|
36
|
+
self.counters = [MorrisPlusCounter(self.s) for _ in range(self.t)]
|
|
37
|
+
|
|
38
|
+
def process_stream(self, stream: List[int]) -> None:
|
|
39
|
+
"""
|
|
40
|
+
Processes the input data stream with the Morris Plus Plus Counter algorithm.
|
|
41
|
+
|
|
42
|
+
Parameters
|
|
43
|
+
----------
|
|
44
|
+
stream : List[int]
|
|
45
|
+
Data stream represented as a list of integers.
|
|
46
|
+
"""
|
|
47
|
+
for item in stream:
|
|
48
|
+
for counter in self.counters:
|
|
49
|
+
counter.process_stream([item])
|
|
50
|
+
|
|
51
|
+
def estimate_count(self) -> int:
|
|
52
|
+
"""
|
|
53
|
+
Estimates the count of the elements in the data stream.
|
|
54
|
+
|
|
55
|
+
Returns
|
|
56
|
+
-------
|
|
57
|
+
int
|
|
58
|
+
Estimated count of elements in the data stream.
|
|
59
|
+
"""
|
|
60
|
+
estimates = [counter.estimate_count() for counter in self.counters]
|
|
61
|
+
return int(np.median(estimates))
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
from typing import List
|
|
2
|
+
from .bjkst_sketch import BJKSTSketch
|
|
3
|
+
from math import ceil, log
|
|
4
|
+
import statistics
|
|
5
|
+
|
|
6
|
+
class BJKSTSketchPlus:
|
|
7
|
+
"""
|
|
8
|
+
The BJKST sketch with epsilon-delta approximation is a probabilistic data structure
|
|
9
|
+
that serves as a distinct element counter in a stream of data.
|
|
10
|
+
|
|
11
|
+
This implementation runs multiple instances of the BJKST algorithm and returns the
|
|
12
|
+
median of the estimates to improve the accuracy of the estimation.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
def __init__(self, n: int, epsilon: float, delta: float, b: float = 10, max_input_length: int = 64, c: float = 576):
|
|
16
|
+
"""
|
|
17
|
+
Initializes BJKSTSketchPlus class.
|
|
18
|
+
|
|
19
|
+
Parameters
|
|
20
|
+
----------
|
|
21
|
+
Initializes BJKST Sketch class.
|
|
22
|
+
|
|
23
|
+
Parameters
|
|
24
|
+
----------
|
|
25
|
+
n: int
|
|
26
|
+
Size of the input universe.
|
|
27
|
+
|
|
28
|
+
b: float
|
|
29
|
+
Constant factor for controlling the hash function range of g.
|
|
30
|
+
|
|
31
|
+
c: float
|
|
32
|
+
Constant factor for size of set B.
|
|
33
|
+
|
|
34
|
+
epsilon: float
|
|
35
|
+
Approximation error factor.
|
|
36
|
+
|
|
37
|
+
delta: float
|
|
38
|
+
Confidence level.
|
|
39
|
+
|
|
40
|
+
max_input_length: int, optional
|
|
41
|
+
Maximum length of the input elements, used for generating hash functions.
|
|
42
|
+
"""
|
|
43
|
+
self.instances = []
|
|
44
|
+
self.num_instances = ceil(log(1 / delta))
|
|
45
|
+
for _ in range(self.num_instances):
|
|
46
|
+
self.instances.append(BJKSTSketch(n, epsilon, b, max_input_length=max_input_length, c=c))
|
|
47
|
+
|
|
48
|
+
def process_stream(self, stream: List[object]) -> None:
|
|
49
|
+
"""
|
|
50
|
+
Executes the BJKST algorithm with epsilon-delta approximation on a stream (list) of elements.
|
|
51
|
+
|
|
52
|
+
Parameters
|
|
53
|
+
----------
|
|
54
|
+
stream: List[object]
|
|
55
|
+
Stream of objects represented as a list.
|
|
56
|
+
"""
|
|
57
|
+
for instance in self.instances:
|
|
58
|
+
instance.process_stream(stream)
|
|
59
|
+
|
|
60
|
+
def estimate_distinct_elements(self) -> int:
|
|
61
|
+
"""
|
|
62
|
+
Returns the estimated number of distinct elements in the stream using the median of
|
|
63
|
+
the results from the multiple instances.
|
|
64
|
+
|
|
65
|
+
Returns
|
|
66
|
+
-------
|
|
67
|
+
int
|
|
68
|
+
Estimated number of distinct elements.
|
|
69
|
+
"""
|
|
70
|
+
estimates = [instance.estimate_distinct_elements() for instance in self.instances]
|
|
71
|
+
return int(statistics.median(estimates))
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import math
|
|
2
|
+
from typing import List
|
|
3
|
+
from sublinear.utils.hash_generator import HashGenerator
|
|
4
|
+
from sublinear.utils.zeros import Zeros
|
|
5
|
+
|
|
6
|
+
class BJKSTSketch:
|
|
7
|
+
"""
|
|
8
|
+
The BJKST sketch is a probabilistic data structure
|
|
9
|
+
that serves as a distinct element counter in a stream of data.
|
|
10
|
+
|
|
11
|
+
It uses two hash functions and a set to estimate the number of distinct elements.
|
|
12
|
+
It operates in sub-linear space, at the expense of some approximation error.
|
|
13
|
+
|
|
14
|
+
The BJKST algorithm was invented by Bar-Yossef, Jayram, Kumar, Sivakumar, and Trevisan.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
def __init__(self, n: int, epsilon: float, b: float = 10, max_input_length: int = 64, c: float = 576):
|
|
18
|
+
"""
|
|
19
|
+
Initializes BJKST Sketch class.
|
|
20
|
+
|
|
21
|
+
Parameters
|
|
22
|
+
----------
|
|
23
|
+
n: int
|
|
24
|
+
Size of the input universe.
|
|
25
|
+
|
|
26
|
+
b: float
|
|
27
|
+
Constant factor for controlling the hash function range of g.
|
|
28
|
+
|
|
29
|
+
c: float
|
|
30
|
+
Constant factor for size of set B.
|
|
31
|
+
|
|
32
|
+
epsilon: float
|
|
33
|
+
Approximation error factor.
|
|
34
|
+
|
|
35
|
+
max_input_length: int, optional
|
|
36
|
+
Maximum length of the input elements, used for generating hash functions.
|
|
37
|
+
"""
|
|
38
|
+
self.n = n
|
|
39
|
+
self.b = b
|
|
40
|
+
self.c = c
|
|
41
|
+
self.epsilon = epsilon
|
|
42
|
+
|
|
43
|
+
self.z = 0
|
|
44
|
+
self.B = set()
|
|
45
|
+
|
|
46
|
+
self.h_generator = HashGenerator(self.n, max_input_length, m=n)
|
|
47
|
+
self.h = self.h_generator.generate_hash_function()
|
|
48
|
+
|
|
49
|
+
g_size = int(b * n * (epsilon ** (-4)) * (math.log(n) ** 2))
|
|
50
|
+
self.g_generator = HashGenerator(self.n, max_input_length, m=g_size)
|
|
51
|
+
self.g = self.g_generator.generate_hash_function()
|
|
52
|
+
|
|
53
|
+
def process_stream(self, stream: List[object]) -> None:
|
|
54
|
+
"""
|
|
55
|
+
Executes the BJKST algorithm on a stream (list) of elements.
|
|
56
|
+
|
|
57
|
+
Parameters
|
|
58
|
+
----------
|
|
59
|
+
stream: List[object]
|
|
60
|
+
Stream of objects represented as a list.
|
|
61
|
+
"""
|
|
62
|
+
for token in stream:
|
|
63
|
+
h_j = self.h_generator.hash_string(token)
|
|
64
|
+
zeros_hj = Zeros.zeros(h_j)
|
|
65
|
+
if zeros_hj >= self.z:
|
|
66
|
+
g_j = self.g_generator.hash_string(token)
|
|
67
|
+
self.B.add((g_j, zeros_hj))
|
|
68
|
+
|
|
69
|
+
while len(self.B) >= self.c / (self.epsilon ** 2):
|
|
70
|
+
self.z += 1
|
|
71
|
+
self.B = {(a, b) for (a, b) in self.B if b >= self.z}
|
|
72
|
+
|
|
73
|
+
def estimate_distinct_elements(self) -> int:
|
|
74
|
+
"""
|
|
75
|
+
Returns the estimated number of distinct elements in the stream.
|
|
76
|
+
|
|
77
|
+
Returns
|
|
78
|
+
-------
|
|
79
|
+
int
|
|
80
|
+
Estimated number of distinct elements.
|
|
81
|
+
"""
|
|
82
|
+
return len(self.B) * (2 ** self.z)
|
|
File without changes
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Experiment 1 (Test)
|
|
2
|
+
|
|
3
|
+
from ..bjkst_sketch import BJKSTSketch
|
|
4
|
+
|
|
5
|
+
def test_bjkst():
|
|
6
|
+
|
|
7
|
+
n = 128
|
|
8
|
+
epsilon = 0.1
|
|
9
|
+
|
|
10
|
+
bjkst = BJKSTSketch(n, epsilon)
|
|
11
|
+
|
|
12
|
+
test1 = []
|
|
13
|
+
|
|
14
|
+
for i in range(100):
|
|
15
|
+
test1 += i * [f"word{i}"]
|
|
16
|
+
|
|
17
|
+
bjkst.process_stream(test1)
|
|
18
|
+
|
|
19
|
+
estimate = bjkst.estimate_distinct_elements()
|
|
20
|
+
|
|
21
|
+
print("Estimated number of distinct elements:", estimate)
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Experiment 1 (Test)
|
|
2
|
+
|
|
3
|
+
from ..bjkst_plus import BJKSTSketchPlus
|
|
4
|
+
|
|
5
|
+
def test_bjkst_plus():
|
|
6
|
+
|
|
7
|
+
n = 128
|
|
8
|
+
epsilon = 0.1
|
|
9
|
+
delta = 0.1
|
|
10
|
+
|
|
11
|
+
bjkst_plus = BJKSTSketchPlus(n, epsilon, delta)
|
|
12
|
+
|
|
13
|
+
test1 = []
|
|
14
|
+
|
|
15
|
+
for i in range(100):
|
|
16
|
+
test1 += i * [f"word{i}"]
|
|
17
|
+
|
|
18
|
+
bjkst_plus.process_stream(test1)
|
|
19
|
+
|
|
20
|
+
estimate = bjkst_plus.estimate_distinct_elements()
|
|
21
|
+
|
|
22
|
+
print("Estimated number of distinct elements:", estimate)
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from ..hyper_log_log import HyperLogLog
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
def test_hll():
|
|
5
|
+
hll = HyperLogLog(16) # Initialize HyperLogLog with 16-bit bucket address
|
|
6
|
+
|
|
7
|
+
num_elements = 100000
|
|
8
|
+
|
|
9
|
+
test2 = []
|
|
10
|
+
for i in range(num_elements):
|
|
11
|
+
test2 += [f"word{i}"]
|
|
12
|
+
|
|
13
|
+
for item in test2:
|
|
14
|
+
hll.add(item)
|
|
15
|
+
|
|
16
|
+
cardinality = hll.estimate_cardinality()
|
|
17
|
+
|
|
18
|
+
print(f"Estimated number of distinct elements: {cardinality}")
|
|
19
|
+
|
|
20
|
+
print(f"Actual number of distinct elements: {num_elements}")
|