How to safely rewrite boolean conditions?
Tags: algorithms, javascript, programming, boolean
Published: 2022-11-05 15:28:27

Hey guys, today we gonna practice with the **"interesting"** algorithm. It was my improvisation at work when I once again tried to rewrite some logical conditions. Let's imagine that you have some statement with logical operators like: `!(foo || (bar && !baz))` and you have a task to somehow rewrite it.

So, it would be nice to have tests for this case, but you might haven't some. Your brain starts thinking: "how to automate it" and "how to check all possible boolean values after changes". 🤔 Let's try to write the function to check the original condition variant with the new possible one. Our function can get original and new conditions in the form of functions in which we can pass boolean combinations to check the result on equality.


**Rules for the code (as usual):**

1. Choose the right names for variables
2. Choose the right loop statements: for, while, forEach, reduce etc.
3. Avoid excess conditions and comparisons for edge cases
4. Escape the side effects in your algorithms function, because 
very often you need to do mutations to decrease space or time complexity

```js
const testBoolConditions = (sourceFn, targetFn) => {
  /**
   * n^r - Permutation with Repetition Formula
   * n - is alphabet length equals 2 (for false and true)
   * r - is number of arguments
   */
  const combinationsCount = 2 ** sourceFn.length;

  let counter = 0;
  while (counter < combinationsCount) {
    /**
     * @example
     * n - 2
     * r - 3
     * combinationsCount = 8
     * in binary system we will args:
     * 0 - [0,0,0]
     * 1 - [0,0,1]
     * 2 - [0,1,0]
     * ...
     * 7 - [1,1,1]
     *
     * where boolArgs will be:
     * 0 - [false,false,false]
     * 1 - [false,false,true]
     * ...
     * 7 - [true,true,true]
     */
    const args = counter.toString(2).padStart(sourceFn.length, '0').split('');
    const boolArgs = args.map(arg => Boolean(Number(arg)));
    const sourceFnResult = sourceFn(...boolArgs);
    const targetFnResult = targetFn(...boolArgs);

    if (sourceFnResult !== targetFnResult) {
      throw new Error(
        `Failed condition for [${boolArgs}] with results sourceFn: ${sourceFnResult}, targetFn: ${targetFnResult}`,
      );
    }

    counter++;
  }

  return 'functions have the same conditions';
};
```

For the `sourceFn` and `targetFn` we are generating all possible variants of arguments via the "Permutation with Repetition Formula" - `n^r` (`n` to the `r` power).

`n` - is alphabet length (in our case, it is 2 for `false` and `true`)
`r` - is number of arguments (it depends on how many arguments in our condition)

**Let's look at how to use it:**

Convert our condition `!(foo || (bar && !baz))` to function:

```js
(foo, bar, baz) => {
  return !(foo || (bar && !baz));
}
```

Rewrite the original condition somehow, for instance like this `foo || (bar && !baz) ? false : true;`

Convert this to a function:

```js
(foo, bar, baz) => {
  return foo || (bar && !baz) ? false : true;
}
```

And now we are ready to use our `testBoolConditions` 

```js
console.assert(
  testBoolConditions(
    (foo, bar, baz) => {
      return !(foo || (bar && !baz));
    },
    (foo, bar, baz) => {
      return foo || (bar && !baz) ? false : true;
    },
  ),
);
```

That's it, if we get different results for `sourceFn` and `targetFn` for some argument combinations it will throw an error. It might be useful for rewriting some complex conditions.

Let me know your thoughts in the comment section below! 😊