善用表驱动法

  最近碰到个需求,计算游戏得分的规则,类似这样:


游戏人数


第一名获得赌注


第二名获得赌注


第三名获得赌注


第四名获得赌注


二人


100%


0%




二人(出现2个第1名时)


50%


50%

 

 


三人


70%


30%


0%



三人(出现3个第1名时)


33.3333%


33.3333%


33.3333%

 


三人(出现2个第1名时)


50%×2


0%

 

 

......
......
    这些奖励规则没有什么规律,随着人数增多,就越发复杂了,并且业务人员可能随时改变这些规则。
    显然,奖励规则可以采用策略模式,定义策略接口,根据游戏人数定义不同的规则,本质上就是利用动态的多态调用。可以想见,还是少不了复杂的case...when语句,以及繁多的代码。恰好最近读《unix编程艺术》和《代码大全2》,两者都提到一个结论:人类阅读复杂数据结构远比复杂的控制流程容易,或者说数据驱动开发是非常有价值的。《代码大全2》声称这个是表驱动法。因此,这个奖励系数的计算,能否转化成一个查表过程呢?注意到,在游戏中,名次是根据个人的积分在所有玩家中的排位来决定,大概会有这么个排序的玩家积分数组[100,50,3],这个数组表示3个玩家,第一名100分,第二名50分,第三名才3分。依据规则,第一名的奖励系数就是0.7,第二名就是0.3。我想到类似这样的数组其实都有个形式表示它们的内置结构,比如[100,50,3]数组的“结构”是"111",代表3个位置都有一个人。将"111"作为关键码去查表不就OK了?
    将每个排好序的积分数组解码为这样的关键码,然后去查预先写好的奖励系数表,这个奖励系数表大概类似:

  @@award_rate_hash={
    :"2"=>{
      :"11"=>{:"1"=>1,:"2"=>0},
      :"20"=>{:"1"=>0.5,:"2"=>0.5}
    },
    :"3"=>{
      :"111"=>{:"1"=>0.7,:"2"=>0.3,:"3"=>0},
      :"300"=>{:"1"=>0.33},
      :"201"=>{:"1"=>0.5,:"3"=>0},
      :"120"=>{:"1"=>1,:"2"=>0}
    },
    :"4"=>{
      :"1111"=>{:"1"=>0.65,:"2"=>0.30,:"3"=>0.05,:"4"=>0},
      :"4000"=>{:"1"=>0.25},
      :"3001"=>{:"1"=>0.33,:"4"=>0},
      :"1300"=>{:"1"=>1,:"2"=>0},
      :"2020"=>{:"1"=>0.5,:"3"=>0},
      :"1201"=>{:"1"=>0.7,:"2"=>0.15,:"4"=>0},
      :"1120"=>{:"1"=>0.7,:"2"=>0.3,:"3"=>0},
      :"2011"=>{:"1"=>0.35,:"3"=>0.3,:"4"=>0}
    }      
  }

    一个三级hash表,首先根据玩家人数查到特定的系数表,比如要查3个玩家、积分数组是[100,50,3]的奖励系数表就是  @@award_rate_hash[:"3"],然后积分数组[100,50,3]解码为:"111",继续查,如此规则的奖励系数表就是@@award_rate_hash[:"3"][:"111"]——也就是 {:"1"=>0.7,:"2"=>0.3,:"3"=>0},那么查积分是100的玩家系数就是@@award_rate_hash[:"3"][:"111"][([100,50,3].index(100)+1).to_s.to_sym],也就是:"1"=>0.7。[100,50,3].index(100)+1就是积分100的玩家在数组中的名次(即1),也就是:"1"指向的结果0.7。其他玩家的查表过程与此类似,不细说了。
    这样,我们所有的奖励规则就是维护这么一张hash表,这个表看起来复杂,其实完全可以自动生成,让业务人员来提供样例数据,解码样例数据并生成这个表是很简单的事情。积分数组的“解码”,我给一个Ruby版本:

   #解码数组为字符串关键码
  def decode_array(array)
    len=array.size
    trace_list=[]
    result=[]
    len.times do |time|
      result[time]=0   
      trace_list[time]=false
    end
    array.each_with_index do |item,index|
      result[index]=count_times(array,trace_list,index,len)
    end
    return result.join('').to_sym
  end
  def count_times(array,trace_list,index,len)
    item=array[index]
    result=0
     (index..len).each do |i|
      if array[i]==item and !trace_list[i]
        result+=1
        trace_list[i]=true
      end
    end
    return result
  end

文章转自庄周梦蝶  ,原文发布时间2008-04-17

时间: 2024-08-30 14:23:31

善用表驱动法的相关文章

表驱动法应用的难点

好的代码总是将复杂的逻辑以分层的方式降低单个层次上的复杂度.复杂与简单有一个相互转化的过程. 1. 表驱动法 在涉及编码解析的功能时,常常有一个带有长长一串case的switch,而且会不断增长.为每一个case搞个类就太夸张了,还是用表驱动(Table Driven)来取代它吧.这种应用已经太多了.而让大家不去用的原因可能就一个,认为表无法表达出属性的差异. 比如一般而言有下面这样一个操作,大家肯定会觉得用表处理没有问题.    Operator         Function    add

