一.應用情境:
有時我們需要從一些列資料中根據權重隨機選取指定條數記錄出來,這裡需要權重、隨機,我們根據權重越大的,出現機率越大。例如廣告系統:可根據客戶支付金額大小來調控客戶們的廣告出現機率,客戶支付金額越大,其廣告出現頻率越頻繁,例如:加入有10條廣告,然後每條廣告都有一個權重,我們每次要根據權重選取5條廣告出來進行顯示。有了需求,我們就進行解決,本文章就是利用一種簡單的演算法來實現根據權重來隨機選取。
二.簡單演算法的實現:
根據我們需求,上網找了不少資料,都沒有找到一種比較適合的方案,就自己想了一個簡單的實現方案,試了一下,效果還挺不錯的,現在和大家分享分享,有不合理或者錯誤之處,還望各位高手糾正。廢話少說,其實演算法很簡單,如下:
- 每個廣告都有一個權重,用W表示。我們假設最小的權重為1,數字越大的權重越高。
- 計算出所有廣告的權重總和,用SUM表示。
- 遍曆每個廣告,將它的權重W與從0到(SUM-1)的一個隨機數(即權重總和SUM以內的隨機數)相加,得到新的權重排序值,用S表示。
- 根據廣告權重排序值S從大到小進行排序。
- 根據排序結果選取最前面的幾條記錄就是我們所需要的結果。
其簡單原理如下:
1.假如我們有A、B、C、D四個廣告,其權重分別為1、2、3、4,則其總權重SUM=1+2+3+4=10。
2.根據其權重值與一個小於SUM的隨機值(由於SUM=10,則隨機值範圍是0~9)相加,得到一個權重排序值,如下:
廣告 |
權重值 |
最小權重排序值 |
最大權重排序值 |
A |
1 |
1+0=1 |
1+9=10 |
B |
2 |
2+0=2 |
2+9=11 |
C |
3 |
3+0=3 |
3+9=12 |
D |
4 |
4+0=4 |
4+9=13 |
3.由此可以得出A、B、C、D的範圍,由上面可以看出A是1~10之間的數,而D的範圍是4~13,由此可以看出,D得出隨機數大的機率比較大。所以我們只要根據其出現機率再排一下順序,再選取排在權重排序值比較大的前幾項即可,這就可以得到我們所需要的根據權重選取機率的廣告。
三.C#代碼演算法的實現:
1.首先我們先聲明一個權重基礎類RandomObject,以後只要繼承該類即可。其代碼如下:
/// <summary>
/// 權重對象
/// </summary>
public class RandomObject
{
/// <summary>
/// 權重
/// </summary>
public int Weight { set; get; }
}
這個類我們只定義一個Weight屬性,其它屬性可由繼承它的類根據具體業務來實現具體屬性。
2.建立一個輔助類RandomHelper,新增一個實現根據權重隨機選取條數的的函數,其代碼如下:
/// <summary>
/// 演算法:
/// 1.每個廣告項權重+1命名為w,防止為0情況。
/// 2.計算出總權重n。
/// 3.每個廣告項權重w加上從0到(n-1)的一個隨機數(即總權重以內的隨機數),得到新的權重排序值s。
/// 4.根據得到新的權重排序值s進行排序,取前面s最大幾個。
/// </summary>
/// <param name="list">原始列表</param>
/// <param name="count">隨機抽取條數</param>
/// <returns></returns>
public static List<T> GetRandomList<T>(List<T> list, int count) where T: RandomObject
{
if (list == null || list.Count <= count || count <= 0)
{
return list;
}
//計算權重總和
int totalWeights = 0;
for (int i = 0; i < list.Count; i++)
{
totalWeights += list[i].Weight + 1; //權重+1,防止為0情況。
}
//隨機賦值權重
Random ran = new Random(GetRandomSeed()); //GetRandomSeed()隨機種子,防止快速頻繁調用導致隨機一樣的問題
List<KeyValuePair<int, int>> wlist = new List<KeyValuePair<int, int>>(); //第一個int為list下標索引、第一個int為權重排序值
for (int i = 0; i < list.Count; i++)
{
int w = (list[i].Weight + 1) + ran.Next(0, totalWeights); // (權重+1) + 從0到(總權重-1)的隨機數
wlist.Add(new KeyValuePair<int, int>(i, w));
}
//排序
wlist.Sort(
delegate(KeyValuePair<int, int> kvp1, KeyValuePair<int, int> kvp2)
{
return kvp2.Value - kvp1.Value;
});
//根據實際情況取排在最前面的幾個
List<T> newList = new List<T>();
for (int i = 0; i < count; i++)
{
T entiy = list[wlist[i].Key];
newList.Add(entiy);
}
//隨機法則
return newList;
}
為了適合各個情境,我們定義T是一個泛型類,該類務必繼承RandomObject類,實現自己類資訊。該方法有相關的注釋,這裡就不再詳細重複描述。
特別注意一下GetRandomSeed()這個函數,這個是為了防止在短時間內頻繁建立和調用Random時候,會出現重複的隨機數(也就是隨機不在是隨機的問題),其函數代碼如下:
/// <summary>
/// 隨機種子值
/// </summary>
/// <returns></returns>
private static int GetRandomSeed()
{
byte[] bytes = new byte[4];
System.Security.Cryptography.RNGCryptoServiceProvider rng = new System.Security.Cryptography.RNGCryptoServiceProvider();
rng.GetBytes(bytes);
return BitConverter.ToInt32(bytes, 0);
}
3.調用方法:
調用方法很簡單,只需要聲明一個自己的實體,整合Object,然後根據實際情況直接調用RandomHelper.GetRandomList()方法即可。
如下:
//1.聲明一個繼承RandomObject的實體類,如:*
public class AdvertEntity : RandomObject
{
/// <summary>
/// 名稱
/// </summary>
public string Name { set; get; }
/// <summary>
/// 描述
/// </summary>
public string Description { set; get; }
//...其它相關的欄位/屬性
}
//2.初始化調用資料,如:
//初始化類比權重資料
List<Advert> list = new List<Advert>();
list.Add(new Advert { Name = "A", Weight = 1 });
list.Add(new Advert { Name = "B", Weight = 2 });
list.Add(new Advert { Name = "C", Weight = 2 });
list.Add(new Advert { Name = "D", Weight = 3 });
list.Add(new Advert { Name = "E", Weight = 4 });
list.Add(new Advert { Name = "F", Weight = 5 });
list.Add(new Advert { Name = "G", Weight = 60 });
//根據權重隨機取指定記錄條數
List<Advert> newList = RandomHelper.GetRandomList<Advert>(list, 2); //這個就是用法,newList就是隨機取出的記錄(第二個參數就是隨機取多少條)
四.測試回合結果如下:
五.附原始碼下載:
點擊下載原始碼