计算机里的随机数

计算机中没有随机数。

 

本文完。


小游戏里的小随机

如果做个打地鼠小游戏,要让地鼠从 9 个小洞里『随机』出现,不能挨个洞出来。简单办法是给九个小洞编上号 0-8,然后令 Holen+1 = (4 * Holen + 4) mod 9。也就是乘4加4,然后除 9 取余数。游戏开始,我们希望地鼠第一次从中间的洞,也就是4号洞里出来。所以 Hole1=4。得到的结果是:

也就是地鼠会这样出现:

mouse

看起来很规律,记一记就记住了。但把上面公式的几个常数放大,变成 Xn+1 = (1103515245 * Xn + 12345) mod 231,序列就变成了:

数字太大,做一下归一化。因为是 mod 231的结果序列,所以归一化也就是除以 231,得到:

我们再次把这个序列处理回地鼠出现的序列,Floor(Xn*9),得到:

来 1000 个:

这个已经足够『随机』了,发现不了什么明显的规律,统计上也符合平均分布。要说规律也是有的,就像之前 mod 9 于是出现了 9 个一循环一样。在把常数增大以后,这个序列的循环也变成了 231

因为打地鼠游戏中的『随机』的要求,只是无法让玩家找到并掌握规律。任何一个玩家都不可能打上 231 只地鼠,所以这个算法生成的『随机』已经足够了,截取其中一段完全符合打地鼠的要求。

事实上, fn+1 = (a * fn+b) mod c 正是一种很传统的计算机随机数生成法,称之为线性同余法,是使用最广泛也是最古典的随机数生成算法之一。随着时代发展和实际需求不断加深,更加严格的算法被不断发展出来,但线性同余由于算法简单,计算性能高效而又能满足很大一部分一般需求,依然是大部分编程语言 rand() 函数的默认实现方式。从公式可知这些随机数并不是真正的随机,总是会在 c 的范围内循环出现。当

时,输出序列的周期为 c。只要保证 c 足够大,那么一般情况下是不会遇到周期性重复的。

事实上 a=1103515245, b=12345, c=231 正是C语言标准函数 rand() <stdlib.h>所使用的几个常数。

 

种子

从算法可知,假如 X0 不变,那么每一次执行程序,得到的“随机数据”其实都是一样的。执行两遍程序,得到完全样同的数据,重开一次游戏,NPC 的行为一模一样,这感觉其实并不怎么好。但从数学的角度上看,既然算法是一定的,那么每一步得到的数据也必然是确定的,如果初始值相同,那么后续的值也一定是相同的。甚至即使一个序列中断了,只要后续重新设定的种子值与中断前的最后一个值一致,那么这个序列就可以原样继续下去。

换个角度看,只要用不同的初始值,我们就能得到不同的结果。

这一初始值,被称为算法的『种子』(Seed)。最常用的种子数据大概就是使用系统的当前时间了,这是一个简单易用,必然存在,而且还时时刻刻在变化的值。可以让每时每刻产生的随机序列都有所不同。稍复杂一些的有 CPU 当前温度,用户鼠标的移动轨迹,键盘的击键速度等,属于不可预测的 Seed。

『种子』这个词汇甚至不光是随机数据生成算法里有,加密解密算法、身份识别、图像算法等都有这一词汇的出现(当然下载界也有这个词)。对加密算法而言,种子数据往往是高度保密或者完全无法复现的了。前面说的鼠标键盘数据即属于此类。于是围绕种子值往往也会产生许多巧妙的奇思秒想与攻防破解,后文再述。

 

随机与伪随机

不光是线性同余,计算机里一切随机数生成方法实际上都是通过一定的算法生成的,区别只是在于算法复杂不复杂,种子的要求高不高,得出来的 结果能不能满足实际场景的需要。这些通过公式计算得到的“随机数”,有一个特定的名称『伪随机』(Pseudorandomness)。即,看起来像是随机的,实际却不是。但看起来随机往往就足够了。

在有些场景里,这种通过计算机生成的伪随机算法会变得无法符合真实需求。

    1. 大规模的理化生模拟实验,伪随机算法在统计上的瑕疵 可能 会影响到模拟实验的结果数据。

——由于实验可能会用到海量的随机数据,部分伪随机算法的规律性或者其它统计上的瑕疵正好撞上实验的检查项,就会导致实验结果的异常。

    1. 程序员开年会,了解了随机数原理的程序员们纷纷表示不能被一个确定的算法影响了自己的『强运』。

——由于对年会奖品抽奖程序的不满导致年会现场变成 Code Review 大会的事情简直随处可闻。

    1. 赌场这种胜负直接关系到利益的地方

——关于伪随机算法缺陷导致巨额损失的情况后文细讲。

    1. 涉及到窃听与加密、远程控制肉机等可能造成反复利用的情况

——往往破解出一种加密方法,就可以进入大量的服务器,产生巨大的利益。
这时人们会更倾向于使用常识中认为的『真随机数』,例如放射性衰变、电子设备的热噪音、宇宙射线的触发时间等等。比如专门提供这类服务的 random.org 网站,声称是通过测量大气噪音(Atmospheric Noise)获得的随机数据。相比于受到算法限制的数据,毕竟这些数据感觉更“随机”一些。

当然,使用环境数据,则会受到测量传感器采样率的限制,数据的生成速度会受到影响。同时,很多自然环境数据也是连续渐变,或者服从一些确定的分布,随机性可能没有预想的高。比如 CPU 温度之类,在一定时间内的变化总是一条较为连续平滑的曲线。

所以更务实的办法是混用两种情况,即使用一套设计良好的随机数生成算法,了解算法的适用场景,避开算法缺陷,同时使用环境数据作为 Seed。初始值『真随机』后,后续尽管是伪随机但也拥有了更大的不可预测性,在大量重复实验中会表现出来更多的随机性,也就有了更广泛的使用场景。

 

“更随机”与“不那么随机”

对『随机程度』概念的理解多少有点凭感觉。尽管数学上对随机和随机程度是有严格定义的,但引用《信息简史》里提及的,香农提出的例子,大概更容易让人理解『随机程度』的意思:

·零阶近似一一完全随机的字符,其中不存在结构或依赖:
XFOML RXKHRJFFJUJ ZLPWCFWKCYJ FFJEYVKCQSGHYD
QPAAMKBZAACIBZLHJQD.

·一阶近似一一每个字符与其他字符不存在依赖关系,各自的出现频率取在英语中的出现频率:字母 e 和 t 出现得较多,而 z 和 j 较少,且单词长度看起来也较接近现实。
OCRO HLI RGWR NIMIELWIS EU LL NBNESEBYA TH EEI ALHENHTTPA OOBTTVA NAH BRL.

·二阶近似一一不仅单个字母,双字母组合的出现频率也符合英语的情况。
(香农从密码破解者所用的表格中,找到了所需的统计数据。英语中最常出现的双字母组合是 th ,大致每千个单词出现 168 次,紧跟其后的是 he, an, re, 和 er 还有相当数量的双字母组合的出现频率为零。)
ON IE ANTSOUTINYS ARE T INCTORE ST BE S DEAMY ACHIN DILONASIVE TUCOOWE AT TEASONARE FUSO TIZIN ANDY TOBE SEACE CTISBE.

三阶近似一一三字母组合也符合英语的情况。
IN NO 1ST LAT HEY CRATICT FROURE BIRS GROCID PONDENOME OF DEMONSTURES OF THE REPTAGIN IS REGOACTIONA OF CRE.

一阶单词近似
REPRESENTING AND SPEEDILY IS AN GOOD APT OR COME CAN DIFFERENT NATURAL HERE HE THE A IN CAME THE TO OF TO EXPERT GRAY COME TO FURNISHES THE LINE MESSAGE HAD BE THESE

二阶单词近似一一双单词组合以英语中期望的频率出现,所以不会出现上例中 “A IN” 或 “TO OF” 的情况。
THE HEAD AND IN FRONTAL ATTACK ON AN ENGLISH WRITER THAT THE CHARACTER OF THIS POINT THEREFORE ANOTHER METHOD FOR THE LETTERS THAT THE TIME OF WHO EVER TOLD THE PROBLEM FOR AN UNEXPECTED

衡量随机性的方法就是香农同学提出的信息熵概念。在这里没有必要展开论述计算方法,我们使用另一个例子来简单说明随机程度:

这是一个来自 知乎 的巧妙方法,尽管局限性很大,但却一目了然:

random

对于通过算法生成的随机数序列而言,在整个序列中多少会存在一定程度的『局部结构』。『结构』的存在和反复出现,就表明这一序列的随机程度较差。结构越多越密,随机度越差。上面两个例子都展示了这种『结构』的存在。结构是可以预测的,可预测的越多,随机性就越少。赌徒们大概更容易理解这点吧。

 

生成方法与评价方法

线性同余法

线性同余法的公式非常简单,如果已知了足够长的结果序列以后,三个参数 a, b, c 也很容易反推出来。从数学上讲,任何一个可以被计算机运行的算法都必然有有限大的取值范围,既然范围有限,到最后必然会出现周期性循环。尽管如此,231(大约21亿)的循环周期也小了一点。尽管没人会去打 21 亿只地鼠,但是 21 亿粒子碰撞,21 亿次装备掉落等等都是很有可能出现的。线性同余法的优点在于高效简洁易于实现,能满足常规需求,但人们还是需要更复杂更随机的算法。

Mersenne Twister

维基百科 上有比较详细的论述。它的循环周期为 219937 − 1,这是个梅森数所以才叫“梅森旋转”,和梅森本人大概是没啥关系。算法优点一是循环周期大,二是可以通过一些比较严苛的随机数测试,三是可以实现 1 ≤ k ≤ 623 范围内的 32 位精度 k-分布。

缺点一是速度较慢,但在现有的计算机性能下常规运算不是太大问题。二是在一些特定的初始值下,输出的序列会有大量相似模式。也就是会出现初始值相似而输出序列大量相似的情况,这个比较要命。另外,它通不过 TestU01 等少数随机测试。

Multiply with carry
MWC 是 George Marsaglia 提出的方法,优点在于速度快周期特别大,大约有 260 到 22000000。缺点维基上没写,我猜测大概是结构较为明显,不适合的场景较多吧。OpenCV 使用了 MWC 作为随机数生成器,在图像领域,速度和周期确实是最重要的,相比之下,结构导致的缺陷并不重要且容易规避。

其它
维基百科上有专门的目录页提到了伪随机数生成器:https://en.wikipedia.org/wiki/Category:Pseudorandom_number_generators,而很显然这并不是全部的随机数生成程序。每一种伪随机算法都有自己的优点和缺点,适用于不同的场景。在一些简单场景下,设计人员也会使用更简化的,可以通过互质齿轮组机械结构实现的随机,比如:

98aed764834fb9165ced3945b6bc87b2_b

算法评价

同时,当然也有各种针对伪随机数测试方法,去检验生成数据的质量(即随机性如何)。原理基本都是评测生成的序列是否符合必要的复杂性要求。这与无损压缩算法反而有某种异曲同工之处,毕竟压缩算法的目的就是寻找规律模式并且使用更短的片段去代替它。几乎所有的随机序列测试都是基于假设检验、广义傅立叶变换和复杂性测试,对一个已经生成的伪随机数序列进行检测,寻找模式,并评测结果。

最简单的理解,如果你用 int(rand(0,255)) 一百万次生成了一个 .bin 文件,扔进 Winzip 里,结果体积压缩了一半,那么这个随机数生成算法就是非常不合格的。这跟上面的点阵图方式很相似。

顺便附上还是从 知乎 同一问题下抄来的四原则:

K1——相同序列的概率非常低
K2——符合统计学的平均性,比如所有数字出现概率应该相同,卡方检验应该能通过,超长游程长度概略应该非常小,自相关应该只有一个尖峰,任何长度的同一数字之后别的数字出现概率应该仍然是相等的等等
K3——不应该能够从一段序列猜测出随机数发生器的工作状态或者下一个随机数
K4——不应该从随机数发生器的状态能猜测出随机数发生器以前的工作状态

作者:DD YY
链接:https://www.zhihu.com/question/20222653/answer/16482344
来源:知乎

说实话这四条只是原则,并不是任何一个实测的测试方法。单一的测试方法也无法涵盖各种可能性,所以一些比较有名的测试,Diehard Test、TestU01 等实际上都是测试包,包含了多种不同的方法,尽可能覆盖测试的方方面面。

回到游戏

计算机程序大部分都服务于确定的目的,不会有随机的成分存在。查字典不可能随机给解释,打字不可能随机出字,做帐目不可能随机变计算结果。那么什么地方需要随机?最常见的也就是游戏了,包括电子赌博。所以再回来说说游戏中的随机。

『乱步』

圆桌武士(Knight of the Round) 是个当年非常受欢迎的街机游戏,应该也是不少人的童年回忆。

knights001

这款游戏后来被有心人研究出来被发现者命名为『乱步』的方法,类似以下这些规则:

