2008-11-03 60 views
0

對於一個隨機事件發生器,我在寫我需要一個簡單的算法來生成隨機範圍。在C#中獲得一個範圍內的隨機持續時間

所以,例如:

我可以說我希望10周隨機的時間間隔,和1/1 1/7之間,沒有重疊,在狀態(1,2,3),其中狀態1個事件添加最多1天,狀態2事件加起來2天,狀態3事件加起來剩下的事情。

或者代碼:

struct Interval 
{ 
    public DateTime Date; 
    public long Duration; 
    public int State; 
} 

struct StateSummary 
{ 
    public int State; 
    public long TotalSeconds; 
} 

public Interval[] GetRandomIntervals(DateTime start, DateTime end, StateSummary[] sums, int totalEvents) 
{ 
    // insert your cool algorithm here 
} 

我的工作現在這個樣子,但如果有人打我的解決方案(或優雅的預先存在的算法都知道),我對SO張貼本。

+0

無論間隔(狀態2),狀態1事件總是加起來1嗎?你需要多少隨機數?什麼類型的分配? – 2008-11-03 00:35:53

+0

正確,在示例狀態1事件總是加起來1天,狀態2總是2天。所以它是隨機的,但它有上限。我想要在一些標準偏差內分發東西。 – 2008-11-03 00:49:51

+0

標準偏差在3個事件中分佈的樣本量爲10時實際上沒有意義。 – 2008-11-03 04:20:24

回答

0

這是我目前的實施,似乎工作正常,佔所有時間。如果我不必定位目標,這將會非常乾淨.net 1.1

public class Interval 
{ 
    public Interval(int state) 
    { 
     this.State = state; 
     this.Duration = -1; 
     this.Date = DateTime.MinValue; 
    } 
    public DateTime Date; 
    public long Duration; 
    public int State; 
} 

class StateSummary 
{ 
    public StateSummary(StateEnum state, long totalSeconds) 
    { 
     State = (int)state; 
     TotalSeconds = totalSeconds; 
    } 
    public int State; 
    public long TotalSeconds; 
} 

Interval[] GetRandomIntervals(DateTime start, DateTime end, StateSummary[] sums, int totalEvents) 
{ 
    Random r = new Random(); 
    ArrayList intervals = new ArrayList(); 

    for (int i=0; i < sums.Length; i++) 
    { 
     intervals.Add(new Interval(sums[i].State)); 
    } 

    for (int i=0; i < totalEvents - sums.Length; i++) 
    { 
     intervals.Add(new Interval(sums[r.Next(0,sums.Length)].State)); 
    } 

    Hashtable eventCounts = new Hashtable(); 
    foreach (Interval interval in intervals) 
    { 
     if (eventCounts[interval.State] == null) 
     { 
      eventCounts[interval.State] = 1; 
     } 
     else 
     { 
      eventCounts[interval.State] = ((int)eventCounts[interval.State]) + 1; 
     } 
    } 

    foreach(StateSummary sum in sums) 
    { 
     long avgDuration = sum.TotalSeconds/(int)eventCounts[sum.State]; 
     foreach (Interval interval in intervals) 
     { 
      if (interval.State == sum.State) 
      { 
       long offset = ((long)(r.NextDouble() * avgDuration)) - (avgDuration/2); 
       interval.Duration = avgDuration + offset; 
      } 
     } 
    } 

    // cap the durations. 
    Hashtable eventTotals = new Hashtable(); 
    foreach (Interval interval in intervals) 
    { 
     if (eventTotals[interval.State] == null) 
     { 
      eventTotals[interval.State] = interval.Duration; 
     } 
     else 
     { 
      eventTotals[interval.State] = ((long)eventTotals[interval.State]) + interval.Duration; 
     } 
    } 

    foreach(StateSummary sum in sums) 
    { 
     long diff = sum.TotalSeconds - (long)eventTotals[sum.State]; 
     if (diff != 0) 
     { 
      long diffPerInterval = diff/(int)eventCounts[sum.State]; 
      long mod = diff % (int)eventCounts[sum.State]; 
      bool first = true; 
      foreach (Interval interval in intervals) 
      { 
       if (interval.State == sum.State) 
       { 
        interval.Duration += diffPerInterval; 
        if (first) 
        { 
         interval.Duration += mod; 
         first = false; 
        } 

       } 
      } 
     } 
    } 

    Shuffle(intervals); 

    DateTime d = start; 
    foreach (Interval interval in intervals) 
    { 
     interval.Date = d; 
     d = d.AddSeconds(interval.Duration); 
    } 

    return (Interval[])intervals.ToArray(typeof(Interval)); 
} 

