【问题标题】:Displaying random rows without repetition显示随机行而不重复
【发布时间】:2019-02-04 10:29:34
【问题描述】:

我的问题是:

  1. 如何展示随机广告,使相同的广告不会再次出现。
  2. 如何重视具有较高信用价值的广告。

Sql Server 数据库表

AdId AdName AdUrl     Credits
 1    Ad1    abc.com   10
 2    Ad2    def.com   40
 3    Ad3    fgi.com   30
 4    Ad4    xyz.com   10

上面的 ads 表可能包含 1000 条记录。我想一次展示 10 个广告(通过重视具有更高价值的信用)。

我的方法

  1. 我目前正在做的是从广告表中随机选择 10 个广告并显示它们。
  2. 还在广告表中添加了一个标志 IsAdDisplayed(布尔值),这样同一个随机广告就不会重复(直到它显示所有广告)
  3. 当我完成所有广告的展示后,我将 IsAdDisplayed 再次更新为 false。

LINQ

var result = (from u in idb.ads
      select u).OrderBy(x => Guid.NewGuid()).Take(10);

但这不会考虑更高的学分。

这是正确的方法吗?如何展示随机广告(考虑信用)以及相同的广告不会重复展示?

【问题讨论】:

  • 我不会随机选择并设置布尔标志,而是将列表打乱,然后遍历打乱的列表。
  • 无论学分对第一次随机排序有什么影响:如果有 1000 条记录,则在至少 100 次查询后会再次出现价值更高的广告。
  • 我会使用 HashSet 或独特的容器
  • 这样的事情只有在您有一个用户的情况下才有效,否则您需要有关当前用户看过哪些广告的信息。您无需更新数据库,但拥有 cookie 并请求广告,不包括那些标记为已插入当前 cookie 的广告。
  • 按“信用”对广告进行排序,信用最高的广告在顶部,然后在位置 0 到长度为 1 处随机选择一个广告。当您选择一个广告时,只需将其从列表中删除,这样就无法再次选择它。这是一个棘手的问题,你如何使你的随机数更有可能出现在列表的开头?有多种选择随机数 1 - N 的方法,这样更容易出现较小的数字。

标签: c# sql-server asp.net-mvc linq razor


【解决方案1】:

尝试以下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;

namespace ConsoleApplication100
{
    class Program
    {
        static void Main(string[] args)
        {
            DataTable dt = new DataTable();
            dt.Columns.Add("AdId", typeof(int));
            dt.Columns.Add("AdName", typeof(string));
            dt.Columns.Add("AdUrl", typeof(string));
            dt.Columns.Add("Credits", typeof (int));
            dt.Rows.Add(new object[] {1, "Ad1", "abc.com", 10});
            dt.Rows.Add(new object[] {2, "Ad2", "def.com", 40});
            dt.Rows.Add(new object[] {3, "Ad3", "fgi.com", 30});
            dt.Rows.Add(new object[] {4, "Ad4", "xyz.com", 40});

            Random rand = new Random();
            List<DataRow> randomRows = dt.AsEnumerable().Select(x => new { row = x, rand = rand.Next() }).OrderBy(x => x.rand).Select(x => x.row).ToList();
            DataTable dt2 = randomRows.CopyToDataTable();

        }
    }
}

【讨论】:

  • 每次用户提出的每个请求都会加载10个广告,那么有没有办法直接处理广告表,而不是复制到dt2?
  • 你不需要最后一行。我刚刚添加,以便您可以在需要时查看结果。只需使用列表。
【解决方案2】:

如何显示随机广告(考虑到信用)以及 相同的广告不会重复展示?

如果您想优先考虑学分,您可以尝试编写类似以下的查询(您可能需要将其转换为 LINQ)。

这里的记录有更多的信用将获得更高的优先级。比如credit &gt; 80 将获得最高优先级,credit &lt; 40 将获得最低优先级。

