一个循环体引发的思考

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/a839579332/article/details/53289342
/*
* 计算 n*(n-1)*……(n-k) / 1*2*3……*k
* 循环要找到循环体

*/

忽略上面格式的问题,其实我只想说 如果要用一个for循环来计算这个结果,当然按照字面意思,我们可以不假思索的写到

for循环 计算出  n*(n-1)*……(n-k)  的结果

然后再用for循环计算出 1*2*3……*k的结果 然后再相除,但是其实只要我们把这个计算公式做个简单的变形,我们只需要一个for循环就可以计算出这个结果,

所以我明白了一个道理   用for循环尽量简便的找到循环体 和 循环条件,这样 使得 写法简便,但不一定是最好理解的。

n/k  *   (n-1)/(k-1)  * (n-2)/(k-2)  ....  (n-k)/1 一共是k-1项  所以你是不是发现这样很好做了? 但如果你以为就这么简单的话 我还有必要写一个博客嘛?

这里还有一个陷阱,我们知道 java在计算除法时 结果默认为整形,比如 3/2 =1, 所以 这里这么多除法运算 你还需要借助一个 BIgDecimal 类 来处理,这个类是java自带的类,当然 它用起来极其麻烦,因为里面用的运算 都是通过调用里面的方法来实现的,并且参数类型是 BigDecimal 类 ,这 时候 你就需要自己写个封装类 封装下这层的函数 使得你调用方法简便,快捷, 下面是网上找来的例子:

package xy.base;


import java.math.BigDecimal;


public   class   Arith{    
    
    //默认除法运算精度    
    private   static   final   int   DEF_DIV_SCALE   =   10;    


    //这个类不能实例化    
    private   Arith(){    
    }    
    /**   
      *   提供精确的加法运算。   
      *   @param   v1   被加数   
      *   @param   v2   加数   
      *   @return   两个参数的和   
      */    
    public   static   double   add(double   v1,double   v2){    
            BigDecimal   b1   =   new   BigDecimal(Double.toString(v1));    
            BigDecimal   b2   =   new   BigDecimal(Double.toString(v2));    
            return   b1.add(b2).doubleValue();    
    }    
    /**   
      *   提供精确的减法运算。   
      *   @param   v1   被减数   
      *   @param   v2   减数   
      *   @return   两个参数的差   
      */    
    public   static   double   sub(double   v1,double   v2){    
            BigDecimal   b1   =   new   BigDecimal(Double.toString(v1));    
            BigDecimal   b2   =   new   BigDecimal(Double.toString(v2));    
            return   b1.subtract(b2).doubleValue();    
    }      
    /**   
      *   提供精确的乘法运算。   
      *   @param   v1   被乘数   
      *   @param   v2   乘数   
      *   @return   两个参数的积   
      */    
    public   static   double   mul(double   v1,double   v2){    
            BigDecimal   b1   =   new   BigDecimal(Double.toString(v1));    
            BigDecimal   b2   =   new   BigDecimal(Double.toString(v2));    
            return   b1.multiply(b2).doubleValue();    
    }    


    /**   
      *   提供(相对)精确的除法运算,当发生除不尽的情况时,精确到   
      *   小数点以后10位,以后的数字四舍五入。   
      *   @param   v1   被除数   
      *   @param   v2   除数   
      *   @return   两个参数的商   
      */    


    public   static   double   div(double   v1,double   v2){    
            return   div(v1,v2,DEF_DIV_SCALE);    
    }    


 


    /**   
      *   提供(相对)精确的除法运算。当发生除不尽的情况时,由scale参数指   
      *   定精度,以后的数字四舍五入。   
      *   @param   v1   被除数   
      *   @param   v2   除数   
      *   @param   scale   表示表示需要精确到小数点以后几位。   
      *   @return   两个参数的商   
      */    


    public   static   double   div(double   v1,double   v2,int   scale){    
            if(scale<0){    
                    throw   new   IllegalArgumentException(    
                            "The   scale   must   be   a   positive   integer   or   zero");    
            }    
            BigDecimal   b1   =   new   BigDecimal(Double.toString(v1));    
            BigDecimal   b2   =   new   BigDecimal(Double.toString(v2));    
            return   b1.divide(b2,scale,BigDecimal.ROUND_HALF_UP).doubleValue();    
    }    


 


