在C#中获取范围内的随机工期
本文关键字:随机 范围内 获取 | 更新日期: 2023-09-27 17:47:48
对于我正在编写的随机事件生成器,我需要一个简单的算法来生成随机范围。
例如:
我可以说,我想要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.
首先使用DateTime.Subtract来确定最小日期和最大日期之间的分/秒数。然后使用Math.Rom得到一个分钟/秒/该范围内的随机数。然后使用其结果构造另一个TimeSpan实例,并将其添加到最小DateTime中。
这里有一个编译并运行的实现,尽管它仍然有些粗糙。它要求输入状态数组正确地考虑感兴趣的整个时间范围(结束-开始),但添加一点代码会使最终状态填满前N-1个状态中没有考虑的时间,这将是微不足道的。我还修改了您的结构定义,使用int而不是long来表示持续时间,只是为了稍微简化一些。
为了清晰(和懒惰),我省略了所有的错误检查。它适用于您所描述的输入,但它决不是防弹的。
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();
}
这是我目前的实现,它似乎可以正常工作,并一直有效。如果我不需要瞄准.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);
}