這是剛做完的一小段代碼,經測試已經無誤,呵呵,這裏share给大家看看,有朋友覺得有用,可以拿去用。
這個程序解决的問題如下:
已知一件事物有幾種狀態,每種狀態出現的概率不一样,要求做一個隨機數產生器,返回狀態值,要求狀態值出現的規律,符合輸入的概率。
這是小弟上午問我的問題,我們正在做一個工業測試模型,實際的例子是,根據實際情況,某種設備返回的狀態概率符合下表:
設備狀態 百分比
1 12%
2 40%
3 40%
4 7%
5 1%
要求寫段代碼,模擬設備的上述行为。
我下午上班想了一下,花了半個小時为他寫了一個隨機數產生器,經測試,0bug,呵呵。他現在正在用。
Code:
  1. #define CTonyRandomArea_TOKEN_MAX 100           //最大類型數   
  2. #define CTonyRandomArea_TOKEN_AREA_MAX 10000    //類型數組單元數,精確到小數點後兩位   
  3. //輸入最大100個元素的數組,每個數組表示每類占有的百分比,內部自帶百分比調整。   
  4. //即如果外部輸入的數字之和不是整數100,內部會根據百分比,自動調整其比例,使總和=100.0   
  5. //然後內部建立10000個單元的類型數組,根據傳入的每種類型的比例,在類型數組中批量填充對應的類型值   
  6. //總之,類型數組中每種類型的數量,占據的比例正好是輸入的百分比   
  7. //最後,在0~10000中取隨機,然後在對應的類型數組單元中取類型值,即为返回的類型   
  8. class CTonyRandomArea   
  9. {   
  10. public:   
  11.     CTonyRandomArea(double* pTokenPercentArray,char cTokenCount)   
  12.     {   
  13.         m_nTokenCount=cTokenCount;   
  14.         if(CTonyRandomArea_TOKEN_MAX<m_nTokenCount)   
  15.             m_nTokenCount=CTonyRandomArea_TOKEN_MAX;   
  16.         int i=0;   
  17.         for(i=0;i<m_nTokenCount;i++)   
  18.         {   
  19.             m_dTokenPercentArray[i]=*(pTokenPercentArray+i);   
  20.         }   
  21.         //動態調整內部的值   
  22.         //有時候試驗人員,測得幾個狀態出現的數字,可能懶得再計算成百分比   
  23.         //程序幫忙自動計算   
  24.         double dNumberCount=0;   
  25.         for(i=0;i<m_nTokenCount;i++)   
  26.         {   
  27.             dNumberCount+=m_dTokenPercentArray[i];   
  28.         }   
  29.         if(100.0!=dNumberCount)   
  30.         {   
  31.             for(i=0;i<m_nTokenCount;i++)   
  32.             {   
  33.                 m_dTokenPercentArray[i]/=dNumberCount; 
  34.                 m_dTokenPercentArray[i]*=100; 
  35.             }   
  36.         }   
  37.         //以小數點後兩位精度,開始計算在10000個總單元中,每種類型對應的數量。   
  38.         for(i=0;i<m_nTokenCount;i++)   
  39.         {   
  40.             m_sTokenPercentArray[i]=(short)(m_dTokenPercentArray[i]*100);   
  41.         }   
  42.   
  43.         //按比例填充類型數組   
  44.         int j=0;   
  45.         int nFillMin=0;   
  46.         int nFillMax=0;   
  47.         for(i=0;i<m_nTokenCount;i++)   
  48.         {   
  49.             m_cTokenPercentArrayAreaUp[i]=-1;   
  50.         }   
  51.   
  52.         for(i=0;i<m_nTokenCount;i++)   
  53.         {   
  54.             nFillMax=nFillMin+m_sTokenPercentArray[i];   
  55.             for(j=nFillMin;j<nFillMax;j++)   
  56.             {   
  57.                 m_cTokenPercentArrayAreaUp[j]=i;   
  58.             }   
  59.             nFillMin=nFillMax;   
  60.         }   
  61. //      m_cTokenPercentArrayAreaUp[CTonyRandomArea_TOKEN_AREA_MAX-1]=i-1;   
  62.     }   
  63.     ~CTonyRandomArea(){}   
  64.     void PrintfInfo(void)   
  65.     {   
  66.         int i=0;   
  67.         double dNumberCount=0;   
  68.         for(i=0;i<m_nTokenCount;i++)   
  69.         {   
  70.             dNumberCount+=m_dTokenPercentArray[i];   
  71.             printf("%d = %f\n",i,m_dTokenPercentArray[i]);   
  72.         }   
  73.         printf("All = %f\n",dNumberCount);   
  74.   
  75.         //打印10000個單元的類型分布,看看排得對不對   
  76.         //這段打印起來太長,一般隱掉,需要了再打印   
  77. //      int nOutMax=400;   
  78. //      int nInMax=25;      //二者相乘为10000   
  79. //      int j=0;   
  80. //      for(i=0;i<nOutMax;i++)   
  81. //      {   
  82. //          printf("%05d - ",i*nInMax);   
  83. //          for(j=0;j<nInMax;j++)   
  84. //          {   
  85. //              printf("%d ",m_cTokenPercentArrayAreaUp[i*nInMax+j]);   
  86. //          }   
  87. //          printf("\n");   
  88. //      }   
  89.     }   
  90.   
  91.     //取類型數組對應單元的值   
  92.     char GetType(int nTypeIndex)    //輸入参數0~10000   
  93.     {   
  94.         if(10000<=nTypeIndex) nTypeIndex=9999;   
  95.         if(0>nTypeIndex) nTypeIndex=0;   
  96.         return m_cTokenPercentArrayAreaUp[nTypeIndex];   
  97.     }   
  98.     //真實的工作函數,利用輸入的概率來產生隨機type   
  99.     char GetRandomType(void)   
  100.     {   
  101.         return GetType(GetRandomBetween(0,10000));   
  102.     }   
  103. private:   
  104.     double m_dTokenPercentArray[CTonyRandomArea_TOKEN_MAX];             //比例數組   
  105.     short m_sTokenPercentArray[CTonyRandomArea_TOKEN_MAX];              //比例數組,短整型,1~10000,相當於精確到小數點後兩位   
  106.     char m_nTokenCount;   
  107.     char m_cTokenPercentArrayAreaUp[CTonyRandomArea_TOKEN_AREA_MAX];    //類型數組   
  108. };   
  109. //這是測試代碼   
  110. void TestCTonyRandomArea(void)   
  111. {   
  112.     double dTokenPercentArray[100];   
  113.   
  114.     dTokenPercentArray[0]=12.0;   
  115.     dTokenPercentArray[1]=40.0;   
  116.     dTokenPercentArray[2]=40.0;   
  117.     dTokenPercentArray[3]=7.0;   
  118.     dTokenPercentArray[4]=1.0;   
  119.   
  120.     CTonyRandomArea Area1(dTokenPercentArray,5);   
  121.     Area1.PrintfInfo();   
  122.   
  123.     int i=0;   
  124.     for(i=0;i<20;i++)   
  125.     {   
  126.         printf("RandType = %d\n",Area1.GetRandomType());   
  127.     }   
  128. }  
