gpt4 book ai didi

c# - 将 ETA 添加到嵌入式循环序列

转载 作者:行者123 更新时间:2023-12-03 17:11:06 24 4
gpt4 key购买 nike

更新 1:
下面提供的一些解决方案看起来不错。但是,我只知道循环在其父循环的迭代确定后迭代的次数。所以我不能事先计算所有的迭代。
原问题:
我在一个类似的程序中嵌入了循环:
程序 1:

using System;
using System.Threading;

namespace time_remaining_loop_strip
{
class Program
{
static void Main(string[] args)
{
var random = new Random();
Console.Clear();

// Simulate initiation delay
Console.WriteLine("initiate");
Thread.Sleep(random.Next(100, 1000));

int intCount = random.Next(1, 10);

for (int loop1 = 0; loop1 <= intCount; loop1++)
{
// Simulate loop1 delay
Console.WriteLine("\tloop1");
Thread.Sleep(random.Next(100, 1000));

for (int loop2 = 0; loop2 <= random.Next(1, 10); loop2++)
{
// Simulate loop2 delay
Console.WriteLine("\t\tloop2");
Thread.Sleep(random.Next(100, 1000));

for (int loop3 = 0; loop3 <= random.Next(1, 10); loop3++)
{
// Simulate loop3 delay
Console.WriteLine("\t\t\tloop3");
Thread.Sleep(random.Next(100, 1000));

for (int loop4 = 0; loop4 <= random.Next(1, 10); loop4++)
{
// Simulate loop4 delay
Console.WriteLine("\t\t\t\tloop4");
Thread.Sleep(random.Next(100, 1000));
}
}
}
}
}
}
}
我正在尝试显示剩余处理时间 (ETA),因此我可以看到对上述循环序列完成之前剩余时间的粗略估计
我现在有另一段代码,它确实显示了一个 ETA,当循环非常简单时它可以正常工作:
程序 2:
using System;
using System.Threading;

namespace time_remaining
{
class Program
{
public static TimeSpan ComputeRemaining((int count, DateTime time) start, (int count, DateTime time) current, int end) =>
current.count - start.count == 0
? TimeSpan.MaxValue
: TimeSpan.FromSeconds((end - current.count) * current.time.Subtract(start.time).TotalSeconds / (current.count - start.count));

static void Main(string[] args)
{
Console.Clear();

var random = new Random();
int Count = random.Next(10, 60);
DateTime startTime = DateTime.Now;

for (int i = 0; i <= Count; i++)
{
Thread.Sleep(random.Next(100, 2000));
TimeSpan timeRemaining = ComputeRemaining((0, startTime), (i, DateTime.Now), Count);

Console.SetCursorPosition(0,0);
Console.Write("ETA: ");
Console.Write(String.Format("{0} Days, {1} Hours, {2} Minutes, {3} Seconds", timeRemaining.Days.ToString().PadLeft(3,'0'), timeRemaining.Hours.ToString().PadLeft(2,'0'), timeRemaining.Minutes.ToString().PadLeft(2,'0'), timeRemaining.Seconds.ToString().PadLeft(2,'0')));
}
}
}
}
当我尝试将 Prog1 的 ETA 方面合并到 Prog2 中时,它似乎效果不佳:
Prog3 = Prog1+Prog2:
using System;
using System.Threading;

namespace time_remaining_loop_strip
{
class Program
{
public static TimeSpan ComputeRemaining((int count, DateTime time) start, (int count, DateTime time) current, int end) =>
current.count - start.count == 0
? TimeSpan.MaxValue
: TimeSpan.FromSeconds((end - current.count) * current.time.Subtract(start.time).TotalSeconds / (current.count - start.count));

static void Main(string[] args)
{
DateTime startTime = DateTime.Now;

var random = new Random();
Console.Clear();

// Simulate initiation delay
//Console.WriteLine("initiate");
Thread.Sleep(random.Next(100, 1000));

int intCount = random.Next(1, 10);

for (int loop1 = 0; loop1 <= intCount; loop1++)
{
// Simulate loop1 delay
//Console.WriteLine("\tloop1");
Thread.Sleep(random.Next(100, 1000));

for (int loop2 = 0; loop2 <= random.Next(1, 10); loop2++)
{
// Simulate loop2 delay
//Console.WriteLine("\t\tloop2");
Thread.Sleep(random.Next(100, 1000));

for (int loop3 = 0; loop3 <= random.Next(1, 10); loop3++)
{
// Simulate loop3 delay
//Console.WriteLine("\t\t\tloop3");
Thread.Sleep(random.Next(100, 1000));

for (int loop4 = 0; loop4 <= random.Next(1, 10); loop4++)
{
// Simulate loop4 delay
//Console.WriteLine("\t\t\t\tloop4");
Thread.Sleep(random.Next(100, 1000));
}
}
}

TimeSpan timeRemaining = ComputeRemaining((0, startTime), (loop1, DateTime.Now), intCount);

Console.SetCursorPosition(0,0);
Console.Write("ETA: ");
Console.Write(String.Format("{0} Days, {1} Hours, {2} Minutes, {3} Seconds", timeRemaining.Days.ToString().PadLeft(3,'0'), timeRemaining.Hours.ToString().PadLeft(2,'0'), timeRemaining.Minutes.ToString().PadLeft(2,'0'), timeRemaining.Seconds.ToString().PadLeft(2,'0')));
}
}
}
}
这似乎根本不起作用。它确实显示了 ETA,但由于循环的结构方式,它在显示任何内容之前有很长时间的延迟。
我如何更新它,以便 ETA 代码更准确地以更具预测性的间隔显示 ETA,就像每秒一样?

