写在前面

由于这两天测试机房停电,这两天无事可做,闲着干嘛呢?想着要不试着再写篇文章来唠唠吧。于是就有了今天这篇。

题选由来

在上个月,社区里有人提了这么一个问题——这JS判断怎么简化,把我给看晕了

const isDel = (op, o) => {
    let fal = false;
    if (op.is_show_lock && op.is_show_sf && op.is_show_bz) {
        if (o.is_lock && o.is_sf && o.is_bz) {
            fal = false;
        } else {
            fal = true;
        }
    } else if (op.is_show_lock && op.is_show_sf) {
        if (o.is_lock && o.is_sf) {
            fal = false;
        } else {
            fal = true;
        }
    } else if (op.is_show_lock && op.is_show_bz) {
        if (o.is_lock && o.is_bz) {
            fal = false;
        } else {
            fal = true;
        }
    } else if (op.is_show_sf && op.is_show_bz) {
        if (o.is_sf && o.is_bz) {
            fal = false;
        } else {
            fal = true;
        }
    } else if (op.is_show_lock) {
        if (o.is_lock) {
            fal = false;
        } else {
            fal = true;
        }
    } else if (op.is_show_sf) {
        if (o.is_sf) {
            fal = false;
        } else {
            fal = true;
        }
    } else if (op.is_show_bz) {
        if (o.is_bz) {
            fal = false;
        } else {
            fal = true;
        }
    }
    return fal;
};

可以看出这段代码主要的逻辑就是根据几个字段来判断出最终的结果输出布尔值。最终笔者给出的解答是——

const isRemove = (op, o) => (op.is_show_lock && !o.is_lock) || (op.is_show_sf && !o.is_sf) || (op.is_show_bz && !o.is_bz)

那么我是如何简化呢?当看到上面一长串的逻辑判断的时候,显而易见的就是存在太多字段的重复使用,笔者第一感觉就是应该可以用布尔代数来简化,这里就涉及到两个知识点了,的法则以及,关于这两个的理论知识就不做赘述,可点击链接查看或者使用习惯的搜索引擎进行搜索查阅相关资料。我们直接进入正题。

简化

首先,我们再回顾下题主的代码可以看出满足true的条件较少【ps:因为false不仅包含了if中的条件,还包括了不满足else的情况,这也是笔者第一次回答有误时的bug之处】,那么就列出所有满足true的布尔表达式如下:

op.is_show_lock && op.is_show_sf && op.is_show_bz && !(o.is_lock && o.is_sf && o.is_bz) ||
op.is_show_lock && op.is_show_sf && !(o.is_lock && o.is_sf) ||
op.is_show_lock && op.is_show_bz && !(o.is_lock && o.is_bz) ||
op.is_show_sf && op.is_show_bz && !(o.is_sf && o.is_bz) ||
op.is_show_lock && !o.is_lock ||
op.is_show_sf && !o.is_sf ||
op.is_show_bz && !o.is_bz

字段不过是一种表示,所以这里可以用字母来代替以便于简化表达式。假设:

op.is_show_lock -> A
op.is_show_sf -> B
op.is_show_bz -> C
o.is_lock -> a
o.is_show_lock -> b
o.is_show_lock -> c

又因为要表示为数学语言,在数学中:

&& -> ·
|| -> +
! -> ′

于是,代码则表示为如下布尔代数式:

A · B · C · (a · b · c)′ +
A · B · (a · b)′ +
A · C · (a · c)′ +
B · C · (b · c)′ +
A · a′ +
B · b′ +
C · c′

接下来就是对此布尔代数式套用布尔运算律对其简化得出最终最优的布尔代数。

步骤

  1. 德·摩根律(反演律):(a+b)′=a′·b′,(a·b)′=a′+b′.

    A · B · C · (a′ + b′ + c′) +
    A · B · (a′ + b′) +
    A · C · (a′ + c′) +
    B · C · (b′ + c′) +
    A · a′ +
    B · b′ +
    C · c′
  2. 分配律:a·(b+c)=(a·b)+(a·c),(a+b)·c=(a·c)+(b·c)
    从1步骤的结果看出所有abc均为a′b′c′,那么这里再做一次替换,各自表示为xyz;然后根据分配律可得:

    A · B · C · x + A · B · C · y + A · B · C · z +
    A · B · x + A · B · y +
    A · C · x + A · C · z +
    B · C · y + B · C · z +
    A · x +
    B · y +
    C · z
  3. 交换律:a+b=b+a, a·b=b·a.结合律:(a+b)+c=a+(b+c) ,(a·b)·c=a·(b·c).
    从第2步骤中可以看出存在两两子项有公共代数式,如:A · B · x,A · x;根据交换律和结合律,可对其组合运算,这里暂以这两个为例:
    A · B · x + A · x
  4. 零一律(幺元律):a+0=a, a·1=a.
    A · B · x + A · x · 1
  5. 分配律:a·(b+c)=(a·b)+(a·c),(a+b)·c=(a·c)+(b·c)
    A · x · (B + 1)
  6. 囿元律(极元律):a+1=1, a·0=0.
    A · x
  7. 根据3~6步骤的运算,得出了A · B · x + A · x = A · x ,重复以上步骤对其他两两子代数运算最终得出代数式:

    A · x +
    B · y +
    C · z

    至此,布尔代数式已不可再优化,那么就可以将其按照起初约定的转为如下代码:

    (op.is_show_lock && !o.is_lock) ||
    (op.is_show_sf && !o.is_sf) ||
    (op.is_show_bz && !o.is_bz)

写在最后

在我作为开发的5年时间里,尚未碰到题主的情况,出现这种代码的情况概率应该也比较少,即使碰到,大多数人可能也会用回答中的其他方案,但大多逃离不了循环,复杂度直接从O(1)变为O(N),虽然最终的影响微乎其微,但了解下其他更优的方案也是百利无害的,从可读性上来说也有提升,读者若是遇到此类代码可以尝试用下这个技巧。

03-05 15:18