其實這個原理很簡單:
1、我先從外部導入一個比例列表,在100以內的數組單元,每個單元裏面放置一個double值,相當於對應類別的比例。這样,我預設最大有100種狀態,具體本次試驗有多少種狀態,即100個狀態比例數組多少個單元是有效的,看構造函數的第二個参數,就是這個参數輸入的。
2、這裏面我做了點人性化考慮,因为很多時候,我們測試的設備狀態是直接的采样值,即每種狀態出現了多少次,懶得計算成百分比,因此,我內部自動幫他重新計算一遍百分比。這样用起來很方便。
3、我根據各種類型的比例,內部准備一個10000個單元的大數組,我根據每種狀態的比例,在這個數組中填充足夠的狀態數,這样,構建了一個比例分配表。這實際上是把計算精度放大到小數點後兩位,即99.99%這種精度
4、我真正提供隨機數的函數,是在0~10000中取值,即隨機命中比例分配表的某個單元,這個單元取出來是哪種狀態,就返回哪種狀態。由於比例分配表决定了各種狀態被命中的比例,因此,我返回值是符合出現比例的。
5、最後我给了一個測試函數TestCTonyRandomArea,這是我團隊的規矩,任何人寫一個模塊,必須同時提供相應的白盒測試函數,並將測試結果展示给使用者看,作为驗收標准,即“你必須自己證明自己的工作是有效的,並接受檢驗”,我這個leader也不能例外。
6、PrintfInfo函數也是我團隊的規矩,位於底層的類,有責任提供一個PrintInfo函數,供調用者隨時查閱你的內部數據,“把你的數據暴露给大家看,想出來混江湖,就不怕裸奔被人看!”,呵呵《0bug-C/C++商用工程之道》裏面很多類都有這個函數的。
嗯,中間有個GetRandomBetween這個函數,就是《0bug-C/C++商用工程之道》這本書P199頁的源代碼,這裏我也给一份Copy,另外,其工作原理,有興趣的讀者可以看看書中的描述。
Code:
  1. inline int _GetNot0(void)   
  2. {   
  3.     int nRet=rand();   
  4.     if(!nRet) nRet++;   
  5.     return nRet;   
  6. }   
  7. inline int GetRandomBetween(int nBegin,int nEnd)   
  8. {   
  9.     int n=_GetNot0();   
  10.     int nBetween=0;   
  11.     if(0>nBegin) nBegin=-nBegin;   
  12.     if(0>nEnd) nEnd=-nEnd;   
  13.     if(nBegin>nEnd)   
  14.     {   
  15.         nBetween=nEnd;   
  16.         nEnd=nBegin;   
  17.         nBegin=nBetween;   
  18.     }   
  19.     else if(nBegin==nEnd)   
  20.         nEnd=nBegin+10;   
  21.     nBetween=nEnd-nBegin;   
  22.     n=n%nBetween;   
  23.     n+=nBegin;   
  24.     return n;   
  25. }  