最佳答案

由于您刚刚构建了一个实际发生的简单模型的让步(您有一系列可变延迟和计数的嵌套过程,甚至在运行时才能确定),您目前要求的是一个随机数预测器。n之间会有& m o 之间的周期(在您的示例中为 1 和 10) & p持续时间(100 和 1000 毫秒),但仍然是随机的……好吧,随机量化为频段。您编写它的方式是随机掷骰子(骰子没有内存),尽管在实践中似乎更有可能一个周期的持续时间必须在某种程度上暗示下一个周期的持续时间(这就是您的方式写作 ComputeRemaining )并且在一个波段内,一个循环的计数必须有助于下一个循环的计数。
因此,尽管它看起来很简单 Prog2涵盖我们的示例.. 给定一个已知的循环计数,其中每个循环需要一个随机持续时间(实际上是 pick(n,m)^3*pick(o,p) .. 但这仍然只是一个随机数) - 预测结束。出于报告目的,您还需要重构它以考虑内部循环,但这实际上是相同的过程。 (^3是简化,实际上是一系列独立的pick相乘)
好的,所以我们不需要谈论时间/延迟(我的意思是......你显然想要那个,但它只是代表 future 的一些数字 - TimeSpanlong 从一段时间以来 x 滴答的计数...完成时间只是 Now + x*tick )。所以我们可以将其简化为 long预测器。
设置

