這是剛做完的一小段代碼,經測試已經無誤,呵呵,這裡share給大家看看,有朋友覺得有用,可以拿去用。
這個程序解決的問題如下:
已知一件事物有幾種狀態,每種狀態出現的概率不一樣,要求做一個隨機數產生器,返回狀態值,要求狀態值出現的規律,符合輸入的概率。
這是小弟上午問我的問題,我們正在做一個工業測試模型,實際的例子是,根據實際情況,某種設備返回的狀態概率符合下表:
設備狀態
百分比
1
12%
2
40%
3
40%
4
7%
5
1%
要求寫段代碼,模擬設備的上述行為。
我下午上班想了一下,花了半個小時為他寫了一個隨機數產生器,經測試,0bug,呵呵。他現在正在用。
Code:
- #define CTonyRandomArea_TOKEN_MAX 100 //最大類型數
- #define CTonyRandomArea_TOKEN_AREA_MAX 10000 //類型數組單元數,精確到小數點後兩位
- //輸入最大100個元素的數組,每個數組表示每類占有的百分比,內部自帶百分比調整。
- //即如果外部輸入的數字之和不是整數100,內部會根據百分比,自動調整其比例,使總和=100.0
- //然後內部建立10000個單元的類型數組,根據傳入的每種類型的比例,在類型數組中批量填充對應的類型值
- //總之,類型數組中每種類型的數量,占據的比例正好是輸入的百分比
- //最後,在0~10000中取隨機,然後在對應的類型數組單元中取類型值,即為返回的類型
- class CTonyRandomArea
- {
- public:
- CTonyRandomArea(double* pTokenPercentArray,char cTokenCount)
- {
- m_nTokenCount=cTokenCount;
- if(CTonyRandomArea_TOKEN_MAX<m_nTokenCount)
- m_nTokenCount=CTonyRandomArea_TOKEN_MAX;
- int i=0;
- for(i=0;i<m_nTokenCount;i++)
- {
- m_dTokenPercentArray[i]=*(pTokenPercentArray+i);
- }
- //動態調整內部的值
- //有時候試驗人員,測得幾個狀態出現的數字,可能懶得再計算成百分比
- //程序幫忙自動計算
- double dNumberCount=0;
- for(i=0;i<m_nTokenCount;i++)
- {
- dNumberCount+=m_dTokenPercentArray[i];
- }
- if(100.0!=dNumberCount)
- {
- for(i=0;i<m_nTokenCount;i++)
- {
- m_dTokenPercentArray[i]/=dNumberCount;
- m_dTokenPercentArray[i]*=100;
- }
- }
- //以小數點後兩位精度,開始計算在10000個總單元中,每種類型對應的數量。
- for(i=0;i<m_nTokenCount;i++)
- {
- m_sTokenPercentArray[i]=(short)(m_dTokenPercentArray[i]*100);
- }
-
- //按比例填充類型數組
- int j=0;
- int nFillMin=0;
- int nFillMax=0;
- for(i=0;i<m_nTokenCount;i++)
- {
- m_cTokenPercentArrayAreaUp[i]=-1;
- }
-
- for(i=0;i<m_nTokenCount;i++)
- {
- nFillMax=nFillMin+m_sTokenPercentArray[i];
- for(j=nFillMin;j<nFillMax;j++)
- {
- m_cTokenPercentArrayAreaUp[j]=i;
- }
- nFillMin=nFillMax;
- }
- // m_cTokenPercentArrayAreaUp[CTonyRandomArea_TOKEN_AREA_MAX-1]=i-1;
- }
- ~CTonyRandomArea(){}
- void PrintfInfo(void)
- {
- int i=0;
- double dNumberCount=0;
- for(i=0;i<m_nTokenCount;i++)
- {
- dNumberCount+=m_dTokenPercentArray[i];
- printf("%d = %f\n",i,m_dTokenPercentArray[i]);
- }
- printf("All = %f\n",dNumberCount);
-
- //打印10000個單元的類型分布,看看排得對不對
- //這段打印起來太長,一般隱掉,需要了再打印
- // int nOutMax=400;
- // int nInMax=25; //二者相乘為10000
- // int j=0;
- // for(i=0;i<nOutMax;i++)
- // {
- // printf("%05d - ",i*nInMax);
- // for(j=0;j<nInMax;j++)
- // {
- // printf("%d ",m_cTokenPercentArrayAreaUp[i*nInMax+j]);
- // }
- // printf("\n");
- // }
- }
-
- //取類型數組對應單元的值
- char GetType(int nTypeIndex) //輸入參數0~10000
- {
- if(10000<=nTypeIndex) nTypeIndex=9999;
- if(0>nTypeIndex) nTypeIndex=0;
- return m_cTokenPercentArrayAreaUp[nTypeIndex];
- }
- //真實的工作函數,利用輸入的概率來產生隨機type
- char GetRandomType(void)
- {
- return GetType(GetRandomBetween(0,10000));
- }
- private:
- double m_dTokenPercentArray[CTonyRandomArea_TOKEN_MAX]; //比例數組
- short m_sTokenPercentArray[CTonyRandomArea_TOKEN_MAX]; //比例數組,短整型,1~10000,相當於精確到小數點後兩位
- char m_nTokenCount;
- char m_cTokenPercentArrayAreaUp[CTonyRandomArea_TOKEN_AREA_MAX]; //類型數組
- };
- //這是測試代碼
- void TestCTonyRandomArea(void)
- {
- double dTokenPercentArray[100];
-
- dTokenPercentArray[0]=12.0;
- dTokenPercentArray[1]=40.0;
- dTokenPercentArray[2]=40.0;
- dTokenPercentArray[3]=7.0;
- dTokenPercentArray[4]=1.0;
-
- CTonyRandomArea Area1(dTokenPercentArray,5);
- Area1.PrintfInfo();
-
- int i=0;
- for(i=0;i<20;i++)
- {
- printf("RandType = %d\n",Area1.GetRandomType());
- }
- }
其實這個原理很簡單:
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:
- inline int _GetNot0(void)
- {
- int nRet=rand();
- if(!nRet) nRet++;
- return nRet;
- }
- inline int GetRandomBetween(int nBegin,int nEnd)
- {
- int n=_GetNot0();
- int nBetween=0;
- if(0>nBegin) nBegin=-nBegin;
- if(0>nEnd) nEnd=-nEnd;
- if(nBegin>nEnd)
- {
- nBetween=nEnd;
- nEnd=nBegin;
- nBegin=nBetween;
- }
- else if(nBegin==nEnd)
- nEnd=nBegin+10;
- nBetween=nEnd-nBegin;
- n=n%nBetween;
- n+=nBegin;
- return n;
- }
上述代碼是我匆忙寫的,屬於測試用代碼,不完全符合0bug一書裡面的C/C++無錯化程序設計原則,各位讀者請注意哈。
不過,雖然是測試代碼,但是帶了很多工程型代碼的影子,大家有興趣可以看看。
另外,上述代碼沒有做鎖封裝,但是,仍然是多線程安全的。大家有注意到沒有?
因為其工作原理是查表法,所有的表構造時一次生成,以後僅僅是純讀,請《0bug-C/C++商用工程之道》的讀者注意2.3.6節,P50頁的論述,“用鎖的最高境界--不用”,這裡符合第1條特例,“針對一個資源的所有操作都是讀的時候,可以不加鎖”。我這段代碼可以算作實例了。各位讀者可以參考一下。
好吧,就這麼多,大家有興趣可以看看。
嗯,有人可能說,這裡的隨機數產生器沒有使用srand初始化,記住,我在用我自己的工程庫,也就是《0bug-C/C++商用工程之道》的工程庫,工程庫的init動作裡面已經做過這種動作了。
代碼是VS2008下測試的,不過,我的理解,應該是跨平台的。
上述代碼在很多游戲開發中可以投入實用的。
比如說,某個NPC哨兵,他可能在某個時刻,看前後左右,或者抽煙,或者睡覺,或者和另一個哨兵聊天,這時候,可以用這個隨機數產生器,根據預設的每種動作的概率,權重,隨時求出他的行為種類,並予以展示。
再或者,暗黑裡面,我們使用暗金的裝備,每次攻擊,有百分之多少的概率出現壓碎性打擊,有多少概率出現冰凍屬性,等等,也可以用這個隨機數產生器來求。
大家慢慢想吧,呵呵。
嗯,這裡網友發現一處bug,我已經修改了,請昨天看過的朋友注意:
Code:
- if(100.0!=dNumberCount)
- {
- for(i=0;i<m_nTokenCount;i++)
- {
- m_dTokenPercentArray[i]/=dNumberCount;
- m_dTokenPercentArray[i]*=100; //這裡少乘了個100,百分比動態調整失效,因此,我加上了這一句。
- }
- }
這段代碼出來後,一些網友表示看不懂我的原意,我們在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