上述代碼是我匆忙寫的,屬於測試用代碼,不完全符合0bug一書裏面的C/C++無錯化程序設計原則,各位讀者請注意哈。
不過,雖然是測試代碼,但是帶了很多工程型代碼的影子,大家有興趣可以看看。
另外,上述代碼沒有做锁封裝,但是,仍然是多線程安全的。大家有注意到沒有?
因为其工作原理是查表法,所有的表構造時一次生成,以後僅僅是純讀,請《0bug-C/C++商用工程之道》的讀者注意2.3.6節,P50頁的論述,“用锁的最高境界--不用”,這裏符合第1條特例,“針對一個資源的所有操作都是讀的時候,可以不加锁”。我這段代碼可以算作實例了。各位讀者可以参考一下。
好吧,就這麼多,大家有興趣可以看看。
嗯,有人可能說,這裏的隨機數產生器沒有使用srand初始化,記住,我在用我自己的工程庫,也就是《0bug-C/C++商用工程之道》的工程庫,工程庫的init動作裏面已經做過這種動作了。
代碼是VS2008下測試的,不過,我的理解,應該是跨平台的。
上述代碼在很多遊戲開發中可以投入實用的。
比如說,某個NPC哨兵,他可能在某個時刻,看前後左右,或者抽煙,或者睡覺,或者和另一個哨兵聊天,這時候,可以用這個隨機數產生器,根據預設的每種動作的概率,權重,隨時求出他的行为種類,並予以展示。
再或者,暗黑裏面,我們使用暗金的裝備,每次攻擊,有百分之多少的概率出現壓碎性打擊,有多少概率出現冰凍屬性,等等,也可以用這個隨機數產生器來求。
大家慢慢想吧,呵呵。
嗯,這裏網友發現一處bug,我已經修改了,請昨天看過的朋友注意:
Code:
  1. if(100.0!=dNumberCount)       
  2. {       
  3.     for(i=0;i<m_nTokenCount;i++)       
  4.     {       
  5.         m_dTokenPercentArray[i]/=dNumberCount;     
  6.         m_dTokenPercentArray[i]*=100;  //這裏少乘了個100,百分比動態調整失效,因此,我加上了這一句。   
  7.     }       
  8. }       
 這段代碼出來後,一些網友表示看不懂我的原意,我們在CSDN博客有一些問答,我覺得對大家理解本程序的設計思路有幫助,因此,整理了一下,摘錄在這裏:
