Understanding Bit Exhaustive Search

TL;DR

Bit exhaustive search is an important algorithm for exhaustive enumeration. By using bit exhaustive search, you can enumerate all subsets of a given set.

However, I found it difficult to intuitively understand what the actual code is doing, so I summarized it in a way that makes sense to me.

Bit Exhaustive Search Algorithm

The bit operations used in bit exhaustive search are as follows. 0b indicates binary notation.

OperationOperatorDescriptionExample
Bitwise AND&Only positions where both are 1 become 10b0101 & 0b0011 = 0b0001
Bit left shift<<Multiply by 20b01011 << 1 = 0b10110
  1. Left-shift a bit n times (2^n) and loop that many times
  2. Inside the loop, iterate n times (for each subset element)
  3. The values at positions where the bit matches form the subset for that iteration

When we organize the values at matching bit positions, we store each i where (bit & (1 << i)) is not 0 into a sequence, resulting in the following (0b denotes binary notation).

When written out, the subsets are neatly enumerated. In other words, (bit & (1 << i)) returns a non-zero value at positions where the bits match.

bitbit(0b)i=0(0b)bit&ii=1(0b)bit&i(0b)i=2(0b)bit&2array
00000000100010001000()
10001000110010001000(0)
20010000100010201000(1)
30011000110010201000(0,1)
40100000100010001004(2)
50101000110010001004(0,2)
60110000100010201004(1,2)
70111000110010201004(0,1,2)

Implementation

nim

import sequtils

let n = 3

# Enumerate all subsets of {0, 1, ..., n-1}
for bit in 0..<(1 shl n):
  var vec = newSeq[int]()
    for i in 0..<n:
      if (bit and (1 shl i)) != 0:
        vec.add(i)
  echo bit, " : ", vec

Create an issue on GitHub about this article

Read Next