LINQ选择自定义随机

时间:2014-09-18 23:38:44

标签: c# linq random skip

我有这段代码从LINQ查询中选择一个随机条目

Random rand = new Random();
int TotalFound = Query.Count();
if (TotalFound > 0) {
    int toSkip = rand.Next(0, TotalFound);
    Video Result = Query.Skip(toSkip).Take(1).First();
    return Result;
} else
    return null;

现在,我想在Query中添加一个名为“Preference”的列,它是一个介于0和10之间的数字。具有Preference值8的行将被选择为Preference值为的行的两倍。 4.更有可能选择9.8的值。

实施此算法的有效方法是什么?

作为第二步,我可以添加一个参数,允许8为3x或4x,每行设置为4,用指数曲线而不是线性曲线微调结果。

我真的不确定如何有效地实施这项工作。

以下是我实施的解决方案

public static int? SelectRandomId(IQueryable<Media> query, out int totalFound) {
    int? Result = null;

    // Pull list of ID and Preference from database.
    var IdList = query.Select(v => new { ID = v.MediaId, Preference = v.Preference }).ToList();
    totalFound = IdList.Count();

    // Calculate preferences average and total.
    int PreferenceCount = IdList.Where(v => v.Preference.HasValue).Count();
    int NoPreferenceCount = IdList.Count() - PreferenceCount;
    int PreferenceSum = IdList.Where(v => v.Preference.HasValue).Sum(v => PreferenceToInt(v.Preference.Value));
    // Use value 10 for every item if it is not specified for any.
    int PreferenceAvg = (PreferenceCount > 0 ? PreferenceSum / PreferenceCount : 10);
    // Videos with no preference get the average value.
    int PreferenceTotal = PreferenceSum + NoPreferenceCount * PreferenceAvg;

    // Get a random number between zero and the sum of all the preferences
    Random rand = new Random();
    int number = rand.Next(0, PreferenceTotal);

    int rollingSumOfPreferences = 0;

    // Set default value in case a value doesn't get assigned by the loop.
    if (totalFound > 0)
        Result = IdList[0].ID;

    // Select an index from the video list, but weighted by preference
    foreach (var item in IdList) {
        // Add the current item's preference to the rolling sum
        if (item.Preference.HasValue)
            rollingSumOfPreferences += PreferenceToInt(item.Preference.Value);
        else
            rollingSumOfPreferences += PreferenceAvg;

        // If we've hit or passed the random number, select this item
        if (rollingSumOfPreferences >= number) {
            Result = item.ID;
            break;
        }
    }

    return Result;
}

private static int PreferenceToInt(float preference) {
    return (int)(Math.Pow(preference, 1.2) * 100);
}

2 个答案:

答案 0 :(得分:1)

我认为如果你在0[the sum of all preferences]之间随机选择可能会有效。然后,您可以遍历所有项目并将项目首选项的滚动总和存储在另一个变量中。一旦您点击滚动金额等于或大于随机数的项目,请选择该项目。这应该按降序排列较大的首选项。至少在我的测试中它起作用了!

希望这段代码可以更好地解释它:

private class Video
{
    public string Name { get; set; }
    public int Preference { get; set; }
}

public static void GenericTester()
{
    // Initialize array with item name and preference
    var videos = new List<Video>
    {
        new Video {Name = "ten", Preference = 10},
        new Video {Name = "nine", Preference = 9},
        new Video {Name = "eight", Preference = 8},
        new Video {Name = "seven", Preference = 7},
        new Video {Name = "six", Preference = 6},
        new Video {Name = "five", Preference = 5},
        new Video {Name = "four", Preference = 4},
        new Video {Name = "three", Preference = 3},
        new Video {Name = "two", Preference = 2},
        new Video {Name = "one", Preference = 1},
        new Video {Name = "zero", Preference = 0}
    };

    // Dictionary to store results of how many times each
    // preference was selected (for testing purposes)
    var results = new Dictionary<int, int>();
    for (int i = 0; i <= videos.Max(v => v.Preference); i++)
    {
        results[i] = 0;  // Init all items to zero
    }

    // Init random number generator
    var rand = new Random();

    for (int n = 1; n < 100000; n++)
    {
        // Get a random number between zero and the sum of all the preferences
        var number = rand.Next(0, videos.Sum(v => v.Preference));

        // Initialize index to the highest preference
        var index = videos.Max(v2 => v2.Preference);
        var rollingSumOfPreferences = 0;

        // Select an index from the video list, but weighted by preference
        foreach(var video in videos)
        {
            // Add the current item's preference to the rolling sum
            rollingSumOfPreferences += video.Preference;

            // If we've hit or passed the random number, select this item
            if (rollingSumOfPreferences >= number)
            {
                index = video.Preference;
                break;
            }
        }

        // Increment the count for the selected preference
        results[index]++;
    }

    foreach (var result in results)
    {
        Console.WriteLine("The preference value '{0}' was selected '{1}' times.", result.Key, result.Value);
    }
}

答案 1 :(得分:0)

随机功能提供相对均匀的分布,因此它自己不会给你你想要的东西。以下Eric Lippert文章提供了一些背景知识和正确方向的步骤(因为通过转换均匀分布可以实现许多非均匀分布):

http://blogs.msdn.com/b/ericlippert/archive/2012/02/21/generating-random-non-uniform-data-in-c.aspx

生成良好统计分布的代码可能非常复杂,因此许多人使用第三方库。这个非常好:

http://www.extremeoptimization.com/

如果你想对自己的发行版进行破解,那么MSDN文档将展示如何覆盖Sample方法:

http://msdn.microsoft.com/en-us/library/system.random.sample(v=vs.110).aspx

假设您可以创作或找到符合您需求的随机发行版,我会执行以下操作:

Random uniformDist = new Random();
YourRand yourDist = new YourRand();
int groupToSelect = yourDist.Next(1, 10);
int TotalFound = Query.Where(v => v.Preference == groupToSelect).Count();
if (TotalFound > 0) {
    int toSkip = uniformDist.Next(0, TotalFound);
    Video Result = Query.Where(v => v.Preference == groupToSelect).Skip(toSkip).Take(1).First();
    return Result;
} else
    return null;

根据数据库中的行数,我将运行SQL分析器,以确保您可以获得足够的效果。