網友問:if(100.0!=dNumberCount) 浮點數直接用等於作比較是不正確的
我答:通常的做法是if((100.0-dNumberCount)<0.00000001),我知道的,不過,我为什麼這麼寫,你看得懂嗎?
網友問:不懂,老師教教吧,謝謝
我答:這裏主要的目的是否定,是为了驗證所有輸入的double數加起來不是100.0,然後內部重新計算一次。由於外部人員輸入,通常都不是正好100.0,因此,這裏使用否定的嚴厲校驗,即只要不是絕對==100.0,內部就重新計算。看好了,我是否定嚴厲,不是肯定嚴厲,因此不用教科書做法。
網友問:同意這样直接比較在此處也不會產生錯誤,我還是有如下觀點: 1. 這样嚴厲的否定可能會拒絕一些本可以接受的輸入,當然概率比較小,而且即使拒絕了也頂多是多計算一下,不會有bug 2. 即使是通過代碼中的“自動調整其比例“的計算以後,仍然有可能會出發您的”否定嚴厲“ 所以我認为還是不應該用直接比較。 3. (100.0-dNumberCount)<0.00000001 這样的比較還是不合适的,一是要用絕對值,當然這裏可能是您忘記寫了;二是0.00000001的取定要推敲,用float.h中提供的常數宏更好
我答:嗯,你說的有理,我下回注意,呵呵。不過,你說的計算後仍然有否定嚴厲誤差的問題,看我61行,我寫那行代碼的目的就是为了彌補這個誤差的。不過後來看了沒有誤差,所以就隱掉了。
網友問:不是我挑錯,但我總覺的你的代碼顯的很長。好多沒必要。比如 GetRandomBetween函數,其實很簡單。 GetRandomBetween(int nBegin,int nEnd) { int n = abs(nBegin); int nBetween= abs(nEnd) - n; if(nBetween < 0) { n = abs(nEnd); } if(nBetween == 0) nBetween = 10; n += _GetNot0(); return n; } 這样不是更簡潔點麼?完成的功能是完全一样的。
我答:把每句話盡量簡化,簡化到大家看起來一目了然的時候,程序就不容易出錯。你的方案,一句話裏面有多個計算,很繞。不是每個項目成員都有你的水平的。
網友問:再比如,你的 GetType和GetRandomType這2個函數,完全可以結合成一個嘛。 char GetType(int nTypeIndex) //輸入参數0~10000 { return m_cTokenPercentArrayAreaUp[GetRandomBetween(0,10000)]; } 注意,這裏的GetRandomBetween(0,10000)返回範圍,就是0-9999. 這样不是簡單多了?
我答:看下面,是故意拆分的,留兩個api,给別人一個中間查表的切入點。
網友問:哦。原來是故意拆分的。
網友問:還有一點,为什麼程序中,有好多char和short來替代int?這样有什麼好處?是为了節約空間麼?我認为,char和short在做参數傳進傳出,或者與int比較時,每次都要擴展为int,還不如直接用int好。在32位系統中,用int最快了。 只是自己的一些看法,有說的不對的,我們互相學習。

