Problem
Let be a positive integer and be an all-zero vector. There is a sequence of updates, each of which consists of a pair , increasing by .
Model Description
- ;
- For do
- ;
After witnessing the stream , we call the frequency vector. We are interested in answering the following query.
Rank Query
Input:
Returen: .
We formally define the problem of approximate rank estimation.
Problem. Let be an error parameter. Design an algorithm, such that for a rank query with a given element , the summary will return an approximate rank , such that
-Digest
The naive solution maintain the vector explicitly. Assuming that each entry of can always be stored in one word, the space usage of the is .
Theorem. There exists an algorithm, -digest, that has space usage of words, insertion time and query time of .
We first study a prototype which requires words.
Prototype
The algorithm maintains an array of size to keep all values of . Further, it construct a binary tree on the array, such that
- Each node keeps the sum of frequencies of leaves in its subtree, denote as .
By definition, if is a leaf node, then equals the frequency of the node. For each node , denote and its left and right children respectively.
The figure below is an example with and .
Insertion
To construct the tree, we can initialize a tree built on the domain with all counters equal to . Then on each pair of update , we invoke the following algorithm.
Update:
- root;
- while is not a leaf do
- ;
- if is in the left tree of then
- else
- ;
The update takes time .
Query
The query algorithm is also very simple. Given an input , the algorithm walks down the path from the root to the leaf that contains . The rank of equals the sum of of for the such that
- it is a left child of some node on the path;
- it is not on the path.
Rank Query
Input:
- ;
- ;
- while is not a leaf node do
- if is in left subtree of then
- else
- ;
- return
The query algorithm has time complexity .
-Digest
In this section, we show how to reduce the space usage to .
In the previous section, when the algorithm perform a rank query for , it walks down a path , where is the root of the tree, and is the leaf node that contains .
There are nodes in the path in total. If each node introduces an error of to the estimation of , then the final estimation has error bounded by .
This motivates to associate each node in the tree with an error counter , which is interpreted as the maximum error it can introduce to the rank estimation to a given element . It is initialized with and is guaranteed to be bounded by some threshold (except the leaf node). The threshold is updated along with the insertion of elements into the tree. The algorithm maintains the invariant that
Under this invariant, the estimation of is always bounded by .
Insertion
The insertion algorithm is very simple. When an update arrives, the algorithm walks through the path from the root to the leaf that contains . For each internal node in the path, the amount of additional error it can make is . If this amount is greater than , then we just increase by , and stops the algorithm. Intuitively, we insert into this node. On the other hand, if is less than , then we can still partial insert into , detailed by the pseudo-code below. Doing so increase the rank estimation error only for the element greater than , but not for the elements less than or equal to . Finally, if the invariant of does not hold, we need to update and the tree.
Update:
- ;
- root;
- while and is not a leaf do
- ;
- ;
- ;
- ;
- if is in the left tree of then
- else
- ;
- ;
- if then
- ;
- Compress(root, 0) // update the tree
Query
The query algorithm is the same as the one in the previous section, except that if algorithm encounters a node with , then the algorithm can terminate earlier.
Maintenance
Finally, we discuss how to update the tree in a top-down approach. For a node , we try to increase to , by borrowing weights from the its descendent. There are two possible cases. First, if , then we can set and delete all descendants of . Otherwise, setting incurs a "debt" of that 's descendants need to pay. We assign as much debt as possible to the left child.
Compress
- if then return;
- if v is a leaf node then
- else
- if then
- ;
- ;
- ;
- Compress ;
- Compress ;
- else
- ;
- Set weight of all descendants of to ;
By induction, we can show that .
Space Usage
A node is called "saturated" if . By the invariant of , there are at most "saturated" nodes. By the algorithm, each node in the tree with nonzero , either "saturated", or has a "saturated" parent. However, each "saturated" can have at most two children. Therefore, the total number of nodes with nonzero is bounded by .
Reference
[1] G. Cormode and K. Yi, Small Summaries for Big Data, 1st ed. Cambridge University Press, 2020.