    /**   
      *   提供精确的小数位四舍五入处理。   
      *   @param   v   需要四舍五入的数字   
      *   @param   scale   小数点后保留几位   
      *   @return   四舍五入后的结果   
      */    


    public   static   double   round(double   v,int   scale){    
            if(scale<0){    
                    throw   new   IllegalArgumentException(    
                            "The   scale   must   be   a   positive   integer   or   zero");    
            }    
            BigDecimal   b   =   new   BigDecimal(Double.toString(v));    
            BigDecimal   one   =   new   BigDecimal("1");    
            return   b.divide(one,scale,BigDecimal.ROUND_HALF_UP).doubleValue();    
    }    
}; 


都有说明,按照这样的方式调用就好了。


其实虽然做开发时间还算蛮长的了,但一直感觉自己是个low逼,因为感觉心中没有一个写代码的准则,所以又回顾基础知识。到这里 这个循环差不多算是了结了。


下面要说一下可能大部分老手都不一定理解的东西,编码字符集问题:

首先 你要明白 byte char 这对象的含义, byte表示一个字节  一个字节是八位二进制数,这个你知道吧,那么char 表示一个字符,不同的字符集所需要的字节数不一定相同,这里只说下 java内部是用的Unicode字符集,用两个字节 表示一个字符,这里又要介绍另一个术语,代码单元,它就表示一个字符的长度,“A我 ” 表示两个代码单元,四个字节。

还有一个代码点,它是和Unicode的字符集对应的,这个要讲到它的起源,是因为考虑到加入中文,所以原本的八位二进制不够用了,就用了十六位,那么每个数字代表的就是一个代码点。 那么和utf-8是什么关系呢,这个不叫字符集,网上有很多不懂或者一知半解的人乱说,它表示编码的格式,同类的有 GBK...,那么这些编码方式对应的是 在这些数据传输过程中 是怎么进行编码转换的,因为 Unicode字符集 对英文的二进制表示也是十六位,对资源来说是一种浪费,所以就提出了将数据进行编码,数据中英文的还是一个字节表示,中文的用2-4个字节表示,这么说明白了嘛。至于 这之中是怎么转化的 是有一个算法。

展开阅读全文

一个语句思考过程引发思考

12-11