SELECT TOP 10 * 
FROM   (SELECT * 
        FROM   (SELECT TOP 80 * 
                FROM   [table] 
                WHERE  credit > 80 
                ORDER  BY Newid())A 
        UNION ALL 
        SELECT * 
        FROM   (SELECT TOP 60 * 
                FROM   [table] 
                WHERE  credit <= 80 
                       AND credit > 60 
                ORDER  BY Newid())B 
        UNION ALL 
        SELECT * 
        FROM   (SELECT TOP 40 * 
                FROM   [table] 
                WHERE  credit <= 60 
                       AND credit > 40 
                ORDER  BY Newid())C 
        UNION ALL 
        SELECT * 
        FROM   (SELECT TOP 20 * 
                FROM   [table] 
                WHERE  credit <= 40 
                ORDER  BY Newid())D) T 
ORDER  BY Newid() 

【讨论】:

    【解决方案3】:

    你写道:

    通过重视具有更高价值的学分

    这是否意味着只要您有 40 分的未展示广告,您就根本不想展示 10 分的广告?还是您的意思是您希望为具有更高信用的广告提供更高的展示机会。如果是这样:如果学分高 4 倍,展示机会是否应该大 4 倍?

    假设您不想展示信用较低的广告,只要您有较高信用的未展示广告。

    int nrOfAdsToSelect = ...;
    var selectedAds = dbContext.Adds
        .Where(ad => !ad.IsSelected)
        .OrderyDescending(ad => ad.Credit)
        .Take(nrOfAddsToSelect);
    

    但是,如果您想让所有广告都有机会展示,那么只有积分较高的广告才有更高的机会,您应该考虑一下机会高多少。

    最简单的方法是将功劳转化为整数范围,指示应选择广告的时间:

    Id Credit  => Range
    00    01      00
    01    01      01
    02    02      02, 03                                    // credit 2: 2 numbers
    03    03      04, 05, 06                                // credit 3: 3 numbers
    04    10      07, 08, 09, 10, 11, 12, 13, 14, 15, 16    // 10 numbers
    

    在 [00, 16] 范围内取一个随机数。选择Id 04的机会是选择Id 01的机会的10倍。

    因此,您需要一个函数来从 Credit 和 StartNumber 创建一个整数范围。这可以像上面一样简单,也可以是任何其他算法,具体取决于您对具有较高信用的广告机会的评分。

    您可以使用 credit-to-range 函数将未显示的 Add 序列转换为整数序列。

    作为扩展函数:

    static IEnumerable<int> ToRange(this int credit, int startNumber)
    {
         // example: Credit 40 is 40 times more chance than credit 1.
         // so range length is equal to credit
         return Enumerable.Range(startNumber, credit);
    }
    
    static IEnumerable<int> ToRange(this IEnumerable<Ad> adds)
    {
        int startNumber = 0;
        foreach (Ad add in adds)
        {
            // only use ads that are not displayed yet to select a new Ad.
            if (!ad.IsDisplayed)
            {
                 var range = add.Credit.ToRange();
                 foreach (var value in range) yield return value;
            }
        }
    }
    

    用法:

    List<Ad> ads = ...
    var range = ads.ToRange().ToList();
    int selectedIndex = rnd.Next(range.Count);
    var firstSelectedAdd = ads[selectedIndex];
    

    问题是,一旦您选择了第一个广告,第二个广告的机会就会发生变化。您需要创建一个新范围:

    static Ad Select(this IReadOnlyList<Ad> ads)
    {
        var range = ads.ToRange().ToList();
        int selectedIndex = rnd.Next(range.Count);
        return ads[selectedIndex];
    }
    
    static IEnumerable<Ad> SelectAds(this IEnumerable<Ad> ads, int selectCount)
    {
        for (int i=0; i<selectCount; ++i)
        {
            var selectedAd = ads.Select();
            yield return selectedAd;
            selectedAd.IsDisplayed = true; // to make sure it is not selected again
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-06-19
      • 2016-03-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-07-21
      • 2018-04-30
      相关资源
      最近更新 更多