interface IAvger
{
public double Avg { get; }
}
interface IAdder
{
void Add(long value);
}
class Mean
{
private int _count = 0;

public double Total { get; private set; } = 0;
public double? Avg => _count == 0 ? null : (double?)(Total / _count);

public void Add(double val)
{
Total += val;
_count++;
}
}
您可以忽略接口(interface)(我在切换潜在解决方案时使用了它们)。类(class) Mean应该很熟悉......它计算多个值的平均平均值,随着发现更多值而缩放/适应。
/// <summary>
/// Equivalent to your ComputeRemaining
/// </summary>
class RunningAvg : IAvger, IAdder
{
private Mean _mean = new Mean();
private readonly double _guess;
public RunningAvg(double guess)
{
_guess = guess;
}

public double Avg => _mean.Avg ?? _guess;

public void Add(long value) => _mean.Add(value);
}
这相当于您的 ComputeRemaining . guess的值有助于在没有其他信息时进行早期预测(与 TimeSpan.Max 等效)
/// <summary>
/// Drop the lowest and highest value
/// - Fairly typical in stats, however note this is only one biggest/smallest
/// (will work best when the standard devation is low, and outliers
/// are rare)
/// </summary>
class IgnoreExtremes : IAvger, IAdder
{
private long? _worst;
private long? _best;
private Mean _mean = new Mean();
private readonly int _wt;
private readonly double _guess;
public IgnoreExtremes(double guess, int weight = 4)
{
_wt = weight;
_guess = guess;
}

public long Best => _best ?? (long)Math.Round(_guess);
public long Worst => _worst ?? (long)Math.Round(_guess);

public double Avg
{
get
{
var avg = _mean.Avg;
if (!avg.HasValue) return _guess;
return (Best + _wt * avg.Value + Worst) / (2 + _wt);
}
}

public void Add(long value)
{
if (!_best.HasValue)
{
_best = value;
}
else if (value < _best)
{
_mean.Add(_best.Value);
_best = value;
}
else if (!_worst.HasValue)
{
_worst = value;
}
else if (value > _worst)
{
_mean.Add(_worst.Value);
_worst = value;
}
else
{
_mean.Add(value);
}
}
}
终于有数据了! IgnoreExtremes抑制最高和最低(单个)值。在科学抽样中,忽略这些是相当典型的,但是对于数字的真实随机分布(例如掷骰子或 random.Next),只会丢弃一个极端。这应该比 RunningAvg 预测出更好的数字.请注意,这是一种加权平均形式,您可以通过提供 weight 对其进行(略微)调整构建时的值(value)( wt=4 相当常见),或搭售 _wt_mean.count (需要更改一些代码)
class IgnoreStdDevOutlier : IAvger, IAdder
{
private const int AT_LEAST = 5;
private Mean _mean = new Mean();
private readonly List<long> _vals = new List<long>();
private readonly double _guess;
//private long _tot;
private readonly double _outlierStdDevMulti;
public IgnoreStdDevOutlier(double guess, double outlierStdDevMulti = 2)
{
_guess = guess;
_outlierStdDevMulti = outlierStdDevMulti;
}

private double StdDev()
{
var avg = Avg;
double tot = 0;
foreach (var item in _vals)
tot += (item - avg) * (item - avg);
return Math.Sqrt(tot / (_vals.Count - 1));
}

public void Add(long value)
{
_vals.Add(value);
if (_vals.Count > AT_LEAST)
{
var avg = Avg;
var sd = StdDev();
var min = avg - _outlierStdDevMulti * sd;
var max = avg + _outlierStdDevMulti * sd;
//Ignore outliers
if (value < min || value > max) return;
}
_mean.Add(value);
}

public double Avg => _mean.Avg ?? 0;
}
另一种统计方法是忽略大于 n*StandardDeviation 的值。从平均值,其中 n通常是 2 或 3(你会发现相互矛盾的意见)。看到的所有值都是标准偏差的一部分,但只有那些不是异常值的值才被视为平均值的一部分。这最终就像一个抑制因子,防止估计摆动太大。
好的,所以要运行测试,我们需要某种测量类:
class Performance
{
private readonly List<long> _set = new List<long>();
private long _actual;

public void Add(long item) => _set.Add(item);

public void SetFinal(long final) => _actual = final;

public void Report()
{
foreach (var item in _set)
{
Console.WriteLine("{0} {1} = {2}", item, _actual, (item / (double)_actual - 1) * 100);
}
}
}
真正的猜测无法知道最终的 ( _actual ) 值,但是这个类允许我们看到到目前为止猜测是如何进行的。
最后是程序类:
class Program
{
const int MIN_MSEC = 100;
const int MAX_MSEC = 1000;
const int MIN_LOOP = 10;
const int MAX_LOOP = 50;

static void C(Random random)
{
int nm = random.Next(MAX_LOOP, MAX_LOOP);
var guess = (double)((MAX_LOOP + MIN_LOOP) / 2 * (MAX_MSEC + MIN_MSEC) / 2);

var predict = new RunningAvg(guess);
//var predict = new IgnoreExtremes(guess);
//var predict = new IgnoreStdDevOutlier(guess,3);
var per = new Performance();
long tot = 0;
for (int i = 0; i <= nm; i++)
{
var op = random.Next(MIN_MSEC, MAX_MSEC);
predict.Add(op);
per.Add((long)Math.Round(predict.Avg * nm));
tot += op;
}
per.SetFinal(tot);
per.Report();
}

static void Main(string[] args)
{
var random = new Random();
C(random);
}
}
您可以忽略在名为 C 的方法中完成的工作。 (只是代码的一个副作用 - A 是您的 Prog1,而 B 是 Prog2)。内 C尝试更改 RunningAvg 中的哪一个, IgnoreExtremesIgnoreStdDevOutlier未注释。因为,同样,写的是骰子随机,你不能把单次运行作为一个好的基准。下一阶段是将其包装在重复运行中并取预测的标准偏差的平均值(或者可能只是后期预测 - 用户可能不介意早期估计是否相距甚远,只要结束它没有跳来跳去) - 但我没时间了。我找到 IgnoreStdDevOutlier ,平均而言,到最后会很快收敛到正确的答案,偏离了 0-1%。 IgnoreExtremes由于只忽略了一个极端(在每个方向上),所以它是 IgnoreStdDevOutlier 的轻量版本。 .如果您的数据不是随机的,并且只是偶尔出现极端情况 - 它会做得很好。 RunningAvg在某些时候实际上并没有表现得非常糟糕,其他时候它一直以两位数的百分比下降。随机数,如果它们很容易预测就好了。
使用注意事项
  • Timespan.Ticks是长。所有这些都是为了预测一个长期的,这可以认为是当时和现在的区别。直接切换使用 new Timespan(long ticks)构建跨度,以及 DateTime.Now.Subtract(startTime).Ticks从跨度中获得长。显然,所有这些类都可以为 TimeSpan 重写。而不是 long .. 不幸的是,没有一个简单的通用 where包含 long 的约束和 TimeSpan
  • 关于c# - 将 ETA 添加到嵌入式循环序列,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63457313/

    24 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com