Binary array set
Introduction
The binary array set^{[0]} is a very spaceefficient data structure that supports adding elements and testing membership reasonably quickly. It basically works as a collection of sorted arrays with powerof2 sizes.
Adding one element to a BAS takes amortized Θ(1) time (worstcase Θ(n)), searching/testing for an element takes worstcase Θ((log n)^{2}) time, and removing an element is not supported at all (although the entire set can be cleared easily). Despite the lack of deletion functionality, the data structure is still useful in applications that only add and test but don’t delete – for example, breadthfirst search maintains an evergrowing set of visited nodes that shouldn’t be revisited. To compare time complexities with a popular alternative, a balanced binary search tree takes worstcase Θ(log n) time alike for adding, testing, or removing one element.
The binary array set data structure is succinct because it only uses Θ(log n) extra space for overhead (i.e. the space not used for storing the element values themselves). For modest sizes of n (e.g. over 1000), the amount of overhead space used by a BAS is practically negligible. The data structure that best minimizes overhead space would be the single flat array (which uses only Θ(1) extra space), but it doesn’t support both fast insertion and testing simultaneously. Note that despite the word “array” in the data structure’s name, the set is in fact orderless and can present elements in any order.
By comparison, a balanced BST uses Θ(n) extra space for pointers to all the tree nodes. (Btrees also use Θ(n) extra space, but the scaling constant approaches zero as the degree increases.) This extra space usage for node pointers would be problematic in the situation where the array of raw data values (without pointers and other overheads) barely fits in system memory. This disadvantage of nodebased data structures is especially painful in languages like Java, where each object (i.e. the tree nodes) inherently has tens of bytes of overhead.
Internal details
How this data structure works is that it has a sequence of ⌊log_{2} n⌋ + 1 slots, where the slot at index k (0based counting) is either null or an ascendingsorted array of length 2^{k}. For example, the abstract conceptual set {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13} could be represented as a binary array set in one way like this:
Number of slots: ⌊log_{2} 13⌋ + 1 = 4.
Slot 0: [5]. (length 1)
Slot 1: Null.
Slot 2: [2, 3, 9, 13]. (length 4)
Slot 3: [1, 4, 6, 7, 8, 10, 11, 12]. (length 8)
Note that the size of the set is 13, whose binary representation is 1101_{2}. The binary representation corresponds with which slots are filled. Also note that the union of all the arrays in the binary array set is exactly equal to the set of elements being represented, no more, no less, without duplicates.
To test if a given value is an member of the set, we simply perform standard binary search in each array (in the nonnull slots) and return whether a result was found.
To add a new element, we wrap the element in a new array of length 1. If slot 0 is empty, we simply store the new array there. Otherwise we merge the new array with the array already at slot 0, which forms a new sorted array of length 2. Next we check if slot 1 is empty; we repeat this merge/check process on increasing slot indexes until we find an empty slot.
Source code
 Java (SE 7+)

The class implements the
java.util.Set
interface, so it can be used as a dropin replacement ofHashSet
orTreeSet
as long asremove()
is not called. The iterator is not failfast on concurrent modifications. The maximum set size isInteger.MAX_VALUE
(i.e. 2^{31} − 1).The implemented iterator is not guaranteed to output the elements in sorted order. The benefits of this unsorted iterator are short code, Θ(1) time complexity per element^{[1]}, and Θ(1) storage space at all times. It is indeed possible to design an iterator that yields elements in sorted order; this would require more code and a priority queue, Θ(log n) time per element, and Θ(log n) space.
 Python (compatible with 2 and 3)

This implementation only supports the methods {
add()
,clear()
}, builtin functions {len()
,iter()
}, and operatorin
– so it only supports a fraction of the methods that the builtinset
type has.  C++ (C++11 and above)

The class is modeled after the Java version, supporting the methods
size()
,add()
,contains()
,empty()
, andclear()
. It has far fewer features thanstd::set
.  Rust

This struct and its methods are modeled after
std::
, but has fewer features.collections:: BTreeSet
License: MIT (open source)
Notes
This data structure is described in Introduction to Algorithms 2nd and 3rd editions, chapter 17 “Amortized Analysis”, problem 172 “Making binary search dynamic” (page 473 in the 3rd edition).
The binarymerge behavior of this data structure is like a simplified version of binomial heaps with flat arrays instead of binary tree nodes.
The binary array set can be readily adapted to a map/dictionary if needed.
[0]: The name “binary array set” for this data structure was made up by me. I had asked a question on Stack Overflow to see if there was an existing name for this structure, but the result was negative. Thus it should be impossible to search for existing literature using the BAS name.
[1]: Strictly speaking, the iterator needs worstcase Θ(log n) time, amortized Θ(1) time to process each element, because it needs to skip over empty slots that have no array. However, the number of slots is at most log_{2} n + 1, which is less than 64 on real computers, which is a rather small number. Moreover, it’s possible to use a linked list of arrays (with no empty elements) instead of an array of arrays (with empty slots), which avoids the Θ(log n) time cost of skipping empty slots. Hence we can treat the act of skipping empty slots to be effectively a constanttime operation.