While developing a Sudoku solver, when dealing with the question of how to represent the board internally in an efficient way, my brain quickly clicked and my eyes lit up, spotting yet another opportunity to use good old binary mapping.
For us humans, it’s natural to see a grid as a string of characters with “.” for empty and “1”–“9” for digits. But for the machine, directly manipulating these symbols is more expensive than working with integers.
So, using the idea of binary mapping, the solution was to apply a mapping:
ASCII = ".123456789" BIN = "\000\001\002\003\004\005\006\007\010\011"
In the initialize method, the line:
s.tr!(ASCII, BIN) @grid = s.unpack('c*')
transforms "53..7...." into [5, 3, 0, 0, 7, 0, 0, 0, 0]. Each Sudoku cell becomes a simple integer (0 for empty, 1–9 for digits). In the to_s method, the reverse path is applied:
(0..8).collect{|r| @grid[r*9,9].pack('c9')}.join("\n").tr(BIN, ASCII)
Thus, the representation goes back to being human-readable.
This design is not only elegant, it's efficient:
• it allows checking duplicates in rows, columns, and blocks using integer operations or bitmasks; • it reduces string comparisons to simple numeric comparisons; • it guarantees perfect reversibility between internal and external forms.
The idea is broader. Many systems — language parsers, compilers, compression engines — use similar tricks. A compiler doesn’t manipulate the word “while”; it maps it to a numeric token, usually through a tokenizer (I’ll probably explore this more when I’m working on my parser — https://github.com/diasculengadaniel/parser — or on my C interpreter, not uploaded to GitHub yet hahaha!). A compressor like Huffman reduces symbols to short binary codes, speeding up manipulation and storage.
The same reasoning was also applied in a higher-level project: rendering event room layouts in JavaScript. The initial approach might be to represent each cell of the room as an object, for example:
let layout = [ { occupied: false, color: "red", type: "seat" }, { occupied: true, color: "blue", type: "stage" } ];
It works, but it’s heavy. Instead, I represented each cell as an integer with reserved bits:
// Compact version with bitmask // bit 0 = occupied/empty // bits 1–3 = color // bits 4–5 = type let layout = [ 0b00010, // empty, red color 0b10101 // occupied, blue color, stage type ];
With this, it was enough to use bitwise operations (&, >>) to extract information:
function isOccupied(cell) { return (cell & 0b1) !== 0; } function color(cell) { return (cell >> 1) & 0b111; } function type(cell) { return (cell >> 4) & 0b11; }
This model was not only more compact, but also much faster to process.
The principle is always the same: convert human-friendly symbols into machine-compact numbers, while keeping the ability to revert when needed. I hope that, even if only superficially, I was able to show the utility and beauty of this old representation trick — and perhaps encourage its use.
Top comments (0)