【问题标题】:Algorithm for selecting random times within a window per user在每个用户的窗口内选择随机时间的算法
【发布时间】:2013-03-22 18:07:17
【问题描述】:

我需要每天随机选择 N 个事件,但它们之间不能太接近(M)。因此,N 个事件必须在特定窗口 (W) 内相隔至少 M。在这种情况下,我想到的窗口是 12 小时。

  • N = 事件数
  • T = 事件发生的时间 (UTC)
  • M = 它们应该分开的最小因子(小时)。
  • W = 事件的窗口期(现在到现在 + 12 小时)。
  • U = 用户(可能对这个问题不重要)

我大概可以解决这个问题,但我认为这将是一个有趣的 StackOverflow 问题,并且对人们如何解决它感兴趣。

提前致谢:)

更新:将答案移至答案

【问题讨论】:

  • 你应该表现出你的努力。
  • 是的,我将在 C# 代码中发布我的分析器,我想查看代码示例。
  • 如果在 W 中相隔 M 的事件少于 N 怎么办?是否应该移除 M 约束以允许选择 N 个事件?
  • 当您选择时,您的窗口中是否有所有事件,或者您选择一个然后查询下一个?
  • 另外,您对活动的持续时间有何看法?它是固定的,还是不是问题?

标签: c# algorithm random


【解决方案1】:

试试这个:

它随机分割可用时间(窗口 - 计数 * 最小值),然后对时间进行排序并添加最小数量以产生最终的事件数组T[]

    static Random rnd=new Random();

    static void Main(string[] args)
    {
        double W=12;
        double M=1.0;

        int N=7;

        double S=W-(N-1)*M;
        double[] T=new double[N];

        for(int i=0; i<N; i++)
        {
            T[i]=rnd.NextDouble()*S;
        }
        Array.Sort(T);
        for(int i=0; i<N; i++)
        {
            T[i]+=M*i;
        }

        Console.WriteLine("{0,8} {1,8}", "#", "Time");
        for(int i=0; i<N; i++)
        {
            Console.WriteLine("{0,8} {1,8:F3}", i+1, T[i]);    
        }

        // With N=3, Window 12h, Min. Span = 5h
        //      #     Time
        //      1    0.468
        //      2    5.496
        //      3   10.529

        // With N=7, Window 12h, Min. Span = 1h
        //      #     Time
        //      1    0.724
        //      2    2.771
        //      3    4.020
        //      4    5.790
        //      5    7.331
        //      6    9.214
        //      7   10.673
    }

作为一项检查,当最小时间完全覆盖时间窗口时,事件是等距的。因此,对于 12 小时窗口上的 3 个事件,最短时间为 6 小时,此算法按预期在 0.0 、6.0 和 12.0 产生事件。

【讨论】:

  • 这真的很聪明,但非常可预测。你可以假设你每 5 小时 X 分钟就会得到一些东西。不过,非常聪明。
  • 我看到了@KhalidAbuhakmeh 所说的关于下一个事件在可预测范围内的批评,我同意。
  • 关于可预测性,我不认为这是一个有效的批评。如果你想要 N 个事件,至少相隔 M,你总是可以预测如果 event1 发生在 T,那么 event2 将至少是 T+M。问题的限制使它如此。我们对此无能为力。
【解决方案2】:

您可以在这里使用我的问题的想法:Generating non-consecutive combinations,本质上要求您只解决 M=0 的情况。

如果你想跳过描述,算法在帖子末尾给出,它没有不可预测的while循环等,并且保证是O(N log N)时间(本来是O(N) ,如果不是用于排序步骤)。


详细说明

为了将一般的 M 情况简化为 M=0 的情况,我们将每个可能的组合(具有“至少 M 约束”)映射到没有“至少 M”分离约束的组合。

如果您的事件位于 T1, T2, .., TN 使得 T1 &lt;= T2 -M, T2 &lt;= T3 - M ... 您将它们映射到事件 Q1, Q2, .. QN 使得

Q1 = T1
Q2 = T2 - M
Q3 = T3 - 2M
...
QN = TN - (N-1)M.

这些Q满足Q1 &lt;= Q2 &lt;= ... &lt;= QN的性质,映射为1到1。(从T可以构造Q,从Q可以构造T)。

所以您需要做的就是生成Q(本质上是M=0 的情况),并将它们映射回T

注意生成Q的窗口变成[Now, Now+12 - (N-1)M]

要解决M=0 问题,只需在您的窗口中生成N 随机数并对其进行排序。


最终算法

因此你的整个算法将是

Step 1) Set Window = [Start, End - (N-1)M]
Step 2) Generate N random numbers in the Window.
Step 3) Sort the numbers generated in Step 2. Call them Q1, Q2, .. , QN
Step 4) Create Ti with the formula Ti = Qi + (i-1)M, for i = 1 to N.
Step 5) Output T1,T2,..,TN