1.红色小兵(不戴头盔的,血少)
a.砍开箱子出 800 分宝箱,他在地上滚动的时候,砍开 800 分,出魔杖。
b.屏幕上如果有两个桶,先不开桶,等他在地上滚的时候,放血放死他,然后开另一个桶,再开 800 分的桶,再开 800 分,出魔杖。
2.绿色小兵(戴头盔的)
a.先吸引他跑动(不攻击),如果他马上又跑动攻击的话,出刀时放血放倒但不要放死他,马上去开箱子。等他站起来踹气的时候,开 400 分或蔬菜盘,出地震法球(这种方法不能出杖,up)。
b.在他在地上滚动时,放血放死他,然后开一个箱子,开里面的分或血,再开 800 箱子,开 800 分,出魔杖。
3.胖子(Fatman)
a.走 C 步时( C 形状的步子,步子很小也很快,就那么一下),放血放倒他,马上开箱子,在他起来踹气时开800分,出魔杖。
b.站在他前方等他冲跑过来攻击你,他冲的时候你马上站在他斜下的方向,他如果走向下走一步(只是1步),放血放死他,然后开两个箱子,再砍 800 分,出魔杖。
4.大剑(Swordman)
a.同样是观察它走 C 步,放血放倒它,马上砍 800 箱子,等它站起来就开 800 分,出魔杖。
…………

乱步的实质就是随机数规律被人掌握了。

受限于街机主板不高的性能,游戏开发使用了比较简单的一个随机数发生器,实际的重复周期很小。同时还将宝物掉落、敌兵行动、主角行动等一系列需要随机的行为,全放在同一个随机数序列中。每一个需要随机的行为,都让这个序列往后走一格,一轮序列走完再从头开始第二轮。

也是因此,玩家可以通过反复的的跳跃、放血大招等动作,来『快进』掉随机数序列中的若干位数字,到需要的位置,再劈砍宝箱,以获得指定的需要的宝物。如下图,当随机序列进行到 03 时,游戏中某个小兵根据该数字进行了冲锋动作,使用掉 03 这个数值。序列进行到 1C,被玩家用连续两次跳跃消耗掉两个随机数,使得随机序列当前值变为 0F。而 0F 对应于掉宝则为『魔杖』。

即,用户根据一些游戏内的特别现象,通过自己的操作控制了掉宝内容。

ranbu

可是主角行动是个受玩家操控的行为,并不是一个需要随机的东西,为什么还会在随机序列中并影响到掉宝结果?

有两个原因。一是为了让画面更富有表现力,游戏主角的动作会有多种画面表现。同样是跳,可以表现为前空翻跳,直立跳。同样是转身,一种先转头,一种先迈步,等等。而另一个原因仅仅是为了让序列更随机。没错,就像前文写过的那样,使用键盘击键、鼠标轨迹等算法外部的影响因素,可以让破解者更难以发现规律。当然,也可能两个原因都有。

我猜测圆桌武士这个游戏就是第二类情况。由于玩家行动,尤其是多人游戏,多位玩家同时行动时,玩家行为的不可预测性甚至比有限机能下内部的随机数发生器更有效,这可以避免简单地被玩家发现『如果做了 A 接下来一定是B』的规律,也避免了敌人行动永远一致的囧境。事实上在它的游戏生命周期内,几乎是整个街机的商业周期内,都运作得很好。直到电脑模拟器时代,才由几个多年孜孜不倦研究的玩家发现并完善了这份随机表。

看起来,似乎让玩家动作可以影响随机序列是个坏主意,若玩家掌握规律,岂不是就可以通过自身动作来影响游戏掉宝等结果了么?但事实并非如此。『并非如此』不是说不影响结果,而是说在玩家动作里设置锚点以影响随机数序列,进而影响掉宝,并不是一个坏主意。

因为在有限的机能和简单的算法下,规律始终是容易被发现的(参考第一节的打地鼠)。假如没有玩家动作影响,规律性的掉宝会让玩家非常容易就总结出形如『第二个箱子不打则第五个箱子必出 +100% 生命大血包』的结论,这会极大缩短游戏寿命。而将玩家动作引入后,即使是同一个玩家玩同一个游戏,两次行动也往往并不一致,于是相同游戏进展也就可能掉出各种不同的宝物了。另外,因为玩家并不清楚哪些动作被埋入了影响因子,因此也很难一开始就针对性的调整动作来尝试『探宝』。

在引入玩家动作影响因素以后,什么时候规律才能被发现呢?一是某位玩家熟练到全套过关动作几乎一致时,他才容易发现掉宝似乎也表现出了某种规律性,从而开始探索对掉宝率的控制。这大概就是上面乱步表的由来。另一种是在游戏起始阶段,玩家的行为并不复杂,正好某个掉率也设置较高容易被随机到时,大量玩家的集体行为容易让规律暴露出来。一个第二关开头的水果盘砍出 +2 生命宝物的古老秘籍大概就是这么来的。

而随着硬件性能的不断提升,这种要依靠玩家行为来产生游戏不确定性的情况越来越少了。该方法毕竟是把双刃剑,在可以不用的时候就不必使用了。现在使用任何语言,都有足够多现成的算法可用,并且硬件性能也足以支撑大量的计算了。唯一还需要讲究的大概就是种子值了。

种子对齐

种子和密码正好相反,密码要求前后不变以供核对,而种子则最好每次都不同,因为相同的种子必然产生相同的『随机』序列。如果种子是预先定义好的固定值,服务器每次重启后跑出来的都是相同的序列,开出一样的奖,显然是不合适的。

相同的种子必然产生相同的序列。

发现什么了么?

这意味着,假如你知道某个随机算法的种子,你就拥有了预测能力,精确地知道每一次开奖的结果,就可以批量地安全地赢走电子赌博网站里所有的奖金。甚至就算你不知道种子,发现每次是相同的序列后,也会拿个纸笔记下来吧。在这层意义上,种子值和密码又有了相通之处——不能为人所知。

好办,只要每次种子值都不一样连程序作者自己都不知道不就行了?

——不是这样的。

前文提到最常用的随机种子是时间戳。可是你服务器总要维护重启吧,甚至可能还有自动重启。假如你在凌晨 3:05 重启,直到 3:15 重新开始赌博抽奖。我就知道你这台服务器的赌博程序一定是在 3:05-3:15 之间启动的。假如你真的使用了时间戳作为种子,也一定是在这 10 分种里取的。10 分种,600 秒, 60 万毫秒。大不了把这 60 万个时间全部作为种子挨个跑一遍看哪个能对上你重启后出产的随机序列就好了。能对上的那个序列,对应的时间戳就是你的种子。然后就是原本计划的那样,安全批量地赢走网站里的所有奖金。

好吧,不用时间戳了,使用 CPU 温度吧。同样也不安全,实际运行的机房计算机的 CPU 温度,我们就算它可能是 50℃ ~ 90℃ 吧,传感器温度有限,就算三位小数,那也就是四万种可能情况而已,挨个跑一遍就好了。奖金就到手了。

要知道,计算机不依赖第三方,内部可以提供的变化数据其实就那么几种,而随机算法也只有那么几种。圈定了一个大致的范围以后,『大不了全跑一遍』来反推种子值,其实是非常有效的。相同的随机算法,相同的种子,必然产生相同的序列,于是你就获得了神的预言能力。

那么假如使用外部数据呢,比如前文提到的 random.org。也可以通过 DNS 解析到假网站,提供自己预先准备好的『毒种子』进行攻击。尽管这个方法在 random.org 启用了 SSL 证书以后变得麻烦一些了,但还有更简单的攻击办法,就是让服务器无法访问该网站,直接让你拿不到网站提供的随机种子。

网站总是要经营的,如果服务器一直无法正常运作,损失不比奖金被赢走小。既然种子对齐的目的是赢走奖金,那么当破解难度大到一定程度时,各种攻击勒索就变成收益更高的方式了。

话题似乎跑远了,但其实本文讨论的核心是『计算机随机,够用就好』。当破解的难度大于勒索的成本,基本也就说明在随机算法这一层面,确实够用了。

感觉的随机

『够用』往往还有另一层意思。

我们还是从掉宝率开始,假如一样极品宝物,比如屠龙宝刀 点击就送 在游戏内的掉率为 1%,这意味着大约杀 100 次 boss 会掉一把。作为一个通过反复击杀 boss 获取的装备,掉率 1% 本就是定位于每个人都能获取的高级装备。如果真要定位为全服稀有,那么掉率必然是几乎接近于零的——只要统计一下整个网游的怪物击杀量,就知道那是多么大一个天文数字。

既然是普及型稀有装备,假如一个玩家每天可以杀 boss 一次,那么大约 100 天期望时间也就是三个月左右会掉一把,这也比较符合网游的更新周期。假如有 10 万人玩游戏,那么平均每天大约有 1000 人会获得该武器,通过游戏社区也会刺激其它玩家的游戏动力。随着游戏进程玩家的装备变好,击杀 boss 的速度越来越快,在版本的最后阶段变成例行公事一般,也可以满足游戏运营方对上线率存留率方面的要求。

如果只计算粗放数据,以上一切看起来都很美好。但事实是:

大约有 10,0000 * (1-1%)100 = 36603 名玩家在三个月以后依然没有获得宝刀;
大约有 10,0000 * (1-1%)150 = 22145 名玩家在五个月以后依然没有获得宝刀;
大约有 10,0000 * (1-1%)240 = 8963 名玩家在八个月以后依然没有获得宝刀;
大约有 10,0000 * (1-1%)300 = 4904 名玩家在十个月以后依然没有获得宝刀;
大约有 10,0000 * (1-1%)365 = 2552 名玩家在一整年以后依然没有获得宝刀;

对于这几千名玩家而言,怀疑游戏开发商虚假承诺数据做假,完全合情合理。在没有附加条件的 1% 随机掉率下,第一天拿到宝物的就有 1000 名玩家,而运气不好刷一整年都不出的有 2552 名玩家。如果我们考虑得更实际一点,不是每个人都天天刷游戏,实际都是新版本刚开火热一点,后面就是有时间上线没时间算了。那我们假设第一周七天天天刷,第一个月每周刷三次,之后丧失新鲜感失望情绪蔓延,每周只上线一次。则:

大约有 10,0000 * (1- 99% 7) = 6793 名玩家在第一周拥有了宝刀,一人多刀没用只算一把,每百人有 6.7 把。
大约有 10,0000 * (1- 99% 16) = 14854 名玩家在第一个月拥有了宝刀,每百人里有 14.8 把宝刀,三周涨了 8.1。
大约有 10,0000 * (1- 99% 24) = 21432 名玩家在三个月内有了宝刀,每百人里有 21.4 把宝刀,两月涨了 6.6。
大约有 10,0000 * (1- 99% 64) = 47440 名玩家在一年内有了宝刀,每百人里有 47.4 把宝刀,九月涨了 28.0。

总比例造成未获得者的不快感蔓延,由于击杀频率的减少也导致存量增长下降。游戏根本撑不到一年。

无标题

假如我们可以付费买 boss 复活再次击杀获得额外一次掉宝机会——就是花钱开宝箱,则需要 chance = 1146 才能使得 10,0000 * (1-1%) chance < 1,也就是说运气最差的哥们需要开一千多个箱才能获得这个 1% 几率的宝物。对他而言,这根本就是千分之一的几率。

不患寡而患不均。不加限制条件的纯随机,对那些一次就出的幸运儿很不错,但整体而言其实给玩家的体验并不怎么好。如果有 10% 的几率,玩家可以容忍 20 次甚至 30 次不出,但如果 1% 的几率,能容忍 200 次的都是凤毛麟角。而前者 20 次不出的几率是 12.15%,后者 200 次不出的几率为 13.40%。越是低的掉率,无限制纯随机给玩家的体验就更不好。究其原因,在于人的感觉与概率的实际表现之间存在差异。人人都不觉得自己是最差的那个倒霉蛋,但总有人会填上这个位置。

有两种截然不同的办法去解决这个问题。

一种是尽量去掉这个位置,比如『积累胜率』。当玩家在一次抽奖不中后,略微提升下一次的中奖几率,如若继续不中,则继续提升,直到 100% 必中为止。例如初始 1% 每次提升 1%,那么再倒霉的人在第 100 次开奖时,面对 100% 的中奖几率也该中了。这样的 100 次才中的倒霉蛋,每十万人里大概会有 9.33×10 -38 人…… 嗯,十万人中最倒霉的倒霉蛋大约也会在第 45-50 次开奖时得到顶级装备,到不了 100 次。魔兽世界里 “幸运币” 的积累机制就是这么做的。

另一种是分割并加大随机性,让这个倒霉蛋的位置被切分到几乎不可感知。暗黑破坏神 3 里的装备随机属性和随机掉落机制是这种方法的例子之一。对于暗黑3而言,一个人物身上有 13 件装备位置,每个位置都有十几种可能的掉落,但最合适的只有一种。而对于每一件特定装备,又是在十几种不同的属性列表中随机获得四到六种属性,每种属性又是在既定的数值上下限区间随机确定某个值。同时对于一个玩家而言,正常游戏在一小时内即可获得十来二十甚至更多的装备。

尽管绝大部分装备都免不了被嫌弃,但通过这种 多装备+多掉落+极大随机 的模式,成功地把倒霉蛋的感觉切分到几乎不可感知了。玩家依然可以在论坛上看见别人的极品装备,但对于自己而言,身上也往往会有几件装备是过得去,勉强能让自己满意的,这已经能极大抚慰倒霉蛋们的感受了。

所以有时候,我们反而需要减少一定程度的随机性,使得它感觉起来更随机。

『We’re making it less random to make it feel more random.』

英文那句不是我说的,是乔布斯说的。

在一个既定音乐列表随机播放时,如果是不加限制的随机,经常会出现同一首曲目连续播放两次甚至更多次的情形。这事实上是很正常的事情,如果每次切歌都是从同一个长度为 n 的列表里选取一首,就意味着每次都有 1/n 的几率选到和上一首相同的歌,于是就重复播放了。直接从播放清单移除听过的歌曲可以解决问题,但就和打乱列表的顺序播放没什么区别了。

