程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> 關於C語言 >> 實際中常用的一個隨機數產生器(分類別概率隨機)

實際中常用的一個隨機數產生器(分類別概率隨機)

編輯:關於C語言

這是剛做完的一小段代碼,經測試已經無誤,呵呵,這裡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 肖舸

本文出自 “肖舸的blog” 博客,請務必保留此出處http://tonyxiaohome.blog.51cto.com/925273/296530

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved