1+ # Licensed to the Apache Software Foundation (ASF) under one
2+ # or more contributor license agreements. See the NOTICE file
3+ # distributed with this work for additional information
4+ # regarding copyright ownership. The ASF licenses this file
5+ # to you under the Apache License, Version 2.0 (the
6+ # "License"); you may not use this file except in compliance
7+ # with the License. You may obtain a copy of the License at
8+ #
9+ # http://www.apache.org/licenses/LICENSE-2.0
10+ #
11+ # Unless required by applicable law or agreed to in writing,
12+ # software distributed under the License is distributed on an
13+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+ # KIND, either express or implied. See the License for the
15+ # specific language governing permissions and limitations
16+ # under the License.
17+
18+ import unittest
19+
20+ from datasketches import theta_sketch , update_theta_sketch
21+ from datasketches import compact_theta_sketch , theta_union
22+ from datasketches import theta_intersection , theta_a_not_b
23+
24+ class ThetaTest (unittest .TestCase ):
25+ def test_theta_basic_example (self ):
26+ k = 12 # 2^k = 4096 rows in the table
27+ n = 1 << 18 # ~256k unique values
28+
29+ # create a sketch and inject some values
30+ sk = self .generate_theta_sketch (n , k )
31+
32+ # we can check that the upper and lower bounds bracket the
33+ # estimate, without needing to know the exact value.
34+ self .assertLessEqual (sk .get_lower_bound (1 ), sk .get_estimate ())
35+ self .assertGreaterEqual (sk .get_upper_bound (1 ), sk .get_estimate ())
36+
37+ # because this sketch is deterministically generated, we can
38+ # also compare against the exact value
39+ self .assertLessEqual (sk .get_lower_bound (1 ), n )
40+ self .assertGreaterEqual (sk .get_upper_bound (1 ), n )
41+
42+ # serialize for storage and reconstruct
43+ sk_bytes = sk .serialize ()
44+ new_sk = update_theta_sketch .deserialize (sk_bytes )
45+
46+ # estimate remains unchanged
47+ self .assertFalse (sk .is_empty ())
48+ self .assertEqual (sk .get_estimate (), new_sk .get_estimate ())
49+
50+ def test_theta_set_operations (self ):
51+ k = 12 # 2^k = 4096 rows in the table
52+ n = 1 << 18 # ~256k unique values
53+
54+ # we'll have 1/4 of the values overlap
55+ offset = int (3 * n / 4 ) # it's a float w/o cast
56+
57+ # create a couple sketches and inject some values
58+ sk1 = self .generate_theta_sketch (n , k )
59+ sk2 = self .generate_theta_sketch (n , k , offset )
60+
61+ # UNIONS
62+ # create a union object
63+ union = theta_union (k )
64+ union .update (sk1 )
65+ union .update (sk2 )
66+
67+ # getting result from union returns a compact_theta_sketch
68+ # compact theta sketches can be used in additional unions
69+ # or set operations but cannot accept further item updates
70+ result = union .get_result ()
71+ self .assertTrue (isinstance (result , compact_theta_sketch ))
72+
73+ # since our process here is deterministic, we have
74+ # checked and know the exact answer is within one
75+ # standard deviation of the estimate
76+ self .assertLessEqual (result .get_lower_bound (1 ), 7 * n / 4 )
77+ self .assertGreaterEqual (result .get_upper_bound (1 ), 7 * n / 4 )
78+
79+
80+ # INTERSECTIONS
81+ # create an intersection object
82+ intersect = theta_intersection () # no lg_k
83+ intersect .update (sk1 )
84+ intersect .update (sk2 )
85+
86+ # has_result() indicates the intersection has been used,
87+ # although the result may be the empty set
88+ self .assertTrue (intersect .has_result ())
89+
90+ # as with unions, the result is a compact sketch
91+ result = intersect .get_result ()
92+ self .assertTrue (isinstance (result , compact_theta_sketch ))
93+
94+ # we know the sets overlap by 1/4
95+ self .assertLessEqual (result .get_lower_bound (1 ), n / 4 )
96+ self .assertGreaterEqual (result .get_upper_bound (1 ), n / 4 )
97+
98+
99+ # A NOT B
100+ # create an a_not_b object
101+ anb = theta_a_not_b () # no lg_k
102+ result = anb .compute (sk1 , sk2 )
103+
104+ # as with unions, the result is a compact sketch
105+ self .assertTrue (isinstance (result , compact_theta_sketch ))
106+
107+ # we know the sets overlap by 1/4, so the remainder is 3/4
108+ self .assertLessEqual (result .get_lower_bound (1 ), 3 * n / 4 )
109+ self .assertGreaterEqual (result .get_upper_bound (1 ), 3 * n / 4 )
110+
111+
112+ def generate_theta_sketch (self , n , k , offset = 0 ):
113+ sk = update_theta_sketch (k )
114+ for i in range (0 , n ):
115+ sk .update (i + offset )
116+ return sk
117+
118+ if __name__ == '__main__' :
119+ unittest .main ()
120+
121+
0 commit comments