Generate All Boolean Array Combinations
by Emile `iMil' Heitor - 2020-05-04
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 string
s 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
.