我答:嗯,看在你說出互相學習這句話,我回答你的問題:這段代碼之所以寫得像你說的這麼繁瑣,是为了盡可能提供api给使用者用,就是我小弟,他覺得用得方便。因为他是用戶。我必須站在用戶的需求角度設計api,方便調用。因此,很多稍微复雜一點的api函數,我會盡量拆細,每一步都提供一個函數接口给用戶用。用不用在他,但是我盡量给全。
 api接口設計,應該站在用戶使用方便來設計的。反而是我的構造函數很复雜,是因为這些是我內部動作,我要屏蔽,無須通報外部,這體現高內聚,低耦合的原則。
 char和short確實是为了節約空間考慮,因为裏面有個10000個單元的大數組,用char是10k,用int是40k。
網友問:這麼考慮的話也可以。我感覺在這種情況的話,用unsigned char會不會更好?
我答:我預設100個類型,<127,char的正數範圍足以。
網友問:我倒是感覺這次的需求這麼簡單,沒必要给更多的中間接口。設計以需求为目標,不是程序員覺的客戶怎麼方便怎麼設計,有很多接口,客戶也許根本用不到。反而造成不必要的設計,程序复雜度上升。
我答:這個算我個人習慣吧,基礎模塊的公有接口我習慣留得越多越好,越簡單越好,最好每個接口一句話。這样,哪天有新需求,省的我改接口。因为這類基礎模塊的使用者,通常就是我團隊成員,大家這麼做也習慣了。不過,對外的接口,還是應該越少越好,這是原則,比如功能層向業務層輸出的接口,和其他小組的接口,暴露越少越好。不同的需求導致不同的設計。
 最後再補充一點,你有想過這個程序的效率沒有?它用查表法,你可以和普通計算法比較一下,每個都跑個1000萬次,你就看出時間差別了。而且,它不用锁,並行環境和串行環境效率一样高。
網友問:查表法肯定比每次都計算省時間。但第一次構造要花時間,而且犧牲一部分空間。就你這次的實現來看,用查表法是對的。
我答:實話跟你講吧,這段代碼是有前提的,我們要做5000萬條記錄,中間有20萬個設備的記錄,每個設備的采样頻率不一样,我要並發模擬,你再想想我寫這麼复雜有道理沒?
 
最後,還有網友反映,構造函數太复雜,看不懂,我這裏也解釋一下。當時情況比較急,小弟趕着用,我也沒時間精雕細琢這個代碼,所以,構造函數寫得就很复雜,基本上想到哪寫到哪。
這個函數的設計,並不符合《0bug-C/C++商用工程之道》第三章的“C/C++無錯化設計原則”,所以看起來就難懂。看見沒,只要不符合這個原則,只要一個函數內有多個循環主體,即多個邏輯意思,大家看起來就混亂。 希望大家以後開發引以为戒,盡量還是寫簡單的程序。
=======================================================
在線底價購買《0bug-C/C++商用工程之道》
(直接點擊下面鏈接或拷貝到瀏覽器地址欄)
http://s.click.taobao.com/t_3?&p=mm_13866629_0_0&n=23&l=http%3A%2F%2Fsearch8.taobao.com%2Fbrowse%2F0%2Fn-g%2Corvv64tborsvwmjvgawdkmbqgboq---g%2Cgaqge5lhebbs6qzlfmqmttgtyo42jm6m22xllqa-------------1%2C2%2C3%2C4%2C5%2C6%2C7%2C8%2C9%2C10%2C11%2C12%2C13%2C14%2C15%2C16%2C17%2C18%2C19%2C20---40--coefp-0-all-0.htm%3Fpid%3Dmm_13866629_0_0
肖舸


From:51CTO

arrow
arrow
    全站熱搜

    戮克 發表在 痞客邦 留言(0) 人氣()