问题描述
我编写了一个程序来计算某些数据的移动平均值。问题是我不能自动化,我的意思是如果我想通过 10 步或更多的步骤来完成这个过程,用交换条款写 10 行是不明智的。
按 4 步计算移动平均值的部分是:
set aux [lindex $line 4]
set T [lindex $line 1]
set aux1 [lrange $valores 1 1]
set valores [lreplace $valores 0 0 $aux1]
set aux1 [lrange $valores 2 2]
set valores [lreplace $valores 1 1 $aux1]
set aux1 [lrange $valores 3 3]
set valores [lreplace $valores 2 2 $aux1]
set aux1 [lrange $valores 4 4]
set valores [lreplace $valores 3 3 $aux1]
set valores [lreplace $valores 4 4 $aux]
set promP [avg $valores]
我知道我必须使用 for 循环,但我所做的尝试没有奏效。
解决方法
假设您对数据保持一个窗口,这并不太难。诀窍是制定一个程序来完成关键工作。
set WINDOW_SIZE 10
set storedData {}
proc updateMovingAverage {value} {
global storedData WINDOW_SIZE
set storedData [lreplace [list {*}$storedData $value] 0 end-$WINDOW_SIZE]
return [expr {[tcl::mathop::+ {*}$storedData] / double([llength $storedData])}]
}
或者你可以创建一个类:
oo::class create MovingAverage {
variable window size
constructor {{windowSize 10}} {
set window {}
set size $windowSize
}
method item {value} {
set window [lreplace [list {*}$window $value] 0 end-$size]
return
}
method average {} {
return [expr {[tcl::mathop::+ {*}$window] / double([llength $window])}]
}
}
该类将添加项目和计算平均值分开。后者现在是 Tcl 中的标准模式。添加项目的技巧是将项目附加到列表中,如果列表的前面大于所需的窗口,则修剪掉它; list {*}$thing $value
执行 append-an-item,而 lreplace THING 0 end-$wantedLength
执行前缀修剪(用空的项目序列替换它们)。
这是一个更高效的版本。
oo::class create MovingAverage {
variable window size index
constructor {{windowSize 10}} {
set window {}
set size $windowSize
set index 0
}
method item {value} {
lset window $index $value
set index [expr {($index + 1) % $size}]
return
}
method average {} {
return [expr {[tcl::mathop::+ {*}$window] / double([llength $window])}]
}
}
这使用的事实是,从 8.6 开始(完全巧合的是,当集成类时),lset
命令可以将项目附加到列表中。
一个协程版本,比如评论中提到的Donal:
[ServiceContract]
public interface IDiscoveryService
{
ValueTask<SaveDiscoveryResultResponse> SaveDiscoveryResultAsync(IAsyncEnumerable<SaveDiscoveryResultRequest> request);
}
它和类版本比标准 proc 版本(如 Donal 的第一个示例)有优势,因为它不依赖于任何全局变量,因此您可以同时计算具有不同窗口大小的多个不同数据集不同协程/对象中的时间。