Generate All Boolean Array Combinations
While writing Go tests for goxplorer, I wanted to test all boolean flags combinations without having to cascade for loops.
This first method came to mind:
package main
import (
"fmt"
)
func stob(bf []bool, s string) {
for i, b := range s {
if b == '0' {
bf[i] = false
} else {
bf[i] = true
}
}
}
func main() {
bf := []bool{false, false, false, false, false}
for i := 0; i < 32; i++ {
b := fmt.Sprintf("%b", i)
stob(bf, b)
fmt.Println(bf)
}
}
Because the fastest way of generating every possible combination of 5 bits is to count from 0 to 31, i.e. 2^5, i.e. 32. The second trick here is to use fmt.Sprintf("%b") to generate a string representing the binary value.
It works, but I found the idea of using strings too heavy for a “binary” task.
A friend came to me with a barbaric bits field solution I found way too smart / complicated :), so I thought about another option but also using bits field, and thought about the following: how to tell if a field is a 1? One of the many options is the or binary operator; indeed, if a field is 0 and is “or’ed” with a 1, its value will become 1, if the initial value is 1, it will not change. Remember or truth table?
| a | b | result |
|---|---|---|
| 0 | 0 | 0 |
| 0 | 1 | 1 |
| 1 | 0 | 1 |
| 1 | 1 | 1 |
Here’s the equivalent code:
package main
import (
"fmt"
)
func btoflag(bf []bool, n int) {
for i, _ := range bf {
if n|(1<<i) > n {
bf[i] = false
} else {
bf[i] = true
}
}
}
func main() {
bf := []bool{false, false, false, false, false}
for i := 0; i < 32; i++ {
btoflag(bf, i)
fmt.Println(bf)
}
}
Yet again, I tend to avoid else as much as possible to respect Go’s “happy ending” best practices, and as is often happens, this btoflag()’s is useless, simply declare / initialize bf at each occurrence of the loop:
func btoflag(bf []bool, n int) {
for i, _ := range bf {
if n|(1<<i) > n {
bf[i] = false
}
}
}
func main() {
for i := 0; i < 32; i++ {
bf := []bool{true, true, true, true, true}
btoflag(bf, i)
fmt.Println(bf)
}
}
There you have it! What do you think? Have you got something better in mind?
Update turns out some of you found the n|(1<<i) > n method kind of gibberish and harder to understand than a &. So here’s the consequent change:
func btoflag(bf []bool, n int) {
for i, _ := range bf {
if n&(1<<i) != 0 {
bf[i] = true
}
}
}
func main() {
for i := 0; i < 32; i++ {
bf := []bool{false, false, false, false, false}
btoflag(bf, i)
fmt.Println(bf)
}
}
In this method, only if both bits are true then the field should be true. For example:
5 in base 10 (decimal) is 00101 in base 2 (binary) with 5 bits
4(10) is 00100
| op | 16 | 8 | 4 | 2 | 1 |
|---|---|---|---|---|---|
| 0 | 0 | 1 | 0 | 1 | |
| & | 0 | 0 | 1 | 0 | 0 |
| = | 0 | 0 | 1 | 0 | 0 |
When 5 is passed to btoflag(), 4 and 1 will match, but no other power of 2, thus, these two bits are set to true.