public static ICollection Shuffle(ICollection c) 
{ 
    Random rng = new Random(); 
    object[] a = new object[c.Count]; 
    c.CopyTo(a, 0); 
    byte[] b = new byte[a.Length]; 
    rng.NextBytes(b); 
    Array.Sort(b, a); 
    return new ArrayList(a); 
} 
1

首先使用DateTime.Subtract以確定有多少分/秒/無論你的最小和最大日期之間。然後使用Math.Random獲得隨機數分鐘/秒/任何在該範圍內。然後使用該結果構造另一個TimeSpan實例並將其添加到最小日期時間。

0

這是一個編譯和工作的實現,儘管它仍然有些粗糙。它要求輸入狀態數組適當地考慮整個感興趣的時間範圍(結束 - 開始),但是添加一些代碼會使得最終狀態填滿在第一個N中未考慮的時間將是微不足道的-1個州。我還修改了你的結構定義,在整個持續時間內使用整數而不是長整數,只是爲了簡化一點。

爲了清楚(和懶惰),我省略了所有錯誤檢查。它可以很好地處理您所描述的輸入,但絕不是無懈可擊的。

public static Interval[] GetRandomIntervals(DateTime start, DateTime end, 
    StateSummary[] states, int totalIntervals) 
{ 
    Random r = new Random(); 

    // stores the number of intervals to generate for each state 
    int[] intervalCounts = new int[states.Length]; 

    int intervalsTemp = totalIntervals; 

    // assign at least one interval for each of the states 
    for(int i = 0; i < states.Length; i++) 
     intervalCounts[i] = 1; 
    intervalsTemp -= states.Length; 

    // assign remaining intervals randomly to the various states 
    while(intervalsTemp > 0) 
    { 
     int iState = r.Next(states.Length); 
     intervalCounts[iState] += 1; 
     intervalsTemp -= 1; 
    } 

    // make a scratch copy of the state array 
    StateSummary[] statesTemp = (StateSummary[])states.Clone(); 

    List<Interval> result = new List<Interval>(); 
    DateTime next = start; 
    while(result.Count < totalIntervals) 
    { 
     // figure out which state this interval will go in (this could 
     // be made more efficient, but it works just fine) 
     int iState = r.Next(states.Length); 
     if(intervalCounts[iState] < 1) 
      continue; 
     intervalCounts[iState] -= 1; 

     // determine how long the interval should be 
     int length; 
     if(intervalCounts[iState] == 0) 
     { 
      // last one for this state, use up all remaining time 
      length = statesTemp[iState].TotalSeconds; 
     } 
     else 
     { 
      // use up at least one second of the remaining time, but 
      // leave some time for the remaining intervals 
      int maxLength = statesTemp[iState].TotalSeconds - 
       intervalCounts[iState]; 
      length = r.Next(1, maxLength + 1); 
     } 

     // keep track of how much time is left to assign for this state 
     statesTemp[iState].TotalSeconds -= length; 

     // add a new interval 
     Interval interval = new Interval(); 
     interval.State = states[iState].State; 
     interval.Date = next; 
     interval.Duration = length; 
     result.Add(interval); 

     // update the start time for the next interval 
     next += new TimeSpan(0, 0, length); 
    } 

    return result.ToArray(); 
}