一个语句思考过程引发的思考rnA 部分:引起思考的 题目rn一个字段的表,怎么样过滤掉冗余数据,用一句SQL写出来!rn冗余数据的条件:rn 相同记录只取一条。rn 在没有相同记录的时,前后两条记录的差必须大于1。rnrn表T(nNum)rn 1rn 2rn 2rn 3rn 4rn说明:nNum为一int,有可能重复。需要选出这样结果rn1,4rnrn条件是这样的:rn(1)现把nNum有重复的记录去掉。并升序排列。则得到(1,2,3,4)rn(2)然后第二条减去第一条nNumrn 如果等1,则去掉第二条记录。=>(1,3,4),重复(2)rn 如果大于1,则用第三条减第二条判断同上。第四条-第三条。。。。rn例:rn(1,1,2,3,3,5,7,9,10)=>rn(1,2,3,5,7,9,10)=>rn(1,3,5,7,9,10)=>rn(1,3,5,7,9)=>最所需要的结果。rnrnB部分:问题分析/解决过程rnrn1-1、过滤重复记录 当然使用disktinct 关键字 记下 (select distinct num from table ) 视图1 是第一步没有重复数据的视图rn1-2、得出结论 :每条记录 必须依靠它前一条记录来判断它是否存在:1前面没有数字存在,2前面有1 取掉,3本来有2但2被除去,所以也留下……于是得出结论:是一个循环递归的判断,似乎没有一句话实现的方法。rnrn2-1、不死心,睡觉以后继续观察,发现 事实上可以在 视图1里得到 绝对要保留的数据(不存在-1num的值),于是得出视图2:rn(rnselect * from 视图1 k2 rn where (select count(*) fromrn 视图1 k3rn where k2.num-k3.num=1)=0rn)rn包含了所有初步必选项目。rn并同时派生出视图3,包含其他项目:rn(rnselect * from 视图1 k2 rn where (select count(*) fromrn 视图1 k3rn where k2.num-k3.num=1)>0rn)rn2-2、但还有一个问题没有解决,那其他该存在的数据该如何判断?既然头大,就先不判断,出去走走再说。rnrn3-1、发现如果把视图2里面的数据作为分界点,可以把视图1分为可以界定的有限部分,且每部分都是连续的,假设k为一个段的分界点,那么 k+1被删除,k+2留下,k+3删除,k+4留下……即k+2x的数据留下rn推翻1-2,发现视图4:该视图包含了第二待选项目rn(rnselect num from rn (rn select k3.num,max(k2.num) cntrn from 视图3 k3,视图2 k2 rn where k3.num>k2.num droup by k3.num rn ) kx rn where (kx.num-kx.cnt)%2=0rn)rn3-2、于是所有的结果就出来了,对于多列表就有视图5包含合格的行:rn(rnselect * from k where k.num in (select num from 视图2)rn or k.num in (select num 视图4)rn) rn4-1、吃完午饭发现既然只有一列,而且对于视图2的数据也符合视图4的规则:(k-k)%2=0rn于是改进 语句,直接成为视图六:rn(rnselect t.num from rn(select distinct num from [table]) t,rn(select * from (select distinct num from [table]) t where rn (select count(*) from (select distinct num from [table]) t2 where t.num-t2.num=1)=0 rn) t2rnwhere t.num>=t2.num rngroup by t.num rnhaving (t.num-max(t2.num))%2=0rnorder by numrn)rn4-2,感觉完美了,睡觉!rnrn5-1、有朋友认为 视图6 没有美感,认为可以变形为视图7:rn(rnselect distinct numrnfrom #t Trnwhere ((select max(num) rn from #t B rn where num<=T.num rn and not exists(select 1 rn from #t rn where num=B.num-1)rn )-T.num)%2=0rnrn)rn观察:视图7 语句简练,可理解性也强,但使用了子查询,可能会有改进余地。rn再次思考:rn视图4有还改进余地, 去掉多余子句,使用更快速的not exists判断, 再加上 视图六 的思想 ,得到视图8:rn(rnselect k.numrnfrom [table] k,rn(select num from [table] where not exists (select 1 from [table] k3 where k3.num=[table].num-1) ) k2rnwhere k.num>=k2.num rngroup by k.numrnhaving (k.num-max(k2.num))%2=0rnorder by k.num rn)rn测试结果出乎意料:视图8比视图6几乎有10倍的提高。rnrn至此,改进工作告一段落。rnrnC部分:反思思考过程rnrn回想这个过程,会发现最成功的一点是能够一直突破思路,改进方案;rn最不成功的一点是太过依赖经验,又总是不够经验,于是总是自己的前一个方案束缚自己的下一个方案。rnrn让我们来分析下分析过程的过程:rnrn1-1、在思考 视图1 的时候 根据经验 理所当然地选择 distinct 作为唯一侯选方案,却忘了过滤大量重复的另外一个高效率方法:group by(也是经验不足的表现)。rn于是有了两个很不好的效果:rn 1、在以后思考问题的时候下意识地不使用 原始数据 进行思考,而是直接使用 视图1 进行思考,于是在生成代码的时候 大量重复使用了 视图1 作为原始数据代码,直接导致 distinct 多次多余重复使用,严重降低前期代码的效率。rn 2、忘记了distinct 可以使用 group by得到重复的效果。rn1-2、根据常识:循环就无法通过单个语句实现。这句话很对,但终于限制了发散思维。rnrn2-1、好的开头是成功的一半:即使已经认定了不可能实现我们也不妨研究一下即使没办法完全实现,也研究下可以实现到什么程度,用这样的态度完成学习绝对不会没有收获。于是写下 根据 1思考 得到的两个 结果视图,这步最大的成功就是把问题明确分成两个部分,直接导致了思维模式的转变,是最后完成的有力保障。rn2-2、不知道是一个好习惯还是坏习惯,个人比较倾向于解决不了问题的时候边做其他事情边稍微想想。rnrn3-1、常识一般是对的,但有时候 并不可以因为常识终止动作:很多时候我们可以绕过常识,换一种角度观察数据发现几乎可以完全抛开常识的影响,既然可以把数据分成有限段,起点又是已知的,那理论上对每个数据的起点都是已知的。这时候终于想起了join 和group by这两种高效率的方式。(我这里想提醒初学sql 的同人注意:join 可以使用 的运算符不止是=,可以是任何有效的逻辑表达式,甚至是 is null ;group by 是在一堆数据里取特征数据最有效的方法 min max count avg 很多时候让你在处理 结果集无所适从的时候可以解放出来(另一个误区:一行记录/没有记录事实上也是一个结果集,可以用结果集的方法进行运算))rn这里的成功在于 突破了1-2。rn3-2、是成功也是失败:rn 2-1的思维定势导致代码 冗长 、烦琐 、不好理解、效率低下 但结果总算出来了:)rn rn4-1、突破了2-1思维的定势,再次把 所有 数据 作为一部分数据处理,但1-1的影响却使得自己忘了 group by 的简单应用。rn4-2、他山之石,可以攻玉(当然,视图7不是石头,缺点在于忘了效率)。rn没什么,就是因为他代码少、漂亮,所以觉得自己的代码也可以更少、更漂亮。而且他使用了双重的子查询,速度估计会改进余地。rn5-1 看到了not exists 想自己怎么那么笨一直用select count ,于是继续反思其他语句有没有类似低能错误的发生,工夫不负有心人,终于发现了group by 可以直接消除重复,于是在最后突破了 1-1的定势 ;简化了视图6以后 效果出乎意料的好。rnrn可以看见,对经验的依赖和经验的缺少是两个很容易犯的错误,同时又是很矛盾的事情,本例里,如果没有 使用 distinct 的经验,如果有 解决分段问题的经验,会是完全不同的高效过程。rn但学习,总要有不肯放弃的精神、勇于怀疑的勇气、探索路径绕过障碍的智慧。本例里,我虽然走了很多弯路,但在以后的问题里,这样的弯路长度会直接降下来,就像视图8和视图6的对比一样!rnrn对学习有决心的朋友们,共勉之!rnrnD 部分rnrn附:测试数据:仅运行于sql server 2000rnrndeclare @krs table (NUM INT)rnDECLARE @X VARCHAR(8000)rnSET @X='1,1,2,3,3,4,6,7,8,9,10,'rnrnWHILE @X<>''rnBEGINrn INSERT INTO @KRS (NUM) VALUES (LEFT(@X,(CHARINDEX(',',@X)-1) ) )rn SET @X=RIGHT(@X,LEN(@X)-CHARINDEX(',',@X))rnEND 论坛

类域引发思考

12-30

这一段时间一直在琢磨对象模型,现在看到个问题都想用模型来解释,不过确实如此,几乎什么都可以用对象模型来解释rn搞这个模型就绝对离不开域rn到底域是什么?这个恐怕不是三言两语说的明白的,每个人都有一定的概念,但是可能大不相同rnC++上指的是作用域rn一对大括号就对应了一个作用域,学过C++的都知道,作用域里的局部变量出了作用域就消亡了(根本原因是栈恢复)rn而对于静态变量,编译器编译的时候做了处理,解析为“全局变量”,但这个全局变量和一般意义上的全局变量不同,它带有记号,这个记号就是 域,标志着这个所谓的全局变量是专属于这个域的rn重点是类域,其实类域和一般域没什么区别也是一对形成的而已rn要讨论的自然是函数和数据rn其实同样是编译器在编译的时候进行了处理,把成员函数(不管是静态还是非静态,虚的还是非虚的)全都解析成了全局函数,但是又带上了记号,那就是专属于某个类的记号,当然解析后的函数全部是独一无二的rn只是寻址这些函数的时候有所区别,这些区别也就是C++本身的特性所导致的,具体什么特性不再此讨论rn然后就是数据成员,静态数据成员,独立于域依附于域。同样编译的时候解析成了专属此类的全局变量,非静态变量是和类的对象同生命的,依附于对象而存在。rn静态成员函数和静态成员数据是独立于域的,所以不需要用对象来存取,依附于域,说明可以用对象来存取,也可以用类名来存取。rn说了这么多,到底域是个什么好像还是很没头绪rn[img=http://hi.csdn.net/attachment/201112/30/10181841_1325218067OLJo.jpg][/img]rn上面这张图上面的虚线就是所谓的类域,局部变量是依附于对象生存在栈空间,静态函数和数据以及非静态函数被解析后分别存在于数据段和代码段rn一些理解,希望对大家有所帮助,有不同意见的请回贴已作深入探讨,谢谢!rnrn 论坛

没有更多推荐了,返回首页