【讨论】:

    【解决方案3】:

    如果我们假设事件是瞬时发生的(因此,可以在时间 = 窗口结束时发生,您可以执行以下操作:

    //Using these, as defined in the question
    double M;
    int N;
    DateTime start; //Taken from W
    DateTime end; //Taken from W
    
    //Set these up.
    Random rand = new Random();
    List<DateTime> times;
    //This assumes that M is 
    TimeSpan waitTime = new TimeSpan.FromHours(M);
    int totalSeconds = ((TimeSpan)end-start).TotalSeconds;
    
    while( times.Count < N )
    {
        int seconds = rand.Next(totalSeconds);
        DateTime next = start.AddSeconds(seconds);
        bool valid = true;
        if( times.Count > 0 )
        {
            foreach( DateTime dt in times )
            {
                valid = (dt > next && ((TimeSpan)dt - next) > waitTime) ? true : false;
                if( !valid )
                {
                    break;
                }
            }
        }
        if( valid )
        {
            times.Add(next);
        }
    }
    

    现在,在一个 12 小时的窗口中,每个事件在下一个事件之前至少一个小时后,您最好有一个小的 N - 我上面的伪代码不会检查是否可以将 N 个事件放入 X 时间每个事件之间的 M 小时。

    【讨论】:

      【解决方案4】:
      timeItems = new List();
      int range;
      double randomDouble;
      
      for i = 1 to N
      {   
         range = W/M;
      
         //assumes generate produces a random number between 0 and 1 (exclusive)
         randomDouble = RandomGenerator.generate() * (range); 
         T =  Math.floor(randomDouble)*M;
         timeItems.add(T);
      }
      
      return timeItems
      

      【讨论】:

      • 这并不能保证事件至少相隔 M 小时。
      • 你是对的,更新了 "T = Math.floor(randomDouble)" 行以合并 M
      • 如果您的随机函数返回两个足够接近的结果,它们将具有相同的 randomDouble 值,从而产生两个相同的 T 值,即两个相同的值。
      • 我认为这没有问题。例如,如果 range=10,那么 0.5343552324 和 0.5334645 实际上会给你一个 randomDouble 5。这有什么问题?您关心的数字中唯一的部分是小数点后的数字
      • 如果两个T值相同,则两个事件同时发生,至少相隔M时间。
      【解决方案5】:

      首先考虑生成 一个 事件的工作,这样还有 (n-1) 个事件要生成(每个事件需要至少相隔 M)并且总共剩下 w 时间。

      时间 t 可以在 0 到 w-(n-1)m 之间。 t 的平均值应为 w/(n-1)。现在,使用任何你喜欢的分布(我推荐泊松)来生成一个均值为 w/(n-1) 的随机数。如果数字高于 w-n-1)m,则再次生成。这会给你的。

      递归调用 (offset=offset+t, w=w-t, n=n-1, m=m) 生成更多数字。

      def generate(offset, w, n, m):
          mean = w/(n-1);
          t=ininity;
          while (t> (w-(n-1)m)):
               t= poisson( w/(n-1) )
          return [t+offset] + generate(offset+t, w-t, n-1, m)
      

      我没有为角落条件和其他情况编码,我留给你。

      【讨论】:

        【解决方案6】:

        这是我的解决方案。如果您分开的时间和 numOfEvents 冲突,您会得到一些奇怪的行为。尝试一下,看看会发生什么。

        using System;
        using System.Collections.Generic;
        
        namespace RandomScheduler
        {
            class Program
            {
                public static Random R = new Random();
        
                static void Main()
                {
                    var now = DateTime.Now;
                    var random = new Random((int)now.Ticks);
                    const int windowOfHours = 12;
                    const int minimumTimeApartInHours = 2;
                    const int numOfEvents = 5;
        
                    // let's start the window 8 AM
                    var start = new DateTime(now.Year, now.Month, now.Day, 8, 0, 0, 0);
                    // let's end 12 hours later
                    var end = start.AddHours(windowOfHours);
        
                    var prev = null as DateTime?;
                    const int hoursInEachSection = windowOfHours / numOfEvents;
        
                    var events = new List<DateTime>();
        
                    for (var i = 0; i < numOfEvents; i++)
                    {
                        // if there is a previous value
                        // let's at least start 2 hours later
                        if (prev.HasValue)
                            start = prev.Value.AddHours(minimumTimeApartInHours);
        
                        DateTime? @event;
                        do
                        {
                            // pick a random double, so we get minutes involved
                            var hoursToAdd = random.NextDouble()*hoursInEachSection;
                            // let's add the random hours to the start
                            // and we get our next event
                            @event = start.AddHours(hoursToAdd);
        
                        // let's make sure we don't go past the window
                        } while (@event > end); 
        
                        prev = @event;
                        events.Add(@event.Value);
                    }
        
                    Console.WriteLine(string.Join("\n", events));
                    Console.ReadLine();
                }
            }
        }
        

        【讨论】:

          猜你喜欢
          • 2014-06-01
          • 1970-01-01
          • 2015-12-26
          • 2012-10-04
          • 1970-01-01
          • 2020-07-14
          • 2015-01-13
          • 2023-01-11
          • 2020-10-30
          相关资源
          最近更新 更多