根据一些访谈所述,苹果 iPod 的随机播放程序是将不同歌手、不同曲风交错播放,让使用者感觉到每一首歌之间毫无关联,相当的“随机”。

所以你看,科学计算需要的随机,和游戏需要的随机,和播放器需要的随机,和自然界的随机,其实都各有不同。

算法服务于目的。

3 逗还是 4 逗?

为方便位数较长的数字,我们经常会加上一些逗号或者故意在若干位数之间加大间隙以方便阅读。比如:

8326cffc1e178a82fd70f340f303738da877e886

100万亿元津巴布韦币

这里的 100 万亿尽管没有直接加逗号『,』,但在每三位数字之间加大了间隙,所以如果学过英语,就可以比较方便地数出  Thousand, Million, Billion, Trillion 四级单位,所以这张钱是 One hundred trillion dollars。

但这是使用英语度量衡的国家,他们的语言以千为一级,所以以 3 位一级标比较方便,而中文是以 4 位一级的,这意味着在数数量级单位时,我们的思维变成了『千,百万,十亿,万亿』,于是得到『一百万亿』这个表达。

在汉语语系下,这个划分并不方便。直观的显然应该是 4 位一级,也就是『万,亿,万亿,兆,…… 』下去。比如:

三位一级:100,000,000,000,000

四位一级:100,0000,0000,0000

看起来显然是 4 位一级更直观。

接下来又到了是民族还是世界的争论,我认为在这种鸡毛蒜皮的『3 逗还是 4 逗』问题上,以下的见解是可以达成一致的:

  1. 这个标记本来就是辅助阅读用的小改进。除了它应当承担的用途外, 不承担别的用途。
  2. 无论 3 逗还是 4 逗,即使不熟悉的人,阅读起来也并不会造成障碍。本来这就是个辅助阅读的事情,辅助不了就当它不存在。
  3. 对于相应语言体系下的人,只有熟悉的分法才有较好的辅助作用。

所以我的结论是,如果作者和可以预知到的读者群体,都更熟悉 4 逗的话,那么相应的文字应该以 4 逗优先。反之 3 逗优先。假如某种文化是 5 位一个数量级的话,他们的出版物显然应该以 5 逗优先。如果作者与读者群的习惯不一致并且可以预期这种不一致的话,我觉得应该以读者优先。如果不可预知,那就是看作者个人习惯了。

于是,在我的博客和我可能的译作(英译中)的相关文字里,我都会尽量以 4 位为大数划分数量级。而若写的是英文博客的话,则会以 3 位划分。

说白了这就是个习惯问题,中文环境中并不需要强求以 3 位划分为准,4 位更习惯就用 4 位吧。

据我所知日语也是 4 位一级单位的,那么想必中国周边受中华文化影响的应该都是 4 位,大概。

 

Excel 计算缺陷与大数计算

Excel 很多时候可以当作一个简易的数学计算程序,代替 Mathematica 或者 Matlab 之类的专业软件进行一些不算太复杂的数值运算。但 Excel 的数据处理存在很多弱项,在遇到时需要相应作一些处理。

问题一:有效位数大约只有 15-16 位,更多的位数只会用 0 填充了。

1

精确计算的 2n 的尾数不会是 0,始终是 2→4→8→6→2…… 的循环,但从截图上可以看到,Excel 在计算 250 时,就遇到了有效位数问题,使得末尾出现了数字0。

关于问题一的应对:

从例子中可以看到,Excel 提供了 15 位的精度,这意味在在『千万亿』这个级别上 Excel 依然可以进行精确的计算。相当于以小数点后 4 位精度,即 0.0001 元 = 0.01 分 的精度下,处理九千亿人民币以下的财务数据。处理全国 GDP 的数据也可以精确到分,以米为精度可以让光跑一个月,以毫秒为精度覆盖三万多年。

但是如果你真觉得不够,就需要自己用公式实现进位,使用多个单元格作为『数字段』,来确保每个单元格内的数字长度不超过 15 位。

以 2为例,其计算由两部分组成:

最右一列公式为:

其中,Right 函数保证每个单元格只取结果的最右 12 位,让精度始终符合 15 位上限的要求。而 Text() 函数则保证当截取 12 位数字时,不会将原来在中间位置的 0 因为截取而成为首位 0 消失掉。例如,263 =  461,1686,0092,1369,3952,当截取 12 位时,会获得0092,1369,3952,如果不通过 Text() 函数保存首位的 0,则最后合并回去时就会产生错误。

左边每一列的公式均为:

这个公式同时适用于左边任意多列,使得只要电脑性能过关,尽可用尽 Excel  的所有列(一共 16384 列)。

公式略复杂,以 Y2 为例:

最外层 Text() 依然是为了保留首位 0。Value(Left()) 用于提取右边列的进位数字,即当前列的右侧列如果出现超过 12 位的数字时,则截取头部进到本列。Iferror() 用于检测是否进位。将进位数字和本列上一行数据乘二的结果相加后,再检测是否本列也多于 12 位,如果多则截取。公式引用关系如下:

I9N46D5M6%U}E@8IXWXS8CU

使用类似思想,可以精确进行一次数值变化不超过 1014 的大部分大数计算。需要注意的是,假如一次数值变化较大,则每单元格所能保留的位数就相应变小,不一定是 12 位了。

应对问题一的要点有二, 一是自行实行截取与进位,二是利用 Text() 公式特性,保留截断后的首位 0 不丢失。我通常把这种处理办法称为『大数多列化处理』。

 

 

问题二:数值上限大约在 21024-1,由于有效位数限制,实际上限更小一点,大约在 21023+21022+……+2971 ≈ 1.7977e308 左右。

2

这与问题一不同点在于,这个问题不关注精确展开,而更关注公式计算过程中的上限值。当然,使用问题一中的办法也能解决本问题中的部分情况,但对于更大的数字,例如用尽 Excel 所有列(16384列)也写不下的数字,大约 1016384*14 = 10229376,问题一中精确展开的解法就无能为力了。况且在实际展开中,在装满 Excel 前就早早会遇到内存和 CPU 瓶颈了。问题二的解法注重于在有限的计算资源下计算尽可能大的数字。

我们以计算 361 的阶乘为例,如果使用 Excel 公式直接输入 =Fact(361) 则只会得到一个 #NUM! 的结果。意即该计算的值或者计算过程中已经出现了超过 Excel 单元格所能容纳的最大值。

关于问题二的应对:

我们在 Excel 中准备三列数字,A 列为从 1-361 的展开。C1 公式为 =FLOOR.MATH(LOG10(A1)),B1 公式为 =A1/10^C1。

从 C2 起公式为:

从 B2 起公式为:

于是形成如下形式:

