Iterating through all possible Boolean values

71 Views Asked by At

I have a function like this:

fun foo(a: Boolean, b: Boolean, c: Boolean) { /*...*/ }

and I wanted to test it with all possible argument values:

for (a in false..true)
    for (b in false..true)
        for (c in false..true) {
            val expected = ...
            assertThat(foo(a, b, c)).isEqualTo(expected)
        }

But unfortunately Kotlin doesn't have a BooleanRange type.

I can do this instead:

for (a in 0..1)
    for (b in 0..1)
        for (c in 0..1) {
            val expected = ...
            assertThat(foo(a == 1, b == 1, c == 1)).isEqualTo(expected)
        }

But this is not at all clear.

I can do this:

for (a in arrayOf(false, true))
    for (b in arrayOf(false, true))
        for (c in arrayOf(false, true)) {
            val expected = ...
            assertThat(foo(a, b, c)).isEqualTo(expected)
        }

But this is still too verbose. Is there a neater way, maybe Kotlin has a function that iterates directly through the two values false and true? I see that it has a BooleanIterator type, but I'm not sure if that's any use here.

4

There are 4 best solutions below

0
k314159 On BEST ANSWER

Thanks for all your answers (upvoted). In the end, I took broot's comment and settled for:

val allBooleans = booleanArrayOf(false, true)
for (a in allBooleans)
    for (b in allBooleans)
        for (c in allBooleans)
            foo(a, b, c)

Although if I wanted it to be shorter, I could have done this:

for ((c, b, a) in (0..7).map { BooleanArray(3) { i and (1 shl it) != 0 } })
    foo(a, b, c)
2
sidgate On

Your solutions would work just fine. Following is an alternative, but might not be fathomable. Using verbose code for clarity is helpful for smaller number of parameters. While using BitSet to get all combinations of boolean values, we can extend to any number of boolean values.

(0 until 8L).map {
  BitSet.valueOf(longArrayOf(it))
}.map {
  assertThat(foo(it[0], it[1], it[2])).isEqualTo(expected)
}

Instead of looping through all the possible values, you can use @ParamerterizedTest and move boolean combination logic out of actual test. You get the test output with all combinations, helps debugging issues with test parameters.

 companion object {
  private val expected = arrayOf(...)
  @JvmStatic fun provideInput() : Stream<Arguments> {
   return (0 until 8).map {
     val b = BitSet.valueOf(longArrayOf(it.toLong()))
     Arguments.of(b[0], b[1], b[2], expected[it])
   }.stream()
  }
 }

@ParameterizedTest
@MethodSource("provideInput")
fun test(a: Boolean, b: Boolean, c: Boolean, expected: Boolean){
  assertThat(foo(a,b,c)).isEqualTo(expected)
}
0
Jan Itor On

You can iterate though all binary numbers from 0 to 111 (three ones by number of arguments) and check corresponding bits:

repeat(8) { n ->
    foo(n and 1 == 0, n and 2 == 0, n and 4 == 0)
}
0
David Soroko On

A low tech approach using pre-calculated Triples may be more readable:

    val allBooleanTriples = listOf(
        Triple(true,  true,  true ),
        Triple(true,  true,  false),
        Triple(true,  false, true ),
        Triple(true,  false, false),
        Triple(false, true,  true ),
        Triple(false, true,  false),
        Triple(false, false, true ),
        Triple(false, false, false),
    )

    allBooleanTriples.map {
        val (a, b, c) = it
        // val expected = ...
        // assertThat(foo(a, b, c)).isEqualTo(expected)
    }

This code is not succinct, but is easy to understand.