表驱动法,逻辑控制优化利器

简介: 最近好多同学在开发过程中谈到设计表结构的一些idea,为了让大家少走一些弯路,今天就计划聊聊表驱动法吧~

最近好多同学在开发过程中谈到设计表结构的一些idea,为了让大家少走一些弯路,今天就计划聊聊表驱动法吧~


1、概念介绍


表驱动法 是一种编程模式,从表里查找信息而不使用逻辑语句(if/else)


事实上,凡是能通过逻辑语句来选择的事物,都可以通过查表来选择。


对简单的情况而言,使用逻辑语句更为容易和直白,但随着逻辑链的越来越复杂,查表法也就愈发显得更具有吸引力。

 

应用原则


适当的情况下,采用表驱动法,所生成的代码会比复杂的逻辑代码更简单,更容易修改,而且效率更高。

 

2、应用实践


2.1 直接访问

 

2.1.1 今天周几?


传统写法:

String today = "周日";
Switch( dayForMonth % 7 ){
    case 0 : 
        today = "周日";
    case 1 : 
        today = "周一";   
    case 2 :
        today = "周二";   
    case 3 :
        today = "周三";   
    case 4 :
        today = "周四";   
    case 5 :
        today = "周五";   
    default:
        today = "周六";   
}


表驱动法:


String [] weekday=newString[]{"周日","周一","周二","周三","周四","周五","周六"};

Stringtoday=weekday [ dayForMonth%7 ];

 

2.1.2 每个月多少天?


传统写法:

if(1 == iMonth) {
  iDays = 31;
} else if(2 == iMonth) {
  iDays = 28;
} else if(3 == iMonth) {
  iDays = 31;
} else if(4 == iMonth) {
  iDays = 30;
} else if(5 == iMonth) {
  iDays = 31;
} else if(6 == iMonth) {
  iDays = 30;
} else if(7 == iMonth) {
  iDays = 31;
} else if(8 == iMonth) {
  iDays = 31;
} else if(9 == iMonth) {
  iDays = 30;
} else if(10 == iMonth) {
  iDays = 31;
} else if(11 == iMonth) {
  iDays = 30;
} else if(12 == iMonth) {
  iDays = 31;
}


表驱动法:


把逻辑写成 map 或是 list,一目了然,可以搞个2维数组还加上了闰年的逻辑。

const monthDays = [
  [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31],
  [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
]
function getMonthDays(month, year) {
  let isLeapYear = (year % 4 === 0) && (year % 100 !== 0 || year % 400 === 0) ? 1 : 0
  return monthDays[isLeapYear][(month - 1)];
}
console.log(getMonthDays(2, 2000))


2.2 索引访问


有时只用一个简单的数学运算还无法把 age 这样的数据转换成为表键值,这种情况可以通过索引访问的方法加以解决。

 

索引应用:先用一个基本类型的数据从一张索引表中查出一个键值,然后在用这一键值查出需要的主数据。


举例:


有100件商品,商店物品编号(范围 0000-9999)


创建两张表:索引表(0-9999),物品(查询)表(0-100)

 

微信图片_20220609123150.png

 

索引访问有两个优点:


  • 如果主查询表的每条记录都很大,那创建一个浪费了很多空间的数组所用的空间,要比建立主查询表所用的空间小得多。


  • 操作索引中的记录比操作主查询表的的记录更方便,编写到表里面的数据比嵌入代码的数据更容易维护。


2.3 阶梯访问


这种访问方法不像索引结构那样直接,但是它要比索引访问方法节省空间。


阶梯结构的基本思想:表中的记录对于不同数据范围有效,而不是对不同的数据点有效。

 

微信图片_20220609123153.png

 

举例:


一个等级评定的应用程序,其中“B”记录所对应的范围是 75.0%-90.0%


>= 90.0%   A


<90.0%     B


<75.0%     C


<65.0%     D


<50.0%     F

 

这种划分范围用在查询表中是不合适的,因为你不能用简单的数据转换函数来把表键值转换成 A-F 字母所代表的等级。用索引也不合适,因为这里用的是浮点数。

 

在应用阶梯方法的时候,必须谨慎的处理范围的端点。

Dim rangeLimit() As Double = {50.0, 65.0, 75.0, 90.0, 100.0}
Dim grade() As String={"F", "D", "C", "B", "A"}
maxGradeLevel = grade.Length - 1
// assign a grad to a student based on the student's score
gradeLevel= 0 
studentGrade = ”A" 
while( studentGrade = "A" and gradeLevel < maxGradeLevel )     
  if( studentScore < rangeLimit( gradeLevel ) ) then         
    studentGrade = grade ( gradeLevel)     
  end if    
  gradeLevel = gradeLevel + 1 
wend

与其他表驱动法相比,这种方法的优点在于它很适合处理那些无规则的数据。


在使用阶梯访问时需要注意的一些细节:


1)留心边界端点


注意边界:< 与 <=,确认循环能够在找出最高一级区间后恰当地终止。


2)考虑用二分查找取代顺序查找


如果列表很大,可以把它替换成一个准二分查找法,从头查找是很耗费性能的


3)考虑用索引访问来取代阶梯访问


阶梯访问中的查找操作可能会比较耗时,如果执行速度很重要,那可以考虑用索引访问来取代阶梯查找,即以牺牲存储空间来换取速度。

 

2.4 构造查询键值


如上述例子,我们希望能够将数据作为键值直接访问表,这样既简单又快速。


但是问题或者数据通常并不是这样友好,那就需要引出 构造查询键值 的方法。


