“算命”,是一种迷信,我父亲那一辈却执迷不悟,有时深陷其中,有时为求一“上上签”,甚至不惜重金,向“天神”保佑。我曾看到过有些算命网站,可以根据人的生辰八字,来求得这个人一生的财运、桃花运,如果第一卦算得不好,还可以向“天神”“请愿”(充钱),再算一卦,直到达到好运为止。
作为一个深信唯物辩证法的人来说,这些东西当然是不信。
但仔细口味,发现这些东西其中需要有些科学道理。我可以将算命总结为以下“三要素”:
如果将算命当作一个函数,那它的输入无疑是姓名、其它个人信息和诚心。,输出就是一个分数(0-100),可以用下图的代码表示:
intdestinyScore=f(name,otherPersonalInformation,faith);下面,我将用.NET实现这个“算命”的功能。
如果只以姓名作为输入,那么这个函数可以简化为:
intdestinyScore=f(name);这可能就好办多了,如.NET中的.GetHashCode(),即可快速获取一个字符串的哈希值,这个哈希值应该是固定的(吗?),该值的取值范围是int.MinValue-int.MaxValue。因此最简单的办法,可以先可以通过对100求模,此时的取值范围是-99~99;然后再取绝对值+1即可,代码如下:
intGetForturn(stringname){ returnMath.Abs(name.GetHashCode()%100)+1;}在.NETFramework4.8中运行,可以算出我(周杰)的得分固定为15分。
在.NETCore中,这个算法每次重新运行,算出的结果都不同,因为.NETCore为了确保安全性,在应用程序启动时,会随机生成一个字符串哈希值种子,因此每次exe运行,哈希值都会变,文档是这么说的:
很显然,这不符合“一致性”,看来想简单地通过GetHashCode()快速“算命”的想法落空了,只能使用标准的哈希算法。
当然,使用如此简单的算法,客户知道了,可能也不太情愿消费更多的“诚意金”了。
哈希算法可以给任意长度的字符转换为一串二进制数组,也就是哈希值。.NET内置了许多不同的哈希算法可供选择:
我们要指定一点点“天机”(加盐),但“天机不可泄露”,因此简单地MD5等单纯哈希算法排除;
我们要转化为一个整数,最大的整数类型,long/Int64,为64位,而最小的内置哈希算法,MD5,就已达128位。因此也要排除HMACSHA等“加盐”哈希。当然这些哈希值也可以手动截取部分长度,但安全性是个问号(也受强迫症影响)。
所以最终我们选择了Rfc2898DeriveBytes,该算法可以生成任意指定长度的哈希值。这个类的构造函数要求输入一个盐值和迭代次数,在这个示例中我们取一个别人不知道的值(代码中写死了,你们假装不知道,你们想用这个代码时可以改改)。可以写出如下代码:
intGetForturn(stringname){ using(varh=newRfc2898DeriveBytes(name, salt:newbyte[8]{44,2,3,4,5,6,7,8}, iterations:10086)) { return(int)(BitConverter.ToUInt64(h.GetBytes(8),0)%100)+1; };}可见算出一卦80分以上的“上签”,已经非常不容易了。我从网上自动生成了888个姓名,然后调用该函数,发现得分超过90分“上上签”标准的,只有83个,相同于十分之一,符合分布特点(详情见Github上的代码)。
通过以下代码,可以算出“狗二”是48分,“狗三”是96分,可见一字之差相差甚远:
GetForturn("狗二").Dump();//48GetForturn("狗三").Dump();//96完整算法最后,依葫芦画瓢,加上个人信息参数(生日)和“诚意金次数”,完成最后的算法:
intGetForturn(stringname,DateTimebirthDay,intfaithCount){ using(varh=newRfc2898DeriveBytes(name+birthDay+faithCount, salt:newbyte[8]{44,2,3,4,5,6,7,8}, iterations:10086)) { return(int)(BitConverter.ToUInt64(h.GetBytes(8),0)%100)+1; };}然后又是“狗二”和“狗三”,加上他们的生日参数后,默认他们的得分是95分和3分:
GetForturn("狗二",newDateTime(1994,5,17),0).Dump();//95GetForturn("狗三",newDateTime(1996,11,3),0).Dump();//3但狗三经过1次“诚意金”后,也求得了高达99分以上的“上上签”:
GetForturn("狗二",newDateTime(1994,5,17),0).Dump();//98GetForturn("狗三",newDateTime(1996,11,3),).Dump();//99最后的话Rfc2898DeriveBytes非常有用,本文说了Rfc2898DeriveBytes的一种使用场景,相信各位在工作当时也经常会有机会去接触它。