数据结构与算法--代码完整性案例分析

确保代码完整性

  • 在撸业务代码时候,经常面对的是接口的设计,在设计之初,我们必然要先想好入参,之后自然会有参数的校验过程,此时我们需要把可能的输入都想清楚,从而避免在程序中出现各种纰漏。但是难免面面俱到,有什么办法能覆盖整个入参的校验呢,我们可以考虑黑盒测试,用测试用例去覆盖,我们编码之前考虑测试用例,如果能先设计黑盒测试的测试用例,那么自然我们需要注意的校验点就出来了。我们一般都从功能测试,边界测试,负面测试三方面设计测试用例,确保代码的完整性。

错误的处理

  • 我们在编码过程中,会遇到各种各样的异常情况,我们有3中方式将错误信息床底给函数的调用者。
    • 第一种,函数的返回值来告知调用者是否出错,比如我们在API设计的时候,给定返回对象中有一个状态,用状态引擎的方式给定调用者具体的调用成功与否,并且在返回值中给定错误对象信息,这种方案最大的问题是使用不方便,因为我们不能直接吧计算结果给调用方,同时也不能将函数的计算结果直接作为参数传递给其他函数。
    • 第二种,定义一个全局变量,在发生错误时候,我们设置全局变量值为某个特殊值,这种方法比第一种要方便,因为调用者可以直接使用返回值,我们可以设计一个特定的函数用来判断返回值的分析,判断是否错误。这种方案也有问题,在于全局变量可能被人遗忘,因为N多个调用方,我只关注你给的调用结果,你的实现多调用方应该透明。
    • 第三种,异常,这是微服务调用中常用的方式,当函数运行异常时候,我们抛出一个自定义异常。函数调用者更具异常信息就能知道错误原因,从而做相应处理。这种问题在于抛出异常会打乱正常的执行顺序,对程序的性能会有影响。
数值的整数次方
  • 案例:实现函数 double power(double base, int exponent), 求base的exponent次方,不能使用函数库,同时不需要考虑大数问题。

  • 在java.lang包中有pow函数可以用来求乘方,以上问题求解类似于pow函数的功能,初见题目其实非常简单,可以用一个循环得出如下代码:

public static double power(double base, int exponent){if(base == 0 || base == 1){return base;}if(exponent == 0){return 1d;}if(exponent == 1){return base;}double result = 1.0;for (int i = 0; i < exponent; i++) {result *= base;}return result;}
  • 指数次幂的大小,直接左幂次的乘法,如上只考虑了正整数次幂显然不是正确的解法,我们可以有如下优化
/*** 求解一个数base 的exponent次幂* */public static double power(double base, int exponent){if(base == 0 || base == 1){return base;}if(exponent == 0){return 1d;}if(exponent == 1){return base;}double result = 1.0;for (int i = 0; i < Math.abs(exponent); i++) {result *= base;}if(exponent > 0){return result;}return 1/result;}
  • 如上实现方案中,已经考虑到了边界值,正负次幂情况,基本上是一个健全的实现方案,但是还有优化的空间,例如,我们在double的比较中,直接判断的是base == 0,此处是有问题的,因为在计算机内标识小数的时候,(double和float类型小数)直接用 这种方式判断是误差的,我在之前价格计算遇到的问题相关文章中也有过详细的解释。
  • 修改后,判断两个小数是否相等,只能判断他们只差的绝对值是不是在一个很小的范围内,如果相差很小,可以认为相等,我们将 等于判断用如下equal方法电梯
 /*** 浮点数判断是否相等* */public static boolean equals(double num, double num1){if((num - num1 > -0.00000001) && (num - num1) < 0.00000001 ){return true;}return false;}
  • 最高效率的解法,在上面的解法中,每次求值都需要循环exponent次,在求解数值的幂次时候,我们想到的了之前 递归与循环中讲到的斐波那契数列的非大众解法,也是时间复杂度最低的解法,我们先有如下公式:

  • 情况一
    an=an/2∗an/2,n为偶数a^n = a^{n/2}* a^{n/2} , n为偶数 an=an/2an/2,n

  • 情况二
    an=a(n−1)/2∗a(n−1)/2∗a,n为奇数a^n = a^{(n-1)/2}* a^{(n-1)/2}*a , n为奇数 an=a(n1)/2a(n1)/2a,n

  • 我们求解a的n次幂,可以按照如上公式来解决,时间复杂度是O(nlogn),因此自然的想到了递归实现方式。如下实现:

