Skip to content

Bitmask

A bitmask is a technique that uses individual bits within an integer to represent the presence or absence of certain properties or flags. Each bit position corresponds to a specific flag, and setting or clearing that bit indicates whether the associated flag is active or inactive. This allows for efficient representation and manipulation of multiple boolean values within a single integer.

Why is it important?

Bitmasks offer significant advantages in terms of memory efficiency and speed. They allow you to represent and operate on multiple boolean flags using a single integer, reducing memory footprint and improving performance, especially when dealing with a large number of flags. Bitwise operations (AND, OR, XOR, NOT, left/right shift) are extremely fast at the hardware level.

Core Concepts:

  • Bit: The smallest unit of data, representing either 0 or 1.
  • Bitwise Operators: & (AND), | (OR), ^ (XOR), ~ (NOT), << (left shift), >> (right shift). These operators work directly on the individual bits of integers.
  • Flag: A boolean value representing a specific property or state.
  • Mask: An integer used to select or modify specific bits within another integer.

Use bitmasks when:

  • You need to represent a set of boolean flags efficiently.
  • You need to perform fast operations on these flags (e.g., checking if a flag is set, setting or clearing a flag, combining flag sets).
  • The number of flags is relatively small (typically less than 64, due to the limitations of integer size).
  • You are working with problems that involve subsets, power sets, or combinations.

Don’t use bitmasks when:

  • The number of flags is very large, exceeding the capacity of an integer.
  • The relationships between flags are complex and not easily represented by bitwise operations.
  • Readability and maintainability become significantly compromised by the use of bit manipulation. A more explicit data structure might be preferable.

3. Core Algorithm / Data Structure Template

Section titled “3. Core Algorithm / Data Structure Template”

A general template for using bitmasks often involves these steps:

  1. Define Bit Positions: Assign a unique bit position to each flag.
  2. Set Flags: Use bitwise OR (|) to set a specific flag.
  3. Clear Flags: Use bitwise AND (&) with the complement (~) to clear a specific flag.
  4. Check Flags: Use bitwise AND (&) to check if a specific flag is set.
  5. Combine Masks: Use bitwise OR (|) to combine multiple masks.
def set_flag(mask, flag_position):
"""Sets a flag at the given position in the mask."""
return mask | (1 << flag_position)
def clear_flag(mask, flag_position):
"""Clears a flag at the given position in the mask."""
return mask & ~(1 << flag_position)
def check_flag(mask, flag_position):
"""Checks if a flag is set at the given position."""
return (mask >> flag_position) & 1
# Example usage:
mask = 0 # Initialize an empty mask
mask = set_flag(mask, 2) # Set the 3rd bit (position 2)
mask = set_flag(mask, 0) # Set the 1st bit
print(f"Mask: {bin(mask)}") # Output: Mask: 0b101
print(f"Flag at position 2: {check_flag(mask, 2)}") #Output: 1
mask = clear_flag(mask, 2)
print(f"Mask after clearing: {bin(mask)}") # Output: Mask after clearing: 0b1
class Bitmask {
public static int setFlag(int mask, int flagPosition) {
return mask | (1 << flagPosition);
}
public static int clearFlag(int mask, int flagPosition) {
return mask & ~(1 << flagPosition);
}
public static boolean checkFlag(int mask, int flagPosition) {
return (mask >> flagPosition) & 1 == 1;
}
public static void main(String[] args) {
int mask = 0;
mask = setFlag(mask, 2);
mask = setFlag(mask, 0);
System.out.println("Mask: " + Integer.toBinaryString(mask)); //Output: Mask: 101
System.out.println("Flag at position 2: " + checkFlag(mask, 2)); //Output: true
mask = clearFlag(mask, 2);
System.out.println("Mask after clearing: " + Integer.toBinaryString(mask)); //Output: Mask after clearing: 1
}
}
#include <iostream>
#include <bitset>
int setFlag(int mask, int flagPosition) {
return mask | (1 << flagPosition);
}
int clearFlag(int mask, int flagPosition) {
return mask & ~(1 << flagPosition);
}
bool checkFlag(int mask, int flagPosition) {
return (mask >> flagPosition) & 1;
}
int main() {
int mask = 0;
mask = setFlag(mask, 2);
mask = setFlag(mask, 0);
std::cout << "Mask: " << std::bitset<8>(mask) << std::endl; //Output: Mask: 00000101
std::cout << "Flag at position 2: " << checkFlag(mask, 2) << std::endl; //Output: 1
mask = clearFlag(mask, 2);
std::cout << "Mask after clearing: " << std::bitset<8>(mask) << std::endl; //Output: Mask after clearing: 00000001
return 0;
}
OperationTime ComplexitySpace Complexity
Set FlagO(1)O(1)
Clear FlagO(1)O(1)
Check FlagO(1)O(1)
Combine MasksO(1)O(1)

All operations are constant time because they involve only bitwise operations on integers. Space complexity is also constant as it only requires storage for a single integer.

Pro Tips:

  • Pre-calculate masks: If you’re repeatedly using the same masks, pre-calculate them to avoid redundant calculations.
  • Use enums for readability: Define enums to represent flag positions for improved code readability and maintainability.
  • Leverage bit manipulation libraries: Some languages offer libraries with optimized bit manipulation functions.

Common Pitfalls:

  • Off-by-one errors: Remember that bit positions are typically zero-indexed.
  • Integer overflow: Be mindful of the limitations of integer size, especially when dealing with a large number of flags.
  • Incorrect bitwise operations: Double-check the logic of your bitwise operations to avoid unexpected results.

Description: An Android unlock pattern consists of a sequence of points on a 3x3 grid. Determine if a given pattern is valid, considering that adjacent points cannot be skipped.

High-level Approach:

This problem can be solved using bitmasks to represent the visited points in the unlock pattern. Each bit in the mask corresponds to a point on the 3x3 grid. As the pattern is traversed, the corresponding bits are set. Validity checks can then be performed efficiently using bitwise operations to ensure that adjacent points are visited in sequence and that no points are skipped. The adjacency constraints can be pre-calculated and stored in an adjacency matrix. The algorithm would recursively explore possible paths, using the bitmask to track visited points and the adjacency matrix to ensure valid moves.