深入数据驱动编程之表驱动法的详解_unix linux

数据驱动编程之表驱动法 本文示例代码采用的是c语言.之前介绍过数据驱动编程<浅谈:什么是数据驱动编程的详解>.里面介绍了一个简单的数据驱动手法.今天更进一步,介绍一个稍微复杂,更加实用的一点手法--表驱动法.关于表驱动法,在<unix编程艺术>中有提到,更详细的描述可以看一下<代码大全>,有一章专门进行描述(大概是第八章).简单的表驱动:<浅谈:什么是数据驱动编程的详解>中有一个代码示例.它其实也可以看做是一种表驱动手法,只不过这个表相对比较简单,它在收到消

表驱动方法

1:什么是表驱动法.      表驱动法是一种编程模式(Scheme),从表里面查找信息而不使用逻辑语句(if 和case) 它的好处是消除代码里面到处出现的if.else.swith语句,让凌乱代码变得简明和清晰.对简单情况而言,表驱动方法可能仅仅使逻辑语句更容易和直 白,但随着逻辑的越来越复杂,表驱动法就愈发有吸引力. 2:表驱动法的例子演示     假设有段程序要计算某年某月的天数     通常的做法如下:   1private void btnCalculate_Click(object

等价类结和判定表的软件测试方法应用

摘要:软件测试的类型通常分为白盒测试和黑盒测试,其中基于等价类的划分法与基于判定表的测试法都是较为典型和实用的黑盒测试技术方法.在实际工作中,为了使测试用例的覆盖更加全面,测试目的更加明确,通常不仅仅局限于某一种测试手段.针对等价类和判定表这两种方法各自的特点,可以将两者有机结合,通过对输入条件进行等价类划分,对输出行为进行判定表列举,用综合的手段进行软件测试工作,从而达到使测试用例的设计覆盖全面.条理清晰的目的. 关键词:等价类:判定表:软件测试 1.概述 软件测试的类型一般来说,可以划分为白

C++函数指针详解

来源:http://www.cnblogs.com/ggjucheng/archive/2011/12/13/2286391.html 指针的概念 指针是一个特殊的变量,它里面存储的数值被解释成为内存里的一个地址.要搞清一个指针需要搞清指针的四方面的内容:指针的类型,指针所指向的类型,指针的值或者叫指针所指向的内存区,还有指针本身所占据的内存区.让我们分别说明.  先声明几个指针放着做例子:   int *ptr; char *ptr; int **ptr; int (*ptr)[3]; int

构件

构件是系统中实际存在的可更换部分,它实现特定的功能,符合一套接口标准并实现一组接口.构件代表系统中的一部分物理实施,包括软件代码(源代码.二进制代码或可执行代码)或其等价物(如脚本或命令文件).在图中,构件表示为一个带有标签的矩形. 目录 机械领域 软件工程中的构件 软件构件应具备以下属性 2.1.3 构件的特点 差别 计算机领域 概述 使用 实施操作 选择算法 实施状态 实施关联关系 实施属性 评估代码 其他定义 展开 机械领域 软件工程中的构件 软件构件应具备以下属性 2.1.3 构件的特点

黑盒测试——测试准备阶段

黑盒测试--测试准备阶段 1.概述 1.1 黑盒测试的概念 黑盒测试(black box test)也称功能测试,它是通过测试来检测每个功能是否都能正常使用.在测试中,把程序看作一个不能打开的黑盒子,在完全不考虑程序内部结构和内部特性的情况下,在程序接口进行测试,它只检查程序功能是否按照需求规格说明书的规定正常使用,程序是否能适当地接收输入数据而产生正确的输出信息.黑盒测试着眼于程序外部结构,不考虑内部逻辑结构,主要针对软件界面和软件功能进行测试. 黑盒测试是以用户的角度,从输入数据与输出数据的

Code Complete — 创建高质量的代码

本文将从变量,语句,代码块,子程序,到类以及框架设计,详细描述了如何编写高质量的程序.尽管大部分原则你可能都知道了,但还是有些点会带给你惊喜. 变量 变量初始化原则 声明的时候初始化 在靠近变量第一次使用的位置初始化,就近原则. 理想情况下,在靠近第一次使用变量的位置声明和定义该变量,但是在JS里面却习惯将变量声明提前. 注意计数器和累加器的修改. 在类的构造函数中初始化数据成员 确定是否需要重新初始化 把每个变量用于唯一用途 #### 变量作用域优化 作用域指变量在程序内的可见和可引用范围.介

简单编译器前端的实现

一个简单编译器前端的实现 小记: 其实这个程序是编译原理这门课的综合实验,前段时间我申请免试又失败了,原因是有缺课,平时分不够,早上赖床现在尝到苦果我也是醉了--没办法,逼上梁山,只好攻克这个大boss以拿下免试资格. 选了一个最简单的文法,分析了1个多星期,终于决定开始要写的时候时间已经很紧了. 去实验室通宵了一晚,在宿舍熬了一晚,睡了3个小时就起来去验收了.还好是通过了,没白费劲. 不得不说,编译原理就是烧脑,知识点都比较抽象,如果数据结构和算法的基础打得不牢的话,实现起来会感到吃力. 再次