/*** 实现Math.pow(a,b)* */public static double powerWithUnsignedExponent(double base, int exponent){if(base == 0 || base == 1){return base;}if(exponent == 0){return 1d;}if(exponent == 1){return base;}double result = powerWithUnsignedExponent(base, exponent >> 1);result*=result;if((exponent & 1) == 1){result*=base;}return result;}
  • 以上,我们用二分法的思想做递归,并在判断奇数偶数的地方用与操作进行判断,奇数的二进制位 低位必然是1,偶数正好相反是 0 ,因此我们让原数与 1 进行与操作,可以区分出奇偶性。位运算相关的知识在之前的文章 数据结构与算法–位运算中有详细介绍。
打印1 到最大的n位数
  • 题目:输入数字n, 按顺序打印出从1 到最大的n为十进制数。比如,输入3,则打印出1,2,3,…999。

  • 题目看去来非常简单,一个循环可以搞定,我们有如下代码:

  /*** 打印 1 ~ n位最大整数,* ex: n=3, 1~999* */public static void print1ToMaxOfNDigits(int n){int number = 1;int i = 0;while(i++ < n){number *=10;}for (int i1 = 0; i1 < number; i1++) {System.out.println(i1);}}
  • 看起来是没有问题,并且程序能正常执行,但是这里没有考虑到整数的表示范围问题
    • 整型数据 32 位,4 个字节,能标识的范围是 -231 ~ 2 32
    • 长整型数据64位,8 字节,能标识的范围 -263~ 264
  • 但是以上案例中的n是没有上限的,也就是我们无论用long还是int都会达到最大数据无法标识问题,因此必须用其他方案来实现这次的累加,可以用字符串或者字符数组来模拟累加的操作。
  • 通过以上分析,我们需要解决两件事情:
    • 用字符串标识数字上的模拟加法
    • 打印用字符串表达的数字。
  • 有如下代码
