C# 蓄水池抽樣

XSpringSun發表於2022-05-25

蓄水池取樣演算法解決的是在給定但長度未知的大資料集中,隨機等概率抽取一個資料。如果知道資料的長度,可以用隨機數rand()%n得到一個確切的隨機位置,或者分塊取值來構造隨機,那麼該位置的物件就是所求的物件,選中的概率是1/n。那長度未知特別是如果這個大資料集不能一次性放入記憶體中,蓄水池抽樣演算法就非常有用,在我的專案中採用的蓄水池隨機抽樣還加入了權重的計算。

其中方法中核心程式碼,也就是蓄水池抽樣就是如下程式碼。

if (i < spotQuantity)
{
    titleIndexList.Add(i);
    eigenValueList.Add(tempEigenValue);
}
else
{
    double minEigenValue = eigenValueList.Min();
    int minIndex = eigenValueList.IndexOf(minEigenValue);

    if (tempEigenValue > minEigenValue)
    {
        eigenValueList[minIndex] = tempEigenValue;
        titleIndexList[minIndex] = i;
    }
}

首先從計算出的要抽取多少數量,根據資料迴圈,先讓抽取數量的資料放入池子中titleIndexList,並且將對應資料的權重放入到抽取資料的權重列表。
在後面的迴圈中,判斷抽取的權重如果大於已經抽取的最小權重則替換最小權重的資料為當前迴圈的資料。
如果你不是按照權重,則可以產生一個隨機數,如果隨機數落在已經抽取佇列的陣列下標內,則替換掉原來的下標資料也能實現隨機性。

        public static void WeightedSampling(List<article> articleList, int grade)
        {
            //根據傳入的grade 計算一個抽樣數量。
            double sampleFactor = (double)Math.Pow((double)1 / (1 + grade), Math.E);
            var spotQuantity = (int)Math.Ceiling(articleList.Count() * sampleFactor);
            //如果規則抽的數量已經超過隨機抽取數則不再抽取
            var spotedCount = articleList.Where(t => t.isspot == 1).Count();
            if (spotedCount >= spotQuantity)
                return;
            //如果數量不足則補齊
            spotQuantity -= spotedCount;
            var spotTitleList = articleList.Where(t => t.isspot != 1).ToList();
            //例項化池子和資料權重List
            List<int> titleIndexList = new List<int>();
            List<double> eigenValueList = new List<double>();

            if (spotArticle.Count() <= spotQuantity)
            {
                for (int i = 0; i < spotArticle.Count(); i++)
                {
                    spotArticle[i].isspot = 1;
                }
            }
            else
            {
                var random = new Random();
                for (int i = 0; i < spotTitleList.Count; i++)
                {
                    double tempWeight = spotTitleList[i].eigenvalue;
                    double tempEigenValue = Math.Pow(random.NextDouble(), 1 / tempWeight);

                    if (i < spotQuantity)
                    {
                        titleIndexList.Add(i);
                        eigenValueList.Add(tempEigenValue);
                    }
                    else
                    {
                        double minEigenValue = eigenValueList.Min();
                        int minIndex = eigenValueList.IndexOf(minEigenValue);

                        if (tempEigenValue > minEigenValue)
                        {
                            eigenValueList[minIndex] = tempEigenValue;
                            titleIndexList[minIndex] = i;
                        }
                    }
                }
                //將抽取出來的物件isspot 抽取標誌設定為1
                foreach (var index in titleIndexList)
                {
                    spotTitleList[index].isspot = 1;
                }
            }
        }

該方法對於我們平時專案中抽取不知道資料長度的隨機數是非常好用的演算法,同時該演算法不復雜其時間複雜度為O(n)。

相關文章