1~P8Q1T0%@[@S4J1CS4Y`U9

即 B C 两列形成了类似科学计数法的 b × 10 c 的数列。但不同之处在于,C 列的所有值全部相加,才是整个计算过程的最终解,如图:

EXWL~KE6X(5_2I4BLEN}%E5

即:361! = B361 × 10 SUM(C1:C361) = 1.43792325888489 × 10768,和上一篇博客对照一下,结果还是很精确的。

仔细观察 B、C 两列数值,其实原理就是每当一个新的 A 乘进来,都对结果作一次科学计数法处理,形成 b × 10 c 结构,确保每一次都有 1<b<10,然后把乘方 c 扔在一边最后再相加。

这一解法的关键在于在计算的每一步都即时处理,避免单元格数字过大而『爆掉』。通过这种方法,Excel 的可计算数域范围从大约 10308 变大到了大约 101,0000,0000,0000,0000,更大的数字则会产生 10 ~1000 倍甚至更大的误差。但如果对 C 列的数字再作问题一解法中的多列化处理,则可计算数上限大约会变成 1010229348 ,这里被迫用了幂的幂。这个数字相当大,并且实际不可能用到。早在这个极限之前,你的电脑内存估计就会挂掉。

 

 

当然,因为有 VBA,Excel 理论上也可以做复杂大数字计算,但考虑到学习成本和应用场景,不如学习 Mathematica 来得方便了。一般使用 Excel 做计算,都仅限于操作单元格和自带公式可以解决的问题。

……数零与进位

最近 AlphaGo 与李世石的人机围棋大赛很火,也蹦出一大波跳梁小丑。你会发现什么热点事件都少不了这种人,总是挡在路上,想不看都不行,比如那个输入法王。我记得上次 SoloLens 它也跳出来大谈 Matrix 来着。

牢骚完毕,这次深夜短篇不是写围棋也不是写计算机的,只说数数,更严格的说,只是数『0』。比如:

围棋棋盘共有 19×19 个点,每个点有『无子』、『黑子』、『白子』三种可能性。不考虑无气提子等特殊情况,则一共有 3^361 种盘面。如果考虑到下子进程,则有 361! 种棋谱。

因为有围棋规则在,实际数字会小一些,但这不是本文重点。这两个数字分别是一个 173 位数和一个 769 位数:

我故意设置了不换行,这样大概能直观地体会这个数字有多大了。(左上角的控制键切换换行与展开)

呼应本文标题的问题来了——这两个数字末尾各有多少个『0』?

你当然可以靠眼力劲儿去数,前者容易看出末尾没有 0,后者大概费些功夫能数出有 88 个 0。当然你也可以使用查找替换,数起来方便些。

但是如果没有事先把总数算出来,又如何数出一个数的末尾有多少个 0 呢?毕竟连乘 300 多次算个 700 多位的数字恐怕是更累的一件事情。能少算就少算点吧。

 

办法是有的,数质因素 5 就可以了。

要注意到,无论多少个数连乘,只有 2×5 才会在末尾添加一个 0。有人说 8×5 也会出 0,4×25 会出两个 0。但 8×5=40=22x2x5,有两个 2 其实浪费掉了。而 4×25 = (2×5)2,本质上正好是两对 2×5 相乘,完全符合之前的设定。又因为在阶乘中,2 作为质因数的出现次数远远超过质因数 5 出的次数。每个偶数都能至少贡献一个质因素 2,而每个 5 个数字才能贡献一或若干个质因数 5。因此可得结论:

只要数清连乘中一共有多少个质因数 5,就知道最终数字末尾有多少个 0 了。

对于 3^361,显然一个质因数 5 都没有。所以末尾没有 0。

对于 361!,

  • 每隔 5 个数字,可以提供一个 5。即 5, 10, 15, 20, 25, 30, ……, 360。一共 361 / 5 向下取整,有 72 个。
  • 每隔 25 个数字,可以一次提供两个 5。即 25, 50, 75, ……。一共 361 / 25 向下取整,有 14 个。由于上一条已经重复计算一次,因此这里的 14 不必再翻倍了。
  • 每隔 125 个数字,可以一次提供三个 5。即 125, 250。一共 361 / 125 向下取整,有 2 个。由于上一条已经重复计算一次,因此这里的 2 不必再翻倍了。

再上去是 625,直接超了。于是数 5 活动就此停止。

一共有 72+14+2=88,而 2 的数量肯定是足够的,于是会产生 88 组 2×5,即末尾有 88 个『0』。

这个方法可以广泛用于各类无聊的数 0 活动中,例如:

任取一个足够大的数字 N,比如 10100 即 1 googol,把小于 N 的所有素数乘起来,那么多素数的乘积的末尾有几个 0?

——答案是只有一个 0,算对了么?

 

那么第二个问题来了,为什么数 5 知 0?

答案是,这个和 10 进制有关。

仔细思考,『进制』的本义就是每满多少就 归 0 进位。换句话说,如果 要让末尾为 0,就必须不多不少,最末尾那些余量恰恰等于当前进制。这个 『余量』不是严谨的用词,意会即可。在 10 进制里,你总要正好凑出 10 个或者 10 的倍数个,才能让末尾是 0。在 8 进制里就要凑 8 的倍数个让末尾是 0。16 进制凑 16,N 进制凑 N。

所以归根结底,要让若干个数连乘得到末尾零,就是看能凑出几份当前进制。10 进制就是要凑 2×5,8 进制要凑 2x2x2,每三份质因数 2 会多一位尾 0,只看 2,别的都没用。

既然 10 进制下,10 的唯一质因数分解就是 2×5,而 2 又远多于 5,所以数 5 知 0 就变成了最简单的办法。

理解了这些,下面这些问题也不难回答了:

3361在 3 进制下,末尾有几个 0?

答案是 361 个 0。这个问题等效于 10 进制下 10361末尾有几个 0,等效于 N 进制下 N361末尾有几个 0。——都是361个。

21024在 16 进制下,末尾有几个 0?

答案是 256 个。因为 16 = 24,1024 / 4 = 256。

最后一个问题:

31024在 2 进制下,末尾有几个 0?

 

Excel 数组公式的概念解释

Excel 公式,本质就是输入原始数据,处理后再输出结果数据,放在公式的单元格里。

有些公式,输入是一个数据,输出也是一个数据,例如取整 int()、10 底对数 log()。若 A1=5.5,=Int(A1) 显示为 5。

有些公式,输入是一组数据,输出一个数据,例如 Sum。这一组数据整个是一个参数。若 A1:A5={1,2,3,4,5},输入公式 =Sum(A1:A5),显示为 15。A1:A5 数组是 Sum() 的一个参数。

有些公式,输入是两个参数,输出一个数据,例如 Match(A2,A1:A5)。两个参数,A1 是一个待查数据,参数二是被搜索的数组。

而数组公式,则是输入一组数据,输出一组数据

以 Match() 为例,Match 公式的形式为 =Match(lookup_value, lookup_array, [match_type]),其中 第三参数 match_type (查询模式)在本文讨论中忽略。则本文讨论的简化为 =Match(lookup_value 待查询数值, lookup_array 被搜索数组)

可以看到,一个 Match 公式一次只能在 lookup_array 里查找一个数值。而把 Match 公式改写为数组公式,并用 Ctrl+Shift+Enter 确认以后,实际公式则变成了 {=Match( lookup_value_array, lookup_array, [match_type] )}。

在公式里,本来应该是单一数值的地方,被替换成了一个数组,待查询数值 变成了 待查询数组。则 Excel 会自动响应 Ctrl+Shift+Enter 命令,把该公式拆分成多次分别执行,每次取待查询数组里的一项,单独给出一个结果,然后循环到该数组里的每个元素都被查询一遍。

例如,选择 C1:C5 单元格并在公式栏中输入 =Match( B1:B5, A1:A10, 0 ) ,按 Ctrl+Shift+Enter 回车。Excel 会自动内部展开五次 =Match() 查询,每次查询在第一个参数位分别填入 B1 – B5。即在 A1:A10 中分别查找 B1 – B5 的值,查 5 遍,并把 5 个结果分别放在对应的 C1:C5 单元格里。

array_match

所以:

1. 因为往往有多个输出结果,使用数组公式需要先选择好输出位置,再在公式栏写公式,写完用 Ctrl+Shift+Enter 确认。注意,这多个单元格包含的是『一个公式』。

2. 数组公式需要你在写公式时,把『一个数据』的参数改写为『一组数据』。(例子中 Match() 函数本来的 lookup_value 即『需要查找的值』改成了『需要查找的数组』。)Excel 会自动循环这个改写数组里的每一个数据,然后把公式计算结果填到对应的单元格里。

3. 数组公式修改起来较为费劲,经常会出现『不能更改数组的某一部分』,正确的方法是先按 Ctrl+/ 全选该数组公式的整体占用位置,然后再在公式栏进行修改。

4. 某些公式,例如 Sum()、Len() 使用数组公式和直接使用该公式往往没有区别。所以如果你见到某个教程在以 Sum 举例讲数组函数时就不用往下看了。百度搜出来有不少是这样的。

5. 一般来说,常用的数组计算 Excel 都已经提供了特定的函数,比如 Logest()、Frequency 等。如果返回的值有两个以上的,也通常都拆成了多个公式,比如线性回归的 Slope()、Linest()、Steyx() 等。当需要多个计算结果时,也无需使用数组公式,使用 Excel 的公式复制粘贴就可以完成绝大部分工作。上文的例子即是如此,选择 C1:C5 然后输入 Match() 数组公式,和先在 C1 输入普通的 Match 公式 =Match( B1, $A$1:$A:$10,0 ),然后把公式复制到 C2:C5 上,效果是一样的,后续处理起来还方便一些。

 

那么数组公式有什么用呢?

大部分情况其实没什么用,确实没什么用,所以很多人用了好久也没用过数组公式。哲学点地说,等到你需要用数组公式时,数组公式就有用了。

数组公式的最大特点是『输出的是一个数组』,所以它需要用多个单元格才能放下,同时,它可以作为数组参数供其它函数使用。所以数组函数最大的使用场景是通过复杂嵌套函数,实现更大程度的 Excel 自动化。

例如,去除重复单元格,可以使用 Alt+A+M 的『删除重复项』实现,但这样意味着每次数据更新,都需要重新进行人工操作,当处理步骤较多时,往往意味着后续步骤也需要重新操作。而使用数组公式,则可以一劳永逸地解决这个问题。

因为『删除重复项』本质上就是一个『输入一个数组,输出一个数组』的操作。在这个例子里,Match() 函数的第一个参数和两个 Row() 参数进行了相同的对应循环,并把每个计算结果填入相应的单元格里。

而另一个例子

则相当于把 A1 单元格中的每个字符都单拆出来。辅以其它公式嵌套,可以比较方便地计算诸如『若干个单元格一共包含多少个特定字符』之类的问题。

 

一句话总结:当你在使用 Excel 时,需要处理『处理若干个数据,过程中包含若干数据,结果也是若干个数据』时,在宏程序之外,还可以考虑使用数组公式。

星际争霸 2 攻防数值研究

星际争霸里的数值平衡是挺有意思,值得仔细咀嚼的东西。

预备知识:虽然星际显示的所有数值都是整数,但在内部则使用了更精确的小数计算形式。而只在结果上进行了向上/向下取整处理。在不同场景下,这个计算特性也造成了一些数据结果的特性。比如:

  • 虫族的满血单位在受攻击后会立刻回一点血。
  • 取消建造返回 75% 资源但永远是整数。
  • 人类维修在一矿一气内是先扣款再维修。
  • 一次攻击最低造成 0.5 伤害。

等等……

攻击力,护甲值,血量

对于战斗中的任意一次攻击行为,都是通过以上三项得到最后的伤害计算结果,计算公式为:

伤害 = max { 攻击力 – 护甲值, 0.5 }

直观的公式,攻击力被护甲抵消掉一部分,剩下的对单位造成实际的伤害。

  • 人类枪兵默认 6 点伤害;
  • 人类地堡,默认 1 点护甲;

则每枪对地堡造成 5 点伤害。

当护甲值大于等于攻击力时,每次攻击至少造成 0.5 点伤害。

  • SCV 攻击力 5;(战五渣=。=)
  • 行星要塞,升级建筑护甲,护甲值 5。

结果:SCV 每次攻击造成 0.5 伤害,由于显示数值均为向上取整的整数,所以实际上需要两次攻击以后才能看见 HP -1。

虫族被动回血与向上取整

虫族的所有单位均有一个持续的回血速率,为 0.27 血/秒。个别兵种,例如飞龙,或者埋地蟑螂,会有更高的回血速率。这一回血不受任何外界因素影响,也与是否战斗无关,永不停止,直到满血。

星际的血量计算是存在小数数值的,包括伤害下限的 0.5 血,包括虫族回血的 0.27/秒。但这属于内部数据,对外展示出来的最终结果,依然是整数。从真实的小数值到实际呈现的整数值,中间的取数办法是向上取整。也就是说,在任何情况下,只要血量真实值存在小数点,则显示数值均向上取一位。

例如虫族所有单位均有 0.27 血/秒的回血速率(部分特殊兵种更高)。那么任何一个虫族兵种被攻击一次以后,只要不死,则会立刻启动回血过程,在小于半秒的时间内回复了 0.1 的血,在最终数值显示上便回了一点血。这也是那些初级数值研究文章里常说的,虫族在被攻击一次以后会回复一点血的原因。其真实原因如下:

  • 虫族单位的回血是任何时候只要小于满血量便永远存在的;
  • 虫族单位被攻击后进入不满血状态,该回血启动,回复 0.1 甚至更少的值;
  • 系统显示数值是向上取整而非四舍五入;
  • 故显示上是瞬间回 1 血;

某些人认为的“1 点隐藏护甲”,则是完全完全错误的。尝试:

  • 零攻攻城坦克:攻城模式 35 点攻击力,重甲 +15;
  • 零防跳虫:35 血,0 护甲值,轻甲;
  • 一防跳虫:35 血,1 护甲值,轻甲;

零防跳虫就是一炮死。因为来不及回血,也不存在什么隐藏护甲值。但是如果虫族升了一防,则坦克一炮轰上以后跳虫会有两格血,事实上是 35-1 = 34 点伤害,剩余 1 血,同时虫族回血机制启动,极短时间回复 0.1 血(每秒是 0.27,开始回血时还不到 1 秒,几乎瞬间),由于数值向上取整,我们就会见到回复了 2 滴血。再尝试:

  • 一攻女妖:13×2 攻击力;
  • 三攻机枪兵:9 点攻击力
  • 零防跳虫:35 血,0 护甲值;

则零防跳虫在经受了女妖和机枪兵的一轮攻击后,正好还剩下一点血。本来伤害量是正好 35 点的,但由于女妖和枪兵无法做到完全同时产生伤害,使得跳虫有短暂的时间回复零点几的血,从而在第一轮攻击中幸存下来。需要机枪兵额外一次攻击才能杀死。从这些数值可以明显地看出,虫族的 “第一次受攻击秒回一血” 只是一个结果,并不是原因,而 “隐藏护甲” 则简直是瞎扯。另外,虽然从结果上看 “受到攻击后立刻回 1 血” 有时候也正确,但由此衍生出来的什么 “回 1 血隐藏 CD” 也是错误的,单纯只是持续回血过程中是否正好巧合地遇上整数断点罢了。

由于正常情况下伤害值都是整数,故虫族的小数点血量在实际效用上确实等同于整数 1 血。虽然真实血量是个斜线,但效用血量却是阶梯横线。

非零攻异龙的弹射攻击是星际中唯一的小数点攻击力数值,腐化者的腐化技能可能产生大量的小数点伤害数值。奇葩都出在虫族。

攻防升级

每个种族都有针对绝大部分单位的攻防升级科技,可升级三次。攻防升级对已经在战场上的单位同样有效,因此可以利用刚升级完成的时间让大部队发动一次有攻防优势的进攻。

三个种族的攻防升级具体影响的单位并不一样。人类分为生物部队攻防和机械部队攻防;星灵分陆军和空军攻防,另有所有单位的离子盾升级;虫族没有离子盾,但地面部队的近战攻击和远程攻击是分别升级的。

此外,还存在针对个别单位的单独攻防升级项目。人类的建筑护甲升级(+2 甲),雷兽的硬化甲壳升级(+2 甲),人类的恶火对轻甲的伤害加成等。

同一层攻击升级对不同单位的影响也不一。例如虫族地面近战攻击升级,跳虫是每层升 1 点,而雷兽则是每层 3 点。而三族所有防御升级却全是 +1,专项升级除外。因此三攻三防的枪兵内战,和零攻防的枪兵内战效果一模一样,但三攻三防的坦克对打起来却比零攻防的对打死得更快。高级兵种在攻防加成上享受到的比初级兵种更多一些。

多重攻击

在游戏中,“多重攻击”可以理解作“左右开弓”。也就是每次攻击有双份,每份独立进行攻防计算。游戏中除了星灵母舰是 6 重攻击以外,别的拥有多重攻击属性的都是双重攻击。

大部分双重攻击的单位都可以从外观上直观地看出来,收割者是双枪党,显然是双重攻击;女妖导弹有明显的相互缠绕的轨迹,也是双重攻击,导弹塔也类似;狂热者是帅气的幽能双刀;巨像和凤凰的两道激光那么明显,女皇表示她也有两只爪子……

例外的是行星要塞和不朽者。游戏中说明文字上写的行星要塞是加装了双管“伊比克斯”加农炮,外形上也很霸气,却不是双重攻击。不朽者的游戏背景资料写道,他们是仅存的龙骑士们加装了双重相位分裂炮,但实际游戏中也不是双重攻击。

双重攻击意味着 “一轮攻击,两份效果”。通常来说,双重攻击的特性主要是为了数值平衡,“打了一半”的情况极少出现,但如果你刻意制造还是能做出来的,比如狂热者的两刀就是有先后的。

在多重攻击下,伤害计算公式依然简单而符合逻辑:

伤害 = max { 攻击力-护甲值, 0.5 } x 多重攻击数

例如:

  • 零攻收割者,攻击力 4×2;
  • 三防雷兽,护甲 4。

每一轮攻击会造成 0.5×2 的伤害,也就是 1 点。而不是 0.5 点。通常来说,双重攻击在攻防优势时的收益更大,而劣势时的差距也更大。

双重攻击存在完全同步的两次攻击(收割者),以及有先后的双重攻击(狂热者)两种情况,两者在极端情况下的表现会有微小的不同,但大致没有区别。

特定属性加成

很多兵种对特定属性的对象的攻击力会有额外加成。例如劫掠者的普通伤害是 10,而对重甲单位的伤害是 20。被加成的属性通常有轻甲、重甲、生物单位等。目前还没有兵种对两种以上的属性有加成伤害。攻防升级对加成伤害同样有效,升级变化数值有些高于基础伤害的增加值。

但加成伤害的具体数量并没有明确的规律可寻,孢子爬虫对生物单位的伤害高达基础伤害的 3 倍。(15→45),而执政官则只加成约 40%(25→35)。通过特定属性加成并且分别微调,暴雪给自己留了个方便的平衡性改动的口子。

这些加成的计算优先级最高,只和攻击者及被攻击者的属性有关,计算时可以直接视为基础伤害。这造成了非常明显的兵种相克的链条,使用针对性的兵种能有效扩大优势夺取胜利。

攻击间隔

每个可进行直接攻击的单位都有两次攻击间的时间间隔设置,数值也是游戏里的秒。不少资料简单理解为攻击频率,严格地说是不对的。频率定义的是每秒多少次,而不是两次间隔;

  • 攻击间隔是该单位两次攻击行为之间的最短时间间隔,这个时间被形象地称为 “Cooldown”,来源于工业机械的停机散热。并不是到时间就必须进行下一次攻击;
  • 对于分先后的多重攻击,攻击间隔严格描述的是多重攻击每轮攻击中 “第一发” 之间的时间间隔。例如狂热者的两轮攻击,两个第一刀之间的最短时间间隔为 1.2 秒。多重攻击内部间隙一般不考虑了;
  • 除凤凰战机以外,所有兵种在都要站定时才能攻击,也就导致了 “甩枪” 这一操作的出现;
  • 站定输出的实质在于产生一次攻击前有短暂的前置时间,随便理解成瞄准、深呼吸、扣扳机都行,这一时间各兵种有不同但都接近于零,实质上就是程序在判断输出时必须无移动指令;
  • 实战中,站定输出的前置时间长度确实存在,原因是网速。你网速越好,前置站定时间越短;
  • 站定输出的后置停止时间是装饰用的,打完了歇口气的感觉,可以通过手动移动指令覆盖;
  • 兴奋剂、肾上腺素等升级可以减少输出间隔,在相同时间内作出更多次攻击行为,提高伤害;
  • 射程相当时,在移动上有优势的单位可以通过操作把对方兵种的攻击频率拖到跟自己一样;
  • 巢虫领主、航母等附带单位的攻击间隙,主单位与附带单位的攻击间隙都是独立计算的;
  • 尽管攻击力除以攻击间隔就可以得到 DPS(每秒输出伤害)数值,但 DPS 数值并不能有效描述该类单位的实战输出强度;
  • 部分战斗系技能有冷却时间,不耗蓝技能的 CD 较长,而耗蓝技能出于平衡性因素也添加了一些 CD,比如灵能风暴的 2 秒;
  • 修理技能没有 CD,感染者扔异化作战体也没有 CD;

 

距离与体积

星际中的长度单位以“格”为基础单位,游戏中各种涉及到距离、长度、速度的数据,均以格为基础数据,用精确的小数衡量。“格”的范围在建造建筑时体会非常明显。

尽管建筑占地为 2×2、3×3 之类,但建筑实际挤占的空间却略小于占地面积。在两个对角建造的建筑缝隙间,依然可以穿过体积小于 1 的兵种,比如机枪兵或者小狗。需要错一格才能严格封堵。对于 5×5 的主基地则更为特殊,错两格都依然能通过 1 体积兵种,错一格时甚至能通过一个追猎者。

每个单位都有自己的体积,以指定半径的圆形描述,属于一个内部数据,与玩家选中单位时脚下的圆形没有关系,只能通过解析客户端数据获得。所有的占位、可穿过等与体积相关的特征,都与这个内部数据相关。当两个单位的圆形最近处宽度小于兵种自身直径时,寻路算法便不把两单位之间的缝隙视作一条通道了。部分单位在切换形态时,例如攻城坦克切换到攻城模式时,其体积会发生变化。另一个更实际的例子是星灵机械哨兵的力场,通过从某个特定坐标 “膨胀” 出一个圆形,从而对部队分布产生影响。

而在建筑摆放时,则无视圆形体系这套系统,单纯以建筑网格判断是否可以建造。因此两个相邻建筑的碰撞体积圆周可能是相交的。所以有时候会出现攻城模式的坦克挡住了气矿,气矿无法建造的情况。

空中单位也存在体积,区别在于空中单位并不强制进行碰撞,而是缓慢地相互分离,直到达到最后互不重合为止。

视野

每个兵种与建筑都有自己的视野范围,呈圆形,在视野内才能看见敌方的动静,视野外则覆盖有战争迷雾,只能看见地形:

  • 地面单位的视野受地形影响更大一些,会受到树、草丛、柱子等影响,而空中单位则不会;
  • 所有友方单位的视野共享,可以借助友方单位的视野进行一些必须有视野才能操作的动作,比如投放矿骡或者挖虫洞;
  • 建筑也拥有自己的视野,大多都是 9,但主基地和防御建筑的视野会更大一些;
  • 感应塔的视野是 12,但额外拥有 30 的感应范围,可感知敌人位置,但不属于视野;
  • 不考虑感应塔的特例,视野最大的建筑是萨尔那加塔,为 22,单位是星灵母舰,14;
  • 雷达可以全图开视野,半径为 13,这个都知道;
  • 所有的工程兵视野均为 8;
  • 建造中的建筑视野为 5,包括自身占据体积,对人类而言,建造中的 SCV 也会扩大这一区域的视野。
  • 虫茧,即正在变异中的毒爆、眼虫、巢虫领主和幼虫孵化各兵种时的状态,视野为 5。
  • 埋地单位视野减少为 4,但感染者和毒爆不变,异化作战体减少为 5;
  • 反隐可以认为是一种特殊的视野,反隐单位的反隐距离永远与视野一致;
  • 虽然如此,但视野会被地形遮挡,反隐能力却不会。悬崖下光子炮台,地形受限视野无法覆盖崖上,但借助友军视野,依然能侦测到高地上范围内的隐藏单位;
  • 视野详细数据见:http://wiki.teamliquid.net/starcraft2/Sight

 

射程

有距离的衡量单位以后,射程也就可以确定了。在游戏中射程的重要性受操作的影响非常大,更经常与地形、视野配合,获取更大的战斗优势。

  • 射程的起止点是从攻击单位的体积边缘一直到受伤害单位的体积边缘,以确保两个不同体积但相同射程的单位在无额外操作的情况下可以相互攻击到对方。最典型例如地堡枪兵(5+1)对抗追猎者(6);
  • 因此在视觉上,重形单位射程比实际数值更大一些。典型例子:光子超载的星灵枢纽、仲裁官;
  • 每个单位都存在体积,其边缘和视觉并不完全一致,有的大有的小。具体数值解析客户端可得,实际往往通过聚团时大小靠感觉判定;
  • 尽管异龙的射程只有 3,但由于弹射另有 2 的射程,再加上建筑较大的自身体积,异龙能打到 7 以上的距离。借助建筑弹射绕路的地面部队也是异龙操作技巧之一;
  • 近战兵种也是有射程的,数值上视为 0.1,但毒爆的触发范围为 0.25,确保毒爆对近战单位的优势。毒爆的伤害范围为 2.2,以爆炸中心点开始计算;
  • 升级冲锋的狂热者在冲锋追上敌人后必定至少造成一次伤害,这次伤害的 “近战距离” 可能大于 0.1,属于特殊设计;
  • 大部分兵种的远程攻击距离是整数,但并非必然。收割者(4.5)、巢虫领主(9.5)表示有话说。
  • 蔽目毒云造成的近战距离也为 0.1;
  • 人类攻城模式坦克和星灵风暴战舰是仅有的两个最大射程比视野还大的兵种,需要友军帮助打开视野才能获得最大攻击范围;
  • 人类攻城模式坦克是唯一一个有最小攻击距离的单位,距离为 2;
  • 星灵航母的射程分两段,8 点为派出拦截机的触发距离,14 点为拦截机的强制回收距离。
    • 拦截机派出时间为 8 秒,8 秒后必须回到航母后再重新派出,因此以下内容在讨论距离为 8 – 14 范围时都假定在同一个 8 秒内;
    • 拦截机本身在动画上有射程,但该射程实际并无作用,拦截机有时在动画效果上会飞到接近 14 处,但依然不会攻击 14 以外的目标;
    • 在派出拦截机后,航母可以后撤,只要 14 范围内,拦截机在 8 秒内依然会攻击既定目标;
    • 拦截机也有视野,因此在攻击 8-14 范围目标时不需要额外开视野;
    • 在 8-14 范围内,目标死亡后,玩家可以手动点选一个新目标,拦截机可以不回舱直接转向新目标。此时航母会驶向目标,再次手动拖拽可以使它继续停在 8 以外,达到距离外切换目标的效果;
    • 但 8 为“派出距离”,也就是说如果敌方在 8-14 内时,尽管已经派出的拦截机会继续攻击,但新造的或者未派出的拦截机则不会继续派出;
    • 默认的 “派出间隔” 为 0.5 秒,4 秒全派出。可以通过升级提升到 0.125(前四)到 0.25(后四),1.5 秒全派出;
    • 拦截机的攻击间隔为 3 秒,基础攻击力 5×2 双重攻击,一次派出可以攻击三次;
    • 航母的攻击力完全来自于拦截机,相当于 5 点攻击力的 16 重攻击。因此敌方的护甲升级会产生巨大的影响;
    • 拦截机 25 血,虽然无法选中、无法手动点击,但可以被自动防御机制,例如防空塔、机枪兵、光子炮台等击中摧毁,因此航母需要时刻注意补充拦截机。友军人类开启 SCV 自动修理也可以修理拦截机;
    • 拦截机虽然不占用人口,但由于航母占用 6 人口,因此初始四架每架拦截机等效占用 1.5 人口,满载八架后每架等效占用 0.75 人口;
    • 空的航母在攻击指令下达后也会停在 8 格处,而不会飞到敌人脸上;

 

移动速度

移动速度是建立在游戏内距离和游戏内时间双重数值下的的一个兵种动态属性。速度的作用在于快速接近敌人、逃跑、追击、巡图、捡漏等,以建立整个对战过程中的战术优势。速度并不影响攻防数值,但却可能影响兵种相克。速度的衡量标准单位为 1 格/秒,可以简写为 1。

  • 移动速度与攻击速度(攻击间隔)是分别的两个属性,例如时空折跃会将区域内敌方兵种移动速度降低到 50%,但并不改变其攻击速度,虽然从感觉上来说应该变;
  • 在较大部队行进时,每个兵种的速度是各自计算的,由于寻路与体积碰撞等因素,整个部队的移动速度会慢于组成兵种的移动速度;
  • 游戏中速度最快的是星灵航母的拦截机,速度为 7.5,但是考虑到拦截机属于特殊情况,而航母本身却只有可怜的 1.875 的速度,所以实战意义上的最快速度并不是它;
  • 可操作的最快速度是菌毯上的代谢加速跳虫,为 6.1088,其次紧随其后的是冲锋状态中的狂热者,速度为 6.05;
  • 那么显然问题来了,冲锋技术哪家强 冲锋狂热者能否追击到菌毯提速狗?
  • 答案当然是不能。事实上冲锋也是一个 BUFF,持续 3.5 秒,在此时间内差不多能冲过 22 格距离,相当于萨尔那加塔的视野,也差不多是一个屏幕的距离,然后就会慢下来;
  • 这与某次补丁的 “冲锋后至少能击中一下” 不矛盾,冲锋在两者距离小于 0.1 时会提前结束,当提前结束时,必然产生一次攻击。但如果是时间到结束,则相当于始终未曾接近,故不产生攻击行为;
  • 所以如果你够骚,可以尝试拿条跳虫把对方的狂热者带到一个屏幕外自己的大部队这里来消灭掉;
  • 速度第三是无菌毯提速狗,4.6991,所以穿狗入矿区必须靠建筑预防;
  • 并列第四的是推进器状态医疗艇、恶火和凤凰,均为 4.25;
  • 速度最慢的是未升级王虫,为 0.586,理所当然;
  • 其它数据详见:http://wiki.teamliquid.net/starcraft2/Speed

 

弹道

很多兵种发射的子弹都有明确的行进轨迹,人类的各种飞弹,星灵的蓝色激光,虫族的绿色喷吐。弹道并不仅仅是一个视觉效果上的设置,在数据方面也会产生一些变化。

  • 弹道类攻击从攻击方发射出来,到防御方受到攻击,中间存在一个时间差。这个时间差随着弹道长度会变化;
  • 随着目标的移动,弹道也会发生变化,最后总是会击中目标,即使目标已经移出自己的射程外;
  • 铁鸦的可以跑远躲开的自动导航炸弹是技能,不是弹道类普通攻击;
  • 子弹飞行时间的长短并不影响攻击兵种的攻击频率,间隔是按发射频率算的;
  • 弹道类攻击有更明显的发射前动作,而不光光是子弹的飞行时间。这使得 “甩枪” 动作需要停留的时间比机枪兵和收割者更慢一些;
  • 尽管常规移动并不影响弹道类飞行的命中,但确实可能通过某些手段回避掉已经发出的弹道类攻击。比如追猎者的闪烁,或者人类兵的快速上下医疗艇。原理是取消弹道类攻击的飞行终点;
  • 整个游戏唯一针对弹道类攻击的技能是铁鸦的定点防御靶机,但出于平衡性修正,个别视觉上属于弹道类的攻击也被排除在外了;
  • 所以上医疗艇防的弹道类攻击种类还是最多的;
  • 弹道类攻击会出现很多子弹同时射向某个残血目标导致大量输出浪费的现象,原因也是弹道攻击的延时。尽管输出有先后,但在第一发子弹飞到目标前,大家都会认为目标还活着纷纷扔出自己的菜刀;
  • 在非常非常极端的情况下,瞬发技能也可能造成类似的输出浪费。这是因为星际在游戏机制上的最短时间间隔为 0.001 秒,即 1 毫秒,可以通过分析游戏录像包数据得到。这使得在同一个时间间隔内的动作变成 真•同时;

 

星灵护盾(一)

星灵护盾是星灵特有的属性,覆盖所有的建筑和兵种,甚至包括生产过程中的单位。在任何情况下,星灵单位受到伤害时,都优先扣除护盾值,到护盾为 0 时,再扣除 HP。星际 1 中存在蝎子毒血无视护盾直接伤害 HP,星际 2 不存在。

护盾和护甲的防御是分别计算、单独升级的,所有单位的护盾防御初始值是 0,每级 +1。计算公式和护甲完全相同,即:

伤害 = max { 攻击力-护盾值, 0.5 } x 多重攻击数

在最极端的情况下:

  • 零攻收割者,攻击力 4×2;
  • 机械哨兵,守护者之盾,使光环内友方单位受到的远程伤害减少 2 点;
  • 二盾狂热者,护盾防御 2 点。

收割者每轮攻击为 (4-2)x2,而护盾防御也为 2,实际伤害为 0.5×2,即 1 点。当未开启守护者之盾时,则每轮实际伤害为 4 点。守护者之盾开启与否效果很明显。

当护盾的总量不足以抵挡单次伤害时,则护盾防御与护甲防御同时起作用。比如:

  • 零攻掠夺者,对重甲伤害 20;
  • 一防一盾追猎者,护盾剩余 10 点,护盾防御 1 点,护甲防御 2 点,重甲;

则掠夺的下一发伤害计算为:

  1. 护盾防御减伤: 20-1=19 点伤害;
  2. 全部剩余护盾抵挡掉 10 点伤害,19-10=9,剩余 9 点伤害传递给 HP 承受;
  3. 9 点伤害被护甲抵消 2 点,剩余 7 点伤害实际作用于追猎的血量上。

但当该临界点下的伤害被护甲完全防御时,并不造成额外 0.5 的伤害。比如:

  • 零攻狂热者,伤害 8×2;
  • 一防一盾机械哨兵,40 护盾 40 血,护盾防御 1 点,护甲防御 2 点;
  • 零防一盾机械哨兵,40 护盾 40 血,护盾防御 1 点,护甲防御 1 点;

由于狂热者双重攻击有先后,则攻击过程为:

  1. 前两轮打在护盾上,造成两次 (8-1)x2 =14 伤害,哨兵剩余 12 盾 40 血;
  2. 第三轮前一刀 7 点伤害盾完全吸收,后一刀 7 点伤害盾吸收两点,剩余两点打到护甲上;
  3. 零防哨兵会损失 1 点血量,39 HP,一防哨兵则未损失血量,40HP;
  4. 等待哨兵回复满盾后,再重复以上过程,零防哨兵再减 1 点血量,38HP,一防哨兵依然未损失 HP,40HP;

零防哨兵的掉血曲线间接证明了狂热者双刀有先后,而一防哨兵未掉血,表明在临界点上,一次伤害如果已经对盾造成实际损伤,则即使剩余伤害被护甲完全抵消,也不会额外计算 0.5 伤害。理由大概是已经存在伤害,不需要再考虑伤害下限了。

护盾会在最后一次伤害结算后 10 秒开始回复,回复速度为每秒 2 点。有些说明写的是脱战后 10 秒恢复,从语言表述上来说是不正确的。星际跟 WOW 不一样,并没有明确的“战斗状态”,也就没有进战脱战这种说法。只要 10 秒不受到伤害,在战头中也是可以回复的,下次用闪烁追猎记得多操作。

建造中的建筑或折跃中的单位的护盾行为例外。建筑在放下的一瞬间就有 10% 的血量与护盾,而后按建造总时间与总护盾值匀速增长,没有 10 秒的结算时间,没有护盾防御加成,也没有额外的回复。例如:

  • 星灵枢纽:1000 血 1000 盾,建造时间 100 秒,放下的一瞬间有 100 血 100 盾,之后每秒增加 9 血 9 盾;
  • 零攻追猎者对重甲每次攻击 14 点伤害,1.44 秒攻击一次,100 秒攻击 69 次,造成 966 伤害;

也就是说,只靠一个追猎者,在主基地建造过程中完全无法造成实质伤害……

折跃兵种的过程也是一样,速度较快没有仔细计算初始状态是否是 10% 血量。

所有建筑的护盾和血量都相同,绝大部分的兵种血量都大于等于护盾,但黑暗圣堂和执政官除外。考虑到升级时间与升级费用,在对抗虫族时升级三防有时不如升级一盾更合算,尤其是队伍内有大量执政官时。对抗人类……因为有 EMP……还是算了吧。

星灵护盾(二):刚毅护盾

刚毅护盾是星灵不朽者特有的被动技能,当护盾存在时,所有攻防结算伤害高于 10 点的攻击效果都降低为 10 点。这一上限是经由护盾防御结算后再作限定的,也就是说是 min{攻击力-护盾防御值, 10}。比如:

零攻追猎者,普通伤害 10 点,对重甲伤害 14 点;
三盾不朽者,重甲,护盾防御 3 点,护盾满,刚毅护盾有效。

则追猎打不朽,以重甲结算,攻击力为 14 点,不朽护盾防御力为 3 点,则攻防结算伤害为 11 点,触发了刚毅护盾,减少为 10 点,所以最终结果是不朽减 10 点盾,并发出刚毅护盾视觉效果。当不朽的护盾不足 10 点时,刚毅护盾效果依然存在,将高于 10 点的攻击伤害降为 10 点,剩下计算和普通的分界线计算一致。

但刚毅护盾对固定伤害的技能性攻击是无效的。尽管星际2是个科幻背景游戏,不过把以下这些技能理解作是“魔法攻击”,会更方便一些。

“魔法攻击”(技能伤害)

星际2是个科幻背景游戏,但把这些技能理解作是“魔法攻击”,理解上会更方便一些。

  • 聚变打击(幽灵) – 较大范围内 300 点伤害。
  • 大和炮(战列巡航舰)- 单体 300 点伤害。
  • EMP (幽灵) – 范围移除 100 点星灵护盾与 100 点能量(如果有能量)。
  • 哨兵导弹 (寡妇雷) – 对攻击主体造成 125 点伤害(有护盾单位额外 +35 点),并造成 40 点溅射伤害(有护盾单位额外 40 点)。
  • 猎杀飞弹 (铁鸦) – 攻击主体及范围内造成最多 100 点伤害。
  • 狙杀(幽灵) – 对生物单位造成 25 点伤害,对灵能单位额外造成 25 点伤害。
  • 能量反蚀(光明圣堂武士) – 消耗目标所有能量,每点能量造成 1 点伤害。
  • 灵能风暴(光明圣堂武士) – 范围内 8 次伤害每次 10 点,共计 4 秒。
  • 霉菌滋生(感染者) – 范围内 4 秒造成 30 点(重甲 40 点)伤害,且无法移动。

以上为伤害性技能,其特点是:

  • 伤害值不受攻防升级影响
  • 伤害值无视护甲、护盾防御值
  • 伤害值无视守护者之盾,但是会受到腐化影响
  • 伤害值无视刚毅护盾
  • 除聚变打击外,所有的技能施放都需要能量值

基于这些特性,认为是“魔法攻击”也并无不妥。是的,毒暴的伤害并不属于此类,毒暴的伤害是受攻防加成的。

能量

能量值是大部分兵种和单位施放特殊技能的消耗性资源,在游戏中以紫色条显示。大部分有能量单位的能量上限为 200 点,并且永久地以每秒 0.5625 点回复能量,刚生产出来的单位初始拥有 50 能量,一些单位可以研发升级让初始技能变为 75 点。但有三个单位例外:

  • 星灵枢纽初始能量 0 点,上限 100 点;
  • 皇后初始能量 25 点;
  • 定点防御靶机初始能量 200 点,每秒回复 1 点能量。

和血量相反,能量是向下取整的。

没有任何方式改变能量的增长速度与技能消耗,但依然有两个技能可以影响敌方能量:

  • EMP(人类,幽灵) ,范围内有能量的敌对单位减少 100 点能量;
  • 能量反蚀(星灵,高阶圣堂武士),清除敌对目标所有能量,并对其造成等量伤害。

 

临时性攻防提升

特定的技能也会影响伤害数据:

  • 守护者之盾:

星灵机械哨兵的主动技能,展开一个范围为 4 的力场,范围内所有友军单位受到的远程伤害降低两点。守护者之盾属于临时的、有限制的防御提升。要注意的几点是:

  • 跟攻击单位的位置无关,只跟受伤害的单位是否在范围内有关;
  • 同样,无法防御“魔法攻击”,比如寡妇雷的哨兵导弹;
  • 自己也同样受益;
  • 对友方误伤也同样起效果;
  • 近战攻击不受减免,溅射伤害不受减免(未证实)
  • 蟑螂、刺蛇、宿主的蝗虫等,在与敌方贴脸攻击时,攻击动作会发生变化,属于近战,也不受减免

 

  • 腐化:

腐化者主动技能,使目标在 30 秒内受到的伤害提升 20%。注意的点是:

  • 尽管腐化者是对空单位,但腐化技能也可以对地面单位释放;
  • 腐化后的伤害值是可能有小数点的,综合开篇讲的血量向上取整的特性,显示结果就是多次相同情况下的伤害值可能偏差 1 点;
  • 尽管它也属于技能,但并不直接产生伤害,而是依附于普通攻防,因此也受攻防升级影响;
  • 对技能伤害同样有加成,比如寡妇雷炸被腐化不朽伤害为 192 点。(未证实)

 

  • 定点防御靶机:

铁鸦主动技能,在目标区域投放一个定点防御靶机,击落范围内所有向友方发射的弹道类攻击,每次防御消耗 10 点能量。防御机的工作原理是利用激光破坏飞向友方单位的弹道类攻击,包括人类导弹类攻击、星灵慢速弹道激光等等。注意点包括:

  • 与守护者之盾不同,无关攻击者的位置,甚至也无关友方位置,只要飞弹经过防御范围,定点防御靶机就有作用;
  • 与守护者之盾不同,靶机是直接免除某些类型的攻击,对不免的攻击则无影响,故不存在攻防值计算问题;
  • 与守护者之盾不同,靶机对友方误伤不防御;
  • 基于其工作原理,只对“有弹道、飞弹类”的远程攻击起作用,比如掠夺者、女妖、追猎者、凤凰,飞龙、腐化者,对瞬时远程攻击不起作用,比如机枪兵、不朽等。
  • 对航母的拦截机也有作用;
  • 同样基于其工作原理,雷神和落地维京的对地攻击也无法击落,只能击落雷神和维京的对空飞弹。
  • 所以靶机自身也是可以在不触发防御机制的情况下被击落的;
  • 对于多重攻击,每一重攻击都要消耗一份靶机能量;
  • 靶机能量回复速率比一般单位快,为每秒 1 点,故每存在 10 秒便可额外多击落一次弹道攻击;
  • 靶机属于建筑类型,工程站建筑护甲升级可以为靶机提供额外两点护甲,但它是轻甲,全星际唯一的轻甲建筑;
  • 因为是建筑,所以狙击、腐化、灵能风暴都无效;
  • 但你妈能量反蚀是有效的,一放一个准。50 血 200 能量初始还满能量的家伙……
  • 靶机在原理上属于攻击类,只不过攻击目标是弹道类子弹,所以工程站的研发项目“瞬时自动追踪”,也能让靶机的防御范围 +1,从 8 到 9。
  • 靶机的初始存在时间为 180 秒,最多可以防御 38 次攻击,可升级。升级后存在时间延长到 240 秒,至多防御 44 次攻击;
  • 可以被 SCV 修理,详见另一篇:《星际争霸2建造与修理详解》;
  • 幻像的攻击也会防御,一样消耗能量;
  • 攻防升级与靶机的防御能力无关,三攻子弹和零攻子弹一样防,所以越后期靶机减免的伤害越多,而守护者之盾则固定减伤 2 点,后期效果更弱;
  • 刺蛇、皇后等兵种在与敌方贴脸攻击时,属于近战,此时靶机不起作用
  • 多个靶机不会拦截同一发导弹,他们的防御能力总是累加的;
  • 特别注意:某个版本后,对巢虫领主也不起作用了;

 

结算顺序

参考以下事实:

  • 掠夺者,对重甲 20 点伤害;
  • 不朽者,重甲,拥有刚毅护盾,任何超过 10 点的伤害降低为 10 点,且站在守护者之盾范围内;
  • 掠夺者射出一枪,进入守护者之盾,对不朽造成 8 点伤害。

关于腐化,也有类似的结论:

  • 掠夺者,对重甲 20 点伤害;
  • 不朽者,重甲,拥有刚毅护盾,任何超过 10 点的伤害降低为 10 点,但处于被腐化状态,受伤害增加 20%;
  • 掠夺者射出一枪,对不朽造成 12 点伤害。

更复杂的:

  • 掠夺者,对重甲 20 点伤害,开兴奋剂增加 50% 攻速;
  • 不朽者,重甲,拥有刚毅护盾,任何超过 10 点的伤害降低为 10 点,但处于被腐化状态,受伤害增加 20%;
  • 附近的机械哨兵展开了守护者力场保护不朽者,所有的远程伤害减少 2 点;
  • 掠夺者射出一枪,对不朽造成 10 点伤害。

根据这一事实,我们得出一个伤害结算顺序的概念:

  • 伤害值都是通过顺序,在一个最基础的值上根据条件进行数值修正或数值替换;
  • 首先计算永久性攻防值(兵种属性决定基础数值),在此基础上进行技能性攻防修正(兵种技能影响最终数值);
  • 如果有多个永久性攻防值(兵种属性),先结算无附加条件的,再结算有附加条件的(护甲类型、兵种类型);
  • 如果有多个临时性攻防修正(兵种技能),先结算无附加条件的,再结算有附加条件的(远程、弹道);
  • 在以上四条确定的同一计算顺序内,先结算输出的伤害值(伤害别人的企图),再结算受到的伤害值(受伤害的企图);

比如上述例三:

  1. 首先计算永久性攻防值(兵种属性):掠夺者 10 点伤害,不朽者 0 盾防,伤害企图为 10 点;
  2. 因为不朽者护盾防御值为 0,因此受伤害企图变为 10-0 = 10 点;
  3. 因为掠夺者有两个永久性攻击值,其中 “对重甲 +10” 属于有条件数值,攻击数值修正为 20,防御为 0,伤害企图为 20 点;
  4. 因为不朽者没有针对远程攻击的有条件防御加成,因此受伤害图依然为 20-0 = 20 点;
  5. 判断掠夺者永久性攻击技能,有振荡弹,但对伤害数值无影响,因此伤害企图为 20+0 = 20 点;
  6. 判断不朽者永久性技能,有刚毅护盾,会产生效果,受伤害企图无视源伤害大小,强行修正为 10 点;
  7. 判断掠夺者临时性技能,有兴奋剂,但对伤害数值无影响,伤害企图依然为 10 点;
  8. 判断不朽者临时性技能,有两个,腐化和哨兵盾:
    • 首先结算无附加条件的腐化,受伤害企图修正为 10 x ( 1+20% ) = 12 点;
    • 再结算有附加条件的,哨兵盾只对 “远程攻击” 有效果,使得受到的远程伤害减少 2 点,受伤害企图修正为 10 点;
  9. 无其它条件,最后不朽受到 10 点伤害,进行盾数值、血量和护甲的计算。

如果结算时是先哨兵后腐化,那么不朽的每次伤害只有 (10-2) x 2 = 9.6 点,考虑到血/盾的数值向上取整,我们就会见到单次伤害为 9 点,而非 10 点了。

尽管未作测试,但可以大胆断言,在盾血边界的那次伤害上,星灵可以享受盾防御和护甲防御的双重减免,同时腐化也不会影响到转移到血量上的剩余伤害部分。因为在“伤害企图值→受伤害企图值”那次计算中,腐化的技能已经结算完毕。盾血边界的计算,其实是 “受伤害企图值” 与盾、血数值上的计算,而“受伤害企图值”本身已经包含了腐化的结算。否则就变成了一技能两次结算,属于 bug 了。也就是说三防 200 血 1 盾 4 护甲腐化不朽者,被三攻坦克轰了一炮以后,只也掉 7 滴血,而不会出现 (12-1) * 1.2 – 4 = 9.2 血这种复杂的情况。

谈谈保险

昨天下午,某银行信用卡给我打了半小时电话,推销他们的一项保险业务。一如所有的推销电话一样,最后都只能强行挂机,没办法礼貌地互相结束通话,唉,可怜那业务员。(ノへ ̄、)

这个业务很简单,每月交差不多 140来块钱,直接从信用卡上扣,全年 12 期,总长 5 年,可提前结束。价格据说是根据我的年龄、职业等测算出来的,每个人还不一样。小年青们会便宜一点,老头子们更贵一点。然后收益是什么呢,在业务员啰啰嗦嗦不断夸耀优点的情况下,我总结出来以下几项:

  1. 如果是疾病性住院,则只要住院,无视原因无理由赔付每天 ¥200,提供医院帐单复印件、身份证复印件、信用卡复印件即可。有 500 天上限。
  2. 如果是外伤性住院,则只要住院,无视原因无理由赔付每天 ¥400,提供医院帐单复印件、身份证复印件、信用卡复印件即可。有 500 天上限。
  3. 如果一年无赔付,则反还 25%。
  4. 小病小灾,不住院不算保险范围。

其实就是个无理由住院额外险,无论是天上掉流星砸到脚,或者被加班逼到内出血,反正只要躺医院了,就按价赔偿。……不知道自残算不算。( ̄△ ̄;)

然后我拒了。在电话录音说您是否确认现在通过语音确认开通时及时地拒了。

嗯,这个业务本身其实还可以,如果是我玩轮滑狂热的年代我还真就说不定买了,只不过在现在的境况下,核算了一下成本,基于我的逻辑,最后还是回绝掉了。

首先,其实要知道,保险其实永远是越买越亏的。从逻辑上说,保险是以集合众人的付出,给一些人的损失兜底。如果保险运营不需要成本,理论上对于一个人来说,整体的付出大约等于各种损失乘以机率以后的期望值。但实际上,考虑到保险还要支付销售提成、运营成本、赔付审核、股东分红、物业建设等等,对于一个保险客户而言,单纯考虑支出和损失期望,是肯定不合算的。只有考虑到对损失赋以一个额外的权重,也就是考虑到 “损失超出了你的承受能力” 的情况时,购买保险才是一个合算的行为。

换句通俗的话说,就是我明知道不出意外或者出点小意外都是亏的,出大意外,从概率乘以损失来说我还是亏的,但因为我实在承受不起大损失,万一呢?所以还是买点保险用小损失代替吧。比如汽车保险,其实出事的机率,乘以出事要赔的钱,整体而言还是比汽车保费低,但因为真要出点什么事几十万的钱还是会对个人造成极大的甚至是不能承受的打击,那就还是买吧。

我们举另外一个极端的例子。大家在生活中经常会丢失钢磞儿,几毛一块的,积累下来也有不少,比如每年少则十块,多则一百。现在我推出一项保险,“钢磞丢失险”,每月交五块钱,只要丢钢磞就给你等额赔付。假如不考虑实际执行难度,就假设一点不麻烦,说赔就赔,也没有骗保,你会买么?

如果你说,不买,丢了就丢了吧,说不定丢的还没保险费多,只丢钢磞再多也丢不成穷光蛋的。相信大家都完全能理解这种想法。

事实上,大部分人从保险得到的赔付都是不及保费支出的。也就是说,丢的确实不及保费多的。你每月可能丢一个,可能丢五个,可能丢十个,但参保了就相当于稳定丢五个了。全年一统计,你最多的一个月丢了 8 个,有两个月没丢,大多丢三四个,你就发现自己明显赔了。明年就不必参保了。

但假如我们加一个额外限制,『在丢到某一数量以后,这个巨大损失个人承担不了』。比如开开国国王规定如果国民某个月丢硬币损失超过 10 块要杀头。那显然这份保险就变成了买命钱了,五块?太便宜了。

再举一个极端例子。如果你的全部收入全部交了保险,不管你当前交了多少,付给你的保费永远是固定值。过一两年你肯定就会发现亏大了,还不如自己存钱合算。你觉得呢?

也就是说,保险主要针对的是你自己不能承受的重大损失,对于一些承受无压力的小损失,完全可以考虑自行承担,并不一定需要保险,当然也不是不可以考虑,还是看你的承受能力和损失厌恶度。

所以土豪们是不需要常规保险的。是嘛,汽车三方险最多也就到一百万,对于豪车还是杯水车薪,基本都靠土豪自己兜。

其次,这个险号称最长可达 500 天,20 万上限。其实吧……那是住院啊,谁能住院 500 天啊。就算 5 年 500 天,那也是每年住院三个多月啊……这种情况下,医疗保险、扩展医疗保险,甚至是亲朋好友都比这个保险靠谱。当然,两险是不冲突可以同时用的,但归根结底,这个保险反而更像是一种锦上添花而不是真正可以兜底保障的险种。在收益上其实比不上按比例赔付的常规保险类。

那么如何核算支付与收益呢?很简单,每月不是扣 140 来块钱么?全年 1700 块,每年需要住 5-8 天院才能收回保费。这就是一个比较合适的可比较尺度了,就算出了轻伤车祸,在北京这种常规情况下也很难住够五天院,大多都会让你回家休养。而如果重伤到需要超过 8 天,无差别的每天 200-400 也是杯水车薪,我需要更强的保险覆盖。

只有像我学生时代那样,经常没事出个小伤,进医院擦个药打个板,医生也好说话,总能腾出闲天住院的那种状态,才比较合算。而这个保险又是要审核职业、收入等等因素的,想找他们的 “优质客户”,估计在我学生年代就是劣质客户吧。如果我是他们的“优质客户”,那么他们基本就不是我的“优质保险”。

在保险收益预估时,不能以最高保障额度计算,而是应该以个人实际和保险本身赔付方式决定的“常规”意外预期计算。保险公司算得比你精得多,但只要你从“可收回保费所需要的意外程度”进行反向计算,就比较容易得到可感知可衡量的收益标准了。

再次,当然还有心理上的因素,人们对小概率事件的估计总是偏离常规的,我也是。唯一常见例外的也就是车险了,人们还是很认可驾车意外的概率的。

人们对幸运的小概率总是高估,要不然也没有人大把大把地买彩票了。——你可以认为买小额是图个别的心理幸福感之类的,可幸福感不也是心理因素么?

而对厄运的小概率却偏向于低估,就好比真的计算车祸概率,或大或小几十人里也能摊上一个,相当于一个班级、一个小公司、一个小团体中间总能遇上一项,但每个人都不认为是自己。我也是。作为理性青年,在作必要决策时会考虑到这种情况,而在改善性决策时,这种心理就又会占上风了。就好比我如果需要自费购买人生意外、大病医疗院,和需要自费购买这种改善性医疗险,在决策时的“意外概率”是不一样的……

最后,我非常讨厌这种持续性延后性的扣款,利用的又是人们的遗忘心理和小额支付决策时的心理偏差。个人喜恶,冷暖自知。

,在这里还有一个奇葩的部分,就是反还 25%。一开始我也被绕进去了。要注意这个反还只有在你不产生保费的情况下会出现,被推销员包装成了“实际保费仅 110几元”。

别傻了,如果不出意外没有保险赔偿,多少钱都是白付给保险公司的,保险成本还是应该按出事的计算。换句话说,这个只有银行在核算净收入时有影响,对你核算出意外时的成本与收益是完全没有任何关系的。因为这项条款只有在“不出意外”的情况下才生效,而“不出意外”你拿不到赔付,多少钱都是白送给银行。

而且反过来想想,如果真出了事要住一两天院,你如果去索保,虽然能拿到 ¥200-400,却失去了返还部分近 ¥400 块。这反而会导致你即使真的入院,在两天以内的也不会去找银行赔付,银行照样是净赚。这还真不如直接把保费降一点好呢。这跟车险动态调整保费还不一样,车险意外发生的概率和个人的驾车行为安全意识有很大相关性,而对于住院而言,个人可控制的因素则少很多。另一方面,车险是下一年费用变化,也不涉及历史费用的返还,并且各公司还通用。两者的差距巨大,从这个策略中我更多地看见的是恶劣的算计,而不是对客户的善意。

其实话说回来,如果真的到了个人不能承受的意外,通常一个家庭也就完了。目前看见的保险制度,绝大部分也都有个吝啬的上限,或者有严苛的赔付标准。做得到锦上添花,却做不到雪中送炭。只有市场化的车险好点,医疗保险也只能说呵呵。农业险,包括农业期货,以我外行的角度看来似乎也不错。只是不知道农民兄弟们,身在此间多有经验,是会说哈哈,还是呵呵。

今天刚看了知乎一篇文章,里面是从业人员吐槽人民没有保险意识。

我呸。

少几个推销,少几份算计,少几段夸张措辞,多来点真货。

Adobe 研发中心退出中国

Adobe 公司宣布研发部门撤离中国,仅保留销售部门。不过这篇写的不是新闻评论。新闻只是个引子,想写的……可以认为是评论的评论吧。

老有人以 Adobe 各系东西卖得太贵为借口,总说如果能减个零或者减两个零就好了。

……别傻了,好像卖几十块他们就真会买似的。

定价策略这东西,很复杂,但也很简单。简单在于,最后的收益无非是售价乘以销量减掉成本,让收益最大化就行了。而复杂则在于,定价到底在多大程度上会影响销量,进而影响到最大收益,则是要靠商业智慧与决断的。Adobe 的全系列产品不是免费(Flash、Adobe Reader、Creative Suite 2),就是死贵,看起来似乎不合理,但实际上这个定价差不多已经是最优的策略了。

我们通常说的薄利多销,其实并不一定正确 。这句话有一个先验的假设,就是薄利会引发销量的成倍增加。这在传统实物生产中,在某些场景下可能是正确的。在这个成语产生的年代,大部分商品都是小规模作坊式生产的生活必须品。这使得第一对顾客而言,生活必需品是一定需要经常购买的,自然会在过得去的情况下选择更便宜的。第二是对具体的某个商家而言,市场是近乎无穷大的,更便宜意味着有更多顾客会选择他家的商品,只要产量跟得上,他总能卖出去。第三是,更大的产量意味着基于规模效应,单件成本也更低,依然可以腾出足够的利润空间。

也就是在个人作坊向产业规模化集约化发生的过程中,某个商家通过降低售价,一方面扩展自己的市场范围,一方面惠福顾客。这确实是经济学意义上,也是生活意义上的社会改善。……除了那些被竞争挤倒破产的其它商家以外。

但这几点在 Adobe 身上都不存在。

首先,其实对大部分人而言,Adobe 系列软件都称不上是必须品级别的软件,它的市场并没有大家想像的那么大,相反,是个很小的市场。

并不是因为你在家用了盗版 Photoshop,你就也能被计算为市场份额之一。比如假如苹果手机也能盗版,五块一个,那么大街上肯定差不多也是人人都拿着肾6+,可你能就此认为苹果的市场是 99.99% 么?

不是的,因为定价在那里,苹果的市场就是 3 成左右,这是他利润最大化的定价方式。再降,它的市场就算扩大,也比较有限,还不如降价造成已占市场收入减少的损益大。Adobe 软件实际上是“生产力软件”,简单地说,是拿来做生产的工具之一。影楼、设计所、出版业,这些才是 Adobe 家软件瞄准的市场。对于这些市场的用户而言,几乎是固定而没有选择的。反过来就是说,Adobe 的市场真没有大家想像的那么大,那些鼓吹如果便宜就买的,最后只有两种选择,用盗版,或者不用。他们是不会成为 Adobe 家正版用户的。对真正的市场用户来说,Adobe 家的软件真不贵,几千块钱,还没一个相机镜头贵,没一次书籍制版费贵,没一次比稿的路费贵,真心不贵的。

其次,它已经是市场上的王者了,占据了几乎所有的中高端出版软件市场,实际上并不存在可以被挤倒的“其它商家”以让他成倍地扩大市场份额。

有句话是说,所有你觉得 Photoshop 不好用的功能,都是因为你不会用。——这句话依然阐述的是同一个事实:大部分人并不需要 Photoshop 这样级别的软件,没一点臂力,牛刀使起来还不如指甲刀。而另一个事实则是,在针对的市场里 Adobe 则是毫无疑问的王者,他已经把能拿到的市场差不多都拿到了。

最后,软件成本几乎完全是固定成本,少量边际成本也主要出在营销费用、光盘、下载带宽这类上面。也就是说,单纯的销量带不来规模化导致的成本节约。软件的特殊就在于,它的利润完全就等于销量乘以单份售价,那么在市场容量固定,市场份额近乎百分百的情况下,降低售价只能导致更多的损失。你以为¥5000降到¥50,销量就能提升 100 倍?真要降到 ¥50,那帮人依然会抱怨说别的软件都是免费了,为什么 Adobe 要收费,然后依然用着他们的免费盗版。WPS 就是个活生生的例子,坚持到最后也只能靠免费广告流氓手段来推动。

额外一点,如果 Adobe 真在中国降价,你打算让别的国家市场如何推动?Adobe 是序列号授权,并不是云服务,几乎很难以国别定出悬殊的价格差。如果真这样,最后结果只会导致其它国家的用户通过互联网购买最便宜国家的序列号。只会导致 Adobe 的损失更大。现在它退出一个赚不到钱的国家,顶多通过不出对应的语言版本就可以解决(其实也解决不了,因为新加坡用的也是简体中文)。他也想推过云服务,估计也是想做差别定价(比如暴雪就给中国大陆单独架服务器单独定价,其它国家通过信用卡等方式差别定价),但显然正版用户们并不太接受。

无奈。

软件盗版已经是个老生长谈的话题了,而且观点已经趋向一致,即如果没有那么严重的盗版,中国的软件环境和互联网环境也不至于像现在这么恶劣。盗版伤害的不光是大型软件厂家,更是直接导致中国软件梯度被抹杀,没有了中小企业的生存空间。在这种情况下,依然有那么多人把 Adobe 给出的 “收益不及预期” 的原因,归罪到 Adobe 定价太贵上面,有时候真觉得刁民两字,用得不假。

 

题外话:即使 WPS 目前已经如此,我依然尊敬珠海 WPS 小组,稻壳儿是我在软件相关费用中第三大的一项,仅次于山口山和 App Store。

 

COS 交易酱的简易分词算法

http://cosjiaoyi.com 是个针对 cosplay 交易信息的搜索引擎,目前基本完成了对新浪微博特定信息的采集和入库,以及基于拼接 mysql 查询语句的搜索功能。

接下来还有很多要做的事情,包括扩展采集来源、改善垃圾信息过滤、优化前端等工作。不过今天先来讲一下打算做的自制分词算法。

对于动漫这一特定领域,很多常规的分词算法没有多大意义了。因为动漫圈存在大量的仅仅因为“好玩”而产生的各种昵称、诡称、简称。比如 “身陷” 指的是衣服的尺码,包括身高、三围等数据;比如 “穹妹” 指的是动画《缘之空》中的女主角兼主角他妹;比如 “面交” 当然是指当面交易的简称;比如 “炮萝” “秀萝” “叽萝” “咩萝” “丐萝” 都是网游剑网三里的职业人物角色的简称,哦,剑三又别称 “基三”,等等等等……还有一大现象是中英文数字合并成为一个词汇,比如 “PO主”。另外,又因为拼音输入法的原因,会出现“身限”=“身陷” 等现象。由于本来这些词汇也就是圈子生造词,也难以适用词汇纠错的方法。

wordsegment

普通分词算法结果

对于业务场景而言,这里面的很多词汇都是没有多大意义的。而“黑猫”、“弹丸”、“小天使”之类的词能成为高频词,也仅仅是因为它们更接近于“普通词汇”,可以被常规的分词算法认出来。而剑三当中的 “X萝” 系列,甚至剑三这个词汇本身,都因为被分词算法切分,而没有进入这个图表中。

当然,技术上来说,只要有合理的分词词库,辅以相应的参数调整,还是可以做出一套适用于动漫分词的,但考虑到分词算法往往有几百个核心词汇、几千个附加词汇,每个词汇还都有词性标注,想想就算了……

在之前没考虑过这个问题,一是因为没有实际的业务数据积累,其实也没有考虑到这个问题,经过一段时间的推广和数据积累,现在可以研究了。二是因为对于 mySQL 的 like %…% 语法,尽管效率不高,但在目前的场景下,也没有遭受到多少压力。三是因为,呃,忙,呃,之前没时间做,呃,之后其实也不一定有时间做……

那简易分词打算怎么做呢?

现阶段的核心目的,还是在尽可能减少时间的前提下,能正确理解用户输入的“穹妹旗袍” 是指 “穹妹”+“旗袍”,这两个词汇不一定需要放在一起,只要在同一条微博中出现即可。

  1. 只考虑对 QueryRequest 的 wordstring 进行处理,而不考虑入库数据。内部查询在没有遇到性能问题前,依然使用 like %…%。目的仅仅是为了正确理解用户输入的查询,不至于因为用户输入“剑三的炮萝” 因为无法整串匹配到而搜不到结果,这种情况下应当可以正确处理为“剑三”+“炮萝”
  2. 只分析已经记录在服务器的 SearchQuery 表,不考虑对正文文本的分析。
  3. 和普通分词一样,还是基于词汇表,以及替代词汇表。先考虑分词,再考虑戳憋字。
  4. 基于业务,其实有大量的词汇是不需要的,比如“小天使”、“帮扩”等。对于用户而言,进入到本网站,场景就是搜索需要的东西,因而都是以具像物名词为主。没人会在搜索框里填 “小天使”、“随便” 这类词汇。甚至 “二手” 这个词都基本不会出现。

所以简易规则如下,可以覆盖 99% 的实际需求,计算量很小。

  1. 先对 querystring 根据 devide-word-list 进行切分,devide-word-list 包括 “ ”(半角空格)、“ ”(全角空格)、“的”,可能还会添加。但注意,没有“和”。因为在动漫中,“和”字会经常作为成词字或者人物姓名字出现,不能定义为分隔符。“日和” “和服” 等。由于并不是真的基于语义的分词,所以无法判断是成词字还是分隔字,根据历史搜索统计,只有 4% 的“和”是分隔词,交给用户自己调整没有问题。由于 devide-word-list 的数据量非常少,添加也很谨慎,所以可以直接写进代码里。
  2. 对结果的 querystring[] 根据 known-word-list,切分为 已知词块 未知词块 的数组。known-word-list 的生成办法下述。
  3. 对于未知词块,根据词块长度,反复循环,进行简单切分,直到最细:
    • 词块中有英文串的,当英文串字母小于等于两个时,视为一个汉字,不切分。
    • 当英文串大于两个汉字的,视为一个词,对其前后切分,分为三段。
    • 当有数字现出时,视为一个词,对其前后切分,分为三段。
    • 对大于等于四字的词块,直接按两个字一词进行切分。(注意之前把1/2 个英文字母视作一个汉字,这里和中文等同视之)。
  4. 将结果的 querystring[] 数组,按每词之间 and 关系,对数据库进行查询。

比如:求黑子的篮球的火神大我红毛 私信po我。

  1. 变为 [求黑子,篮球,火神大我红毛,私信po我] (“的”和“空格”为分隔词)
  2. 变为 [求,黑子,篮球,火神大我红,毛,私信po我](“求”和“毛”为定义的已知词。
  3. 变为 [求,黑子,篮球,火神,大我,红,毛,私信,po我](“po”视为一个汉字)
  4. 实际 Search Request 即 求+黑子+篮球+火神+大我+红+毛+私信+po我

当然,通常没有这么长的需求,更多的是如下的:

剑三定国炮萝160包邮

  1. 还是 [剑三定国炮萝160包邮],因为没有分隔。
  2. [剑三,定国炮萝160,包邮],剑三和包邮应该会加作已知词。
  3. [剑三,定国炮萝,160,包邮],160 是数字单独成词。
  4. [剑三,定国,炮萝,160,包邮],“定国炮萝” 四字切分为两字两字。这就是最后的搜索请求词组了。

当然,在这种规则下,“雪露女武神”,会变成 “雪露”、“女武”、“神”,但又有什么关系呢,如果一个词汇真的必须是三字词,我使用两字“女武”+一字“神”,依然可以搜到库中的正确结果,这就足够了。

接下来说说怎么做已知词汇表。

  1. 人工整理,优先满足历史搜索词汇表。
  2. 暴力分词法:
    1. 直接把采集数据的正文部分全部提取。
    2. 把空格串、数字串、大于两字的英文串、符号串、链接地址、转发标志、“的” 等都视为分隔符替换掉,形成最初的文本块。
    3. 暴力地把所有文本块全部循环切分成两字一组,然后统计相同词汇并排序。注意是循环切分,比如“小天使”块应当切分成“小天”和“天使” 两部分。
    4. 再暴力地把所有文本块全部切分成三字一组,然后统计相同词汇并排序。
    5. 人工筛选排序最前的 1000 组词汇,足够了。

这个当然也有缺点,但在具体的场景下也已经够用了。动漫词汇尽管创意繁多,但基于交流之用,最常用的词汇还是排名比较靠前的。再通过人工筛选过滤掉通用但无用的词汇,就可以获得有效的 know-word-list 了。这种暴力切分法并不是我第一个用,http://www.guokr.com/blog/76814/ 这篇文章就讲到了用暴力切分法去切分古文词汇。其实还是挺实用的,毕竟我这里最后有个人工筛选过程。

====================下面是严肃的分割线=====================

其实中文分语无非两个部分:

  1. 句子匹配已登录词时,多种匹配方式如何选取最优?
  2. 未登录词如何鉴别词性?这中间又细分为如何通过大量文本收录未登录词,转为已登录词;以及在某段文本中确切出现的未登录词,如何进行处理两种情况。

而以上方案,按分词原则来看,其实都有些反其道而行:

  • 故意适当缩小已登录词,相对扩大未登录词范围。——这个是由业务实际情况经取舍后决定的。确实业务中只需要登录掉大部分 “专属通用词” 即可达到需要的效果。
  • 未登录词一律视为双字词。——这个是实际统计结果后决定的。
  • 把输入的正确性,以及由此导致的结果的正确性,责任全部交给用户自己去解决。——也是业务实际,以及以上两项得到的。

这些简化的前提,最重要的一点在于,Cos 交易酱并不需要真的去处理分词以后的结果,只需要确保分词结果能正确地,范围合适地在库中搜索到用户需要的数据即可。

Cos交易酱搜索优化计划

COS 酱的核心功能是促成 cosplay 交易信息的交换流通,所以对信息的采集、整理和优化是最重要的部分。

经过一段时间的数据积累,目前数据库中已有 60000 多余条微博数据,过滤掉的垃圾信息已经有 20000 多条。我们使用最简单的规则过滤掉了三分之一的无用信息,这些信息绝大部分来自于机器控制的僵尸帐号微博。但目前而言,过滤完的信息依然有不少可以定义为重复、垃圾的信息,所以接下来准备对过滤规则进行一些改进。

1. 微博中有“微话题主持人”这样一种角色,我完全不知道有什么存在意义。他们做的唯一的工作就是把同话题下大量的微博重新转发一下,并在转发时再次添加同一个话题,比如 #cos 二手#。由于本站对微博采集就是基于话题、关键词等方式进行的。因而这些无用的微博也会被搜索到。接下来打算通过帐号黑名单方式过滤这一类需求。

2. 热门微博的多次转发。在最初设计这个网站时,考虑过是否采集转发的,最后确定是包括转发也采集。因为存在二手信息发布者通过转发微博的方式提供一些补充信息,比如 “xx 已经出售,还剩 yy、zz” 等。但当初考虑不周的是,某些粉丝众多的红人 cos 的微博存在大量转发,会导致采集到的数据也出现 “刷屏”。另一种是专业转发号在某一时段大量转发,也会出现类似的情况。基于此,接下来需要添加一些入库规则过滤。

2.1 如果一条转发微博的原微博不在库内,则转发微博和原微博都入库。
2.2 如果原微博已有,转发微博用户和原微博用户相同,则视为对原微博的“补充说明”,也入库。
2.3 如果原微博已有,转发用户非原博用户,则入库标记为垃圾微博。
2.4 如果原微博已有,转发微博用户和原微博用户相同,且转发内容为“转发微博”,则入库标记为垃圾微博。

——以上:原则是,对于搜索引擎而言,多条相同信息是无意义的,只需要保留确实有意义的内容即可。

3. 对于搜索请求,由于动漫这个特定领域的分词非常难做。目前网站的搜索是没有分词的,于是导致“火神毛”的搜索请求并没有表达用户本来的意思 “火神+毛”。接下来会研究一下如何变通地实现这个需求。