费率与年龄、性别、婚姻及交费年数等不同情况而变动。

微信图片_20220609123156.jpg

 

1)复制信息从而能够直接使用键值


age补齐:50 岁以上的年龄都复制一份 50的费率


这样优点在于表自身结构非常简单那,访问表的逻辑也很简单;


缺点在于复制生成的冗余信息会浪费空间,也即是利用空间换效率。


2)转换键值以使其能够直接使用


费率表查询时,用一个函数将 age 转换为另一个数值。


在此例子中,该函数必须把所有介于 0-5 直接的年龄转换成一个键值,例如 5,同时把所有超过 50 的年龄都转换成另一个键值,例如 50。


这样在检索前可以用 min()和 max()函数来做这一转换。


例如,你可以用下述表达式:max(min(50, age), 17) 来生成一个介于 17-50 之间的表键值。


3)把键值转换提取城独立子程序


  如果你必须要构造一些数据来让它们像表键值一样使用,那就把数据到键值的转换操作提取成独立的子程序。这样可避免在不同位置执行了不同的转换,也使得转换操作修改起来更加容易。


任务是个方法,不再是数值了,这里我们可以利用 Dart 这样的支持高阶函数的语言特性,把方法当做一个对象存储在表中。
var data = <String, Map>{
    "A": {
      "name": "AA",
      "action": (name) => print(name + "/AA"),
    },
    "B": {
      "name": "BB",
      "action": (name) => print(name + "/BB"),
    },
  };
  var action = data["A"]["action"];
  action("kk");

 

3、总结


1)如何从表中查数据?


  • List item


  • 直接访问


  • 索引访问


  • 阶梯访问


2)在表里存些什么?


  • 数据


  • 动作(action)-描述该动作的代码/该动作的子程序的引用。

 

表驱动法提供了一种复杂的逻辑和继承结构的替换方案。如果你发现自己对某个应用程序的逻辑或者继承关系感到困惑,那是否可以通过一个查询表来加以简化。


  • 使用表的关键决策是决定如何去访问表,可以采取直接访问、索引访问或阶梯访问


  • 使用表的另一项关键决策是决定如何去把什么内容放入表中


  • 需要保存浮点数和范围数时,使用阶梯访问的形式。

Thanks for reading!

相关文章
|
3月前
|
弹性计算 关系型数据库 Serverless
告别资源瓶颈,函数计算驱动多媒体文件处理方案:https://www.aliyun.com/solution/tech-solution/fc-drive-file
本文介绍了一种基于阿里云的一键部署解决方案,利用云服务器ECS、RDS MySQL、OSS、函数计算FC及MNS等服务,实现高效的多媒体文件处理。方案通过事件驱动机制,将文件处理任务解耦,并自动弹性扩展,按需付费,简化部署流程,提高处理效率。本文还提供了详细的部署步骤与体验反馈,展示了从配置到文件处理的全过程。
|
5月前
|
存储 SQL 运维
MSSQL性能调优精要:索引深度优化、查询高效重构与并发精细控制
在MSSQL数据库的运维与优化领域,性能调优是一项复杂而细致的工作,直接关系到数据库的稳定性和响应速度
|
监控 API C++
驱动开发:文件微过滤驱动入门
MiniFilter 微过滤驱动是相对于`SFilter`传统过滤驱动而言的,传统文件过滤驱动相对来说较为复杂,且接口不清晰并不符合快速开发的需求,为了解决复杂的开发问题,微过滤驱动就此诞生,微过滤驱动在编写时更简单,多数`IRP`操作都由过滤管理器`(FilterManager或Fltmgr)`所接管,因为有了兼容层,所以在开发中不需要考虑底层`IRP`如何派发,更无需要考虑兼容性问题,用户只需要编写对应的回调函数处理请求即可,这极大的提高了文件过滤驱动的开发效率。
361 0
|
SQL 关系型数据库 API
基于C#的ArcEngine二次开发37:循环查询过程的内存管理与性能优化(三)
基于C#的ArcEngine二次开发37:循环查询过程的内存管理与性能优化
基于C#的ArcEngine二次开发37:循环查询过程的内存管理与性能优化(三)
|
NoSQL 数据处理 C#
基于C#的ArcEngine二次开发52:GDB数据处理过程中与Name相关的操作
基于C#的ArcEngine二次开发52:GDB数据处理过程中与Name相关的操作
基于C#的ArcEngine二次开发52:GDB数据处理过程中与Name相关的操作
|
BI 数据处理 Scala
报表统计_执行框架_旧模块改造 | 学习笔记
快速学习报表统计_执行框架_旧模块改造
115 0
报表统计_执行框架_旧模块改造 | 学习笔记
|
SQL 存储 安全
基于C#的ArcEngine二次开发37:循环查询过程的内存管理与性能优化(二)
基于C#的ArcEngine二次开发37:循环查询过程的内存管理与性能优化
|
存储 C#
基于C#的ArcEngine二次开发37:循环查询过程的内存管理与性能优化(一)
基于C#的ArcEngine二次开发37:循环查询过程的内存管理与性能优化
|
NoSQL 数据管理 数据库
GDB索引:更快的速度探索互联数据的奥秘
阿里云图数据库GDB对于用户点边的每一个属性都会自动创建索引信息,减少用户额外的索引操作。同时,用户也可以根据访问场景通过GDB支持的标准图查询语言Gremlin动态地创建复合索引、唯一索引和点中心索引等其它类型的索引来加速特定的查询。
1616 0
GDB索引:更快的速度探索互联数据的奥秘