初探 ES6:构建和解决数织

在代码的开头,放置严格模式标识、全局工具函数和常量。

此时此刻我要记下一点想法,为了提醒未来的自己不进入一个误区,因为下面要说的是一个我非常容易犯的错误。

之前介绍过 mergeSituation 方法的作用。目前它的核心代码如下所示,当然这是好的:

但我通常觉得简短的代码看起来更加舒服:

这是什么魔法,让代码短了这么多?如果你熟悉 Infinity-InfinityNaN 的运算规则,就会发现这两种方式的作用是一模一样的。那为什么这样反而不好呢?一个次要原因是 NaN。众所周知,NaN === NaN 是假,那假如我要判断一个 cell 是不是 INCONSTANT,就必须写 isNaN(cell) 而不是 cell === INCONSTANT,后者会返回一个不期望的结果。主要的原因是代码强耦合,单看 mergeSituation 看不到它的逻辑,必须结合常量具体的值来看,而一旦常量的值发生变更,mergeSituation 将失效!这就与设置常量的初衷『消除耦合』相违背了。

回到代码,看基类的构造。ES6 可以用 class 声明类了,然而它只是语法糖,底层还是用原型实现的继承。并且它有个缺点,就是只能在类里写方法,不能写属性。所以我只好混用了原型和类。据说 ES7 将允许把属性写在类里。 不要把属性共享在原型上。参见:http://codereview.stackexchange.com/questions/28344/should-i-put-default-values-of-attributes-on-the-prototype-to-save-space/28360#28360

三个子类的构造大同小异,以 NonogramSolve 为例。其中 canvas 参数既可以是 <canvas> 元素本身,也可以是它的 id 属性。构造函数中的 config 参数是一个对象,用来自定义实例属性。

函数的参数不应该多于三个,剩余的应当被封装为对象。

——《Clean Code》

下面来看看最难的一部分,getAllSituations 的实现。前面说了,它的作用是对于给定的行的长度和提示数字,求出所有可能的填充状态。那么这些状态应该怎么描述呢?还是用前面的例子,提示数字为 2 和 3,长度为 7:

  1. |█|█|X|█|█|█|X| ---- blanks: [0, 1]
  2. |█|█|X|X|█|█|█| ---- blanks: [0, 2]
  3. |X|█|█|X|█|█|█| ---- blanks: [1, 1]

我们仔细观察可以发现,每个提示数字对应的 方格组左边的空格组的长度组成的数组 ,与每种情况一一对应。两个数组的长度应相等。假如我们把空格组叫做 blanks,提示数字叫做 hints,那么每行的组成从左到右就是:blanks[0] 个空格,hints[0] 个填充,blanks[1] 个空格,hints[1] 个填充……直到数组结束,若长度未满,仍以空格填充。所以这个问题转化为:求一个整数数组 blanks,长度等于 hints 的长度,首位不小于 0,其余位不小于 1,且元素之和加上提示数字之和不大于行的长度。用名词来概括,就是深度优先搜索。方法调用与构造如下:

剩余部分的实现并不深奥,就不详谈了,感兴趣的读者可以戳 GitHub 继续看源代码。似乎还没说这个项目到底怎么玩?GitHub 里也有介绍;如果觉得赞,请在 GitHub 上喂一颗星星!

3 条评论

  1. 博主你来打我呀说道:

    啊好长好长好长

  2. Art9说道:

    二维码的演示效果超级炫酷!对,我就是那种看不懂代码只能看看效果的 (*@ο@*)

发表评论

电子邮件地址不会被公开。 必填项已用*标注