/*** 打印 1 ~ n位最大整数,* ex: n=3, 1~999* */public static void print1ToMaxOfNDigitsByChars(int n){if(n<=0){return;}char[] number = new char[n];for (int i = 0; i < number.length; i++) {number[i] = '0';}while (!increment(number)){printCharArr(number);}}/*** 字符模拟加法*/public static boolean increment(char[] chars){boolean isOverflow = false;int takeOver = 0;for (int i = chars.length-1; i >= 0; i--) {//添加进位,初始为0不影响int nSum = chars[i] - '0' + takeOver;//+1 操作if(i == chars.length-1 ){nSum ++;}if(nSum >= 10){if(i==0){isOverflow = true;}else {nSum -= 10;takeOver = 1;chars[i] = (char) ('0' + nSum);}}else {chars[i] = (char)('0' + nSum);break;}}return isOverflow;}/*** 打印数字,取出首字母中'0'* */public static void printCharArr(char[] chars){if(chars == null || chars.length <= 0){return;}boolean begin = true;for (int i = 0; i < chars.length; i++) {if(begin && chars[i] != '0'){begin = false;}if(!begin){System.out.print(chars[i]);}}System.out.println();}
  • 以上代码,increment实现字符串number上的 加法,printCharArr负责打印。

  • increment 需要注意的是,进位的规则实现,已经数据是否越界,因为我们需要打印的是N位数,当 我们遍历到 第N 位的时候,并且第N位的大小 >= 10 ,此时触发进位规则的时候,就达到了我们的最大值。

  • printCharArr 打印功能虽然简单,但是在字符串标识的数字中,必然会有前n-x位的字符是无需打印的 ‘0’ ,因为我们需要将093,打印成 93 才是我们程序需要的值,我们用一个标志位begin 来遍历之前的所有0 ,当达到第一个非0 操作的时候,将begin标志位设置为true。才开始打印后面的字符。

  • 终极解法,以上思路是完全可以实现的,但是用字符串来实现加法的 复杂度过于高,即使我们能在短时间内写出来,很可能也会有纰漏,我们换一种思路,我们需要打印0 ~ 999 ,也就是一个三位字符数组,每一位数组都是0~ 9 这10 中情况,我们将他看成一个排列组合,只要将所有情况按照排列组合的顺序打印出来,就可以变相的完成累加的工作。

  • 全排列用递归表示非常容易。数字的第一位设置0~9 ,然后递归设置下一位0 ~ 9依次递归到第n位。如下实现:

 /*** 递归打印1 ~ 最大n位整数* */public static void print1ToMaxOfNDigitsRecursively(int n){if(n <= 0){return;}char[] number = new char[n];for (int i = 0; i < number.length; i++) {number[i] = '0';}for (int i = 0; i < 10; i++) {number[0] = (char)(i+'0');print1ToMaxOfNDigitsRecursively(number, 0);}}public static void print1ToMaxOfNDigitsRecursively(char[] number, int index){if(index == number.length -1){printCharArr(number);return;}for (int i = 0; i < 10; i++) {number[index + 1] = (char)('0' + i);print1ToMaxOfNDigitsRecursively(number, index +1);}}
  • 如上递归实现,我们在递归中只有在最高位完成字符串设置的时候才开始打印(index == number.length -1),因为我们将整个字符数组看成是一个排列的一种可能性,即使这个数字是001 ,我们也必须设置完后面的两个 0 才开始打印。

启示录

  • 第一个,陷阱在于大数问题,需要找到合适的数据结构来表示大数据问题。
  • 第二个,在于时间复杂度,每个算法都应该考虑时间复杂度,应该思考不同的方法时间效率问题。
  • 第三个,考虑问题的全面新,在整数的 n次幂问题中,需要考虑到非常多的边界问题,很多会忽略底数为0 指数为负的情况
  • 第四个,还是n次幂中对效率的需求,这个数学公式的运用还是需要靠积累,靠临时来想是不可能的

上一篇:数据结构与算法–位运算
下一篇:数据结构与算法–代码鲁棒性案例分析

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/310423.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C++,Java编程中 标识符 常见命名约定

对方法&#xff0c;变量命名时: 约定1: 标识符是一个单词的时候&#xff0c;首字母小写。 范例: name 约定2: 标识符由多个单词组成的时候&#xff0c;第一个单词首字母小写&#xff0c;其他单词首字母大写 范例: firstName 对类命名时: 约定1: 标识符是一个单词的时候&#…

dotNET Core 3.X 使用 Jwt 实现接口认证

在前后端分离的架构中&#xff0c;前端需要通过 API 接口的方式获取数据&#xff0c;但 API 是无状态的&#xff0c;没有办法知道每次请求的身份&#xff0c;也就没有办法做权限的控制。如果不做控制&#xff0c;API 就对任何人敞开了大门&#xff0c;只要拿到了接口地址就可以…

数据结构与算法--代码鲁棒性案例分析

代码鲁棒性 鲁棒是robust的音译&#xff0c;就是健壮性。指程序能够判断输入是否符合规范&#xff0c;对不合要求的输入能够给出合理的结果。容错性是鲁棒的一个重要体现。不鲁棒的代码发生异常的时候&#xff0c;会出现不可预测的异常&#xff0c;或者程序奔溃。由于鲁棒性非…

【半译】两个gRPC的C#库:grpc-dotnet vs Grpc.Core

grpc-dotnet 是在2019年随着 .NET Core 3.0 一起发布的一个gPRC官方库。在ASP.NET Core 的 gRPC项目模板里面就使用了这个库。.NET Core 3.0之前难道不可以使用gRPC吗&#xff1f;目前&#xff0c;gRPC 在.NET上有两种官方实现&#xff1a;Grpc.Core&#xff1a;这个是原来的gR…

[Java基础]String对象的特点(易错点)

String对象的特点: 1.通过new创建的字符串对象&#xff0c;每一次new都会申请一个内存空间&#xff0c;虽然内容相同&#xff0c;但是地址值不同。 2.以""方式给出的字符串&#xff0c;只要字符串相同(顺序和大小写)&#xff0c;无论在程序代码中出现几次&#xff0…

数据结构与算法--解决问题的方法- 二叉树的的镜像

解决问题的思路 工作中遇到的问题可能用到的数据结构由很多&#xff0c;并且各种数据结构都不简单&#xff0c;我们不可能光凭借想象就能得到问题的解法&#xff0c;因此画图是在家具问题过程中用来帮助自己分析&#xff0c;推理的常用手段。很多问题比较抽象&#xff0c;不容…

使用dnSpy调试asp.net core源码

环境&#xff1a;window 10vs2019 16.5.1dnspy v6.1.4.netcore3.1参考&#xff1a;.Net反编译技术详解及4个反编译工具介绍一、关于dnSpydnSpy是近几年的新秀&#xff0c;功能远比ILSpy强大&#xff0c;甩.net Reflector几条街&#xff0c;被汉化、破解、逆向方面的人才奉为神器…

数据结构与算法--解决问题的方法-顺时针打印矩阵

顺时针打印矩阵 题目输入一个矩阵&#xff0c;按照从外向里顺时针的顺序依次打印每一个数字。例如下案例&#xff1a; 如上图矩阵&#xff0c;顺时针打印&#xff1a;1,2,3,4,8,12,16,15,14,13,9,5,6,7,1,10 以上问题看起来比较复杂&#xff0c;但是又没有涉及到复杂的数据结…

.NET与鲲鹏共展翅,昇腾九万里(二)

在上一篇文章 .NET与鲲鹏共展翅&#xff0c;昇腾九万里&#xff08;一&#xff09;中&#xff0c;我们通过在鲲鹏架构的Euler系统上跑Docker的方式把dotnet core 跑起来了&#xff0c;有读者反馈说“还是走docker喽&#xff0c;你这个标题应该改成鲲鹏和docker两条鲸鱼的故事”…

[Java基础]final和static修饰符

final: final修饰局部变量时: static&#xff1a; static访问特点:

数据结构与算法--举例分析法- 栈的压入弹出序列

举例分析 与上两篇问中画图方法一样&#xff0c;我们可以用举例模拟的方法思考分析复杂问题。当一眼不能看出问题的规律的时候&#xff0c;我们可以用几个具体的例子来模拟一下问题的过程。这样就和我们在程序出现问题时候的debug一样&#xff0c;走一下整个流程&#xff0c;可…

优化委托的 DynamicInvoke

优化委托的 DynamicInvokeIntro委托方法里有一个 DynamicInvoke 的方法&#xff0c;可以在不清楚委托实际类型的情况下执行委托方法&#xff0c;但是用 DynamicInvoke 去执行的话会比直接用 Invoke 的方法会慢上很多&#xff0c;差了两个数量级&#xff0c;所以在知道委托类型的…

数据结构与算法-- 广度优先打印二叉树

广度优先打印二叉树 题目&#xff1a;从上往下打印出二叉树的每一个节点&#xff0c;同一层节点按照从左到右顺序打印&#xff0c;例如下图中二叉树&#xff0c;依次打印出是8,6,10,5,7,9,11 如上题中二叉树的节点定义我们用之前文章 二叉树实现原理中定义的节点结构。此处提议…

实现一个基于动态代理的 AOP

实现一个基于动态代理的 AOPIntro上次看基于动态代理的 AOP 框架实现&#xff0c;立了一个 Flag&#xff0c; 自己写一个简单的 AOP 实现示例&#xff0c;今天过来填坑了目前的实现是基于 Emit 来做的&#xff0c;后面有时间再写一个基于 Roslyn 来实现的示例效果演示演示代码&…

数据结构与算法-- 二叉树后续遍历序列校验

二叉树后续遍历序列校验 题目&#xff1a;输入一个整数数组&#xff0c;判断改数组是否是某个二叉搜索树的后续遍历结果&#xff0c;如果是返回true否则false&#xff0c;假设输入数组的任意两个数字不相同。 例如输入{5,7,6,9,11,10,8}则返回true&#xff0c;因为这个整数序列…

程序员过关斩将-- 工作好多年可能还未真正了解接口和抽象类

点击上方“蓝字”关注我们菜菜哥&#xff0c;我偷偷出去面试了&#xff0c;然后面试官让我回来等消息那你可能挂了呀&#xff0c;有什么问题没回答上来吗确实有一个问题回答的不太好哎&#xff0c;就是接口和抽象类这个确实是面试官比较爱问的题目之一那能不能说说接口和抽象类…