【问题标题】:Fast algorithm to find elements in time range of list在列表的时间范围内查找元素的快速算法
【发布时间】:2013-11-26 14:56:41
【问题描述】:

问题: 我有一个包含时间和值的数据列表(时间 = long milliSec 和 double 值)。我现在需要计算不同时间范围内的多个平均值。
我每秒最多获得 50 个值,但有时只有几个值,需要保留最后 10 秒,所以 500 个值。

我想要的是:计算时间 >= 开始和时间

我可以确保没有时间是双倍的,所以它可以用作钥匙。

目前我使用一个数组来存储值,并有一个位置标记,一旦达到 500,它就会重置为 0,因此旧条目会被回收。我可以很容易地改变它。

我不确定最快的方法是什么,例如手动搜索数组或使用列表、hashMap、Collection(带比较器?)或其他。我找不到一个(java)类似列表的函数,其中我有一个内置搜索“key >= x”或“value >=x”。

性能比漂亮或简单的编码更重要。

如果能指出正确的方向就好了。

每次有新值进入时,我都会计算最后 10 个值的平均值,即每秒 30-50 次计算,这是最重要的数据。我需要将测量中的小误差与实际变化区分开来。 我另外计算每 1/10 秒的平均值(这可能会被丢弃),最后是一秒的平均值和最后 10 秒的平均值。那是每秒额外的 12 次平均计算。减少计算次数并不是一个真正的选择。

由于这有点抽象,这里是数据的示例(其中 avg 是根据最后 10 个值计算的,但这不是程序逻辑)。

value           Avg timeReading timeReadingISO
1024,6668701172 -       1385408750828   2013-11-25 19:45:50
1024,6668701172 -       1385408751350   2013-11-25 19:45:51
1024,6668701172 -       1385408751859   2013-11-25 19:45:51
1024,6683349609 -       1385408752373   2013-11-25 19:45:52
1024,6683349609 -       1385408752878   2013-11-25 19:45:52
1024,6689453125 -       1385408753385   2013-11-25 19:45:53
1024,6689453125 -       1385408753895   2013-11-25 19:45:53
1024,6721191406 -       1385408754406   2013-11-25 19:45:54
1024,6721191406 -       1385408754912   2013-11-25 19:45:54
1024,6774902344 -       1385408755432   2013-11-25 19:45:55
1024,6774902344 1024,67 1385408755994   2013-11-25 19:45:55
1024,6774902344 1024,67 1385408756502   2013-11-25 19:45:56
1024,6837158203 1024,67 1385408757012   2013-11-25 19:45:57
1024,6837158203 1024,67 1385408757520   2013-11-25 19:45:57
1024,689453125  1024,68 1385408758028   2013-11-25 19:45:58
1024,689453125  1024,68 1385408758536   2013-11-25 19:45:58
1024,6938476563 1024,68 1385408759055   2013-11-25 19:45:59
1024,6938476563 1024,68 1385408759560   2013-11-25 19:45:59
1024,6990966797 1024,68 1385408760075   2013-11-25 19:46:00
1024,6990966797 1024,69 1385408760579   2013-11-25 19:46:00
1024,7038574219 1024,69 1385408761086   2013-11-25 19:46:01
1024,7038574219 1024,69 1385408761596   2013-11-25 19:46:01
1024,7111816406 1024,69 1385408762103   2013-11-25 19:46:02
1024,7111816406 1024,70 1385408762606   2013-11-25 19:46:02
1024,7111816406 1024,70 1385408763112   2013-11-25 19:46:03
1024,7111816406 1024,70 1385408763622   2013-11-25 19:46:03
1024,7172851563 1024,70 1385408764128   2013-11-25 19:46:04
1024,7172851563 1024,71 1385408764637   2013-11-25 19:46:04
1024,7208251953 1024,71 1385408765149   2013-11-25 19:46:05

1026,5457763672 -       1385474621756   2013-11-26 14:03:41
1026,6057128906 -       1385474621790   2013-11-26 14:03:41
1026,6257324219 -       1385474621823   2013-11-26 14:03:41
1026,6057128906 -       1385474621858   2013-11-26 14:03:41
1026,6257324219 -       1385474621890   2013-11-26 14:03:41
1026,6257324219 -       1385474621921   2013-11-26 14:03:41
1026,6057128906 -       1385474621956   2013-11-26 14:03:41
1026,5457763672 -       1385474621988   2013-11-26 14:03:41
1026,6557617188 -       1385474622022   2013-11-26 14:03:42
1026,6657714844 -       1385474622057   2013-11-26 14:03:42
1026,6257324219 1026,61 1385474622090   2013-11-26 14:03:42
1026,6057128906 1026,62 1385474622123   2013-11-26 14:03:42
1026,6657714844 1026,62 1385474622159   2013-11-26 14:03:42
1026,6557617188 1026,62 1385474622193   2013-11-26 14:03:42
1026,6557617188 1026,63 1385474622227   2013-11-26 14:03:42
1026,6257324219 1026,63 1385474622260   2013-11-26 14:03:42
1026,6257324219 1026,63 1385474622298   2013-11-26 14:03:42
1026,6557617188 1026,63 1385474622330   2013-11-26 14:03:42
1026,6257324219 1026,64 1385474622365   2013-11-26 14:03:42
1026,6257324219 1026,64 1385474622401   2013-11-26 14:03:42
1026,6257324219 1026,64 1385474622431   2013-11-26 14:03:42
1026,5758056641 1026,64 1385474622466   2013-11-26 14:03:42
1026,6057128906 1026,63 1385474622501   2013-11-26 14:03:42
1026,5457763672 1026,63 1385474622533   2013-11-26 14:03:42
1026,5457763672 1026,62 1385474622565   2013-11-26 14:03:42
1026,6057128906 1026,61 1385474622599   2013-11-26 14:03:42
1026,6057128906 1026,60 1385474622631   2013-11-26 14:03:42
1026,5758056641 1026,60 1385474622665   2013-11-26 14:03:42
1026,5457763672 1026,59 1385474622702   2013-11-26 14:03:42
1026,6057128906 1026,59 1385474622734   2013-11-26 14:03:42
1026,6557617188 1026,58 1385474622766   2013-11-26 14:03:42
1026,5758056641 1026,59 1385474622800   2013-11-26 14:03:42
1026,6057128906 1026,59 1385474622836   2013-11-26 14:03:42
1026,6057128906 1026,59 1385474622868   2013-11-26 14:03:42
1026,5158691406 1026,59 1385474622901   2013-11-26 14:03:42
1026,5457763672 1026,59 1385474622935   2013-11-26 14:03:42
1026,6856689453 1026,58 1385474622966   2013-11-26 14:03:42

【问题讨论】:

  • 平均费率应该有多精确?也许您应该减少刷新频率?
  • 查找二叉索引树。不过,您可能需要动态版本。
  • 对于最后 10 个值,10 的 FIFO 可能有用。计算输入值与输出值的差除以 10 并将其添加到平均值中。第一个值 10 会有副作用。我不知道这是否有帮助
  • 如果您每秒“获得”50 个值并在 10 秒内获得 500 个值,您的性能问题是什么?无论如何它很慢。即使您必须计算 500 个值的平均值 - 您强调性能有多慢?顺便说一句-您如何“获取”该数据。 BTW2 - 更好地描述你的程序应该做什么以及为什么看起来你正在尝试做某事奇怪并且可能以更奇怪的方式。
  • 这是一个实时系统,还是当您说“每秒获取 50 个值”时,您正在读取的数据是否包含该频率的值?

标签: java arrays list


【解决方案1】:

首先,在计算平均值时,你应该创建一个结构的副本(或者使用一个线程安全的并且在添加或删除期间遍历它不会造成痛苦),除非你在一个线程中完成所有事情。

我猜你的集合中的元素是按顺序接收更新的(如果不寻找等价的排序列表)。

我的方法是选择平均测量的最小间隔。假设有 10 个值。然后,您可以创建 50 个集合(大小为 10),其中每个集合都属于类,这为您提供了计算平均值的方法。然后您可以选择要计算的平均值。只需计算集合平均值的平均值。 更重要的是 - 一旦计算出给定集合的平均值就不会改变,因此您可以缓存它

请注意,您不必将值从一个集合转移到另一个集合,因为您的最小间隔已被处理。如果新的 10 个元素进入缓冲区,您只需重新分配引用即可。

/* initializing */
MySlicedCollection buffer = new MySlicedCollection();
MySlicedCollection[] mscArray = new MySlicedCollection[50];

/* when every 10 values came in */
for(int i = mscArray.length-1; i > 0 ; --i) {
    mscArray[i] = mscArray[i-1];
}
mscArray[0] = buffer;
buffer = new MySlicedCollection();

/* avg of all collection */
for(MySlicedCollection msc : mscArray) {
    sum += msc.getAverage();
}
sum /= 50;

您还应该考虑使用以前的结果计算平均值。如果您必须计算 1 秒和 2 秒的平均值,那么您只需将剩余的平均值添加到已计算的 1 秒平均值,然后除以 2。

/* avg for one second */
for(int i = 0; i < 5; ++i) {
    sumOneSec += mscArray[i].getAverage();
}
sumOneSec /= 5;

/* avg for two seconds */
for(int i = 5; i < 10; ++i) {
    sumTwoSec += mscArray[i].getAverage();
}
sumTwoSec = ((sumTwoSec/5) + sumOneSec) / 2;

但请记住有人说过:“先衡量后行动”——也许你的表现就足够了?


更新 通过使用Circular buffer 并做一些简单的技巧,您可以节省至少一次迭代。假设缓冲区已满(并且其容量为 50),则有已知平均值和另一个值 - 您可以通过计算简单地重新计算它
avg = (avg * 50 - oldestValue + newValue)/50;

不幸的是,由于浮点变量的有限表示,这会给您的计算带来一些错误,但是由于您使用的是双精度值,我认为您不需要这种精度。可以为其他平均值提供类似的解决方案,但这需要更多思考:)

【讨论】:

  • 感谢您的方法。我会采纳你的想法,即为最后一个值创建一个小数组或集合。但是数据速率不是恒定的,我可能每秒只有很少的值,这就是为什么我需要在计算平均值之前检查时间。
  • @GunnarBernstein 您的数据是否按顺序到达,以便您可以在不排序的情况下对数组进行排序?
  • @Maciey:是的。但我不确定如何在不无限增长或移动所有值的情况下保持数组排序。
  • @GunnarBernstein 转移是不必要的。您始终可以实现自己的CircularBuffer 或使用existing one。如果数据以正确的顺序依次到达,那么您可以确定在将它们插入到此类缓冲区后,它们会被排序。然后,您可以通过递增计算平均值(如上所述)以及在值超出指定范围时停止迭代来提高性能。
  • 有趣。不知道有没有现成的。使用“指针”实际上是我所做的(“位置标记一旦达到 500 就会重置为 0,因此旧条目被回收”)
【解决方案2】:

在 Maciej 的答案中缓存平均值组是一种有效的方法。您当前列表的一个简单方法是 Java 的 SortedSet,这是一个接口,因此您最终将使用 TreeSet

创建一个Comparable 对象来存储您的时间和价值,或者为 SortedSet 创建一个Comparator。确保根据时间(而不是值)对您进行排序。

public class Holder implements Comparable
{
  private double time, value;
  public Holder (double t, double v)
  {
    this.time = t;
    this.value = v;
  }

  public double getValue()
  {  return this.value; }

  public double getTime()
  {  return this.time; }

  //You may want a better comparator.
  public int compareTo( Holder h )
  {
    return this.getTime < h.getTime() ? -1 : 1;
  }
}

只需像往常一样为集合添加值,它们将根据时间自动排序。如果您想要最近 10 秒的平均值,请找到当前时间并致电 sortedSet.tailSet( new CustomObject( currentTime - 10000 ) )。现在遍历返回的集合来计算你的平均值。

SortedSet<Holder> slice = allHolders.tailset( new Holder( currentTime - 10000 ) );
double sum = 0.0;
for( Holder h : slice )
{
  sum += h.getValue();
} 
double result = sum / slice.size();

如果您觉得平均通话有延迟,您可以使用.subSet() 查找时间组。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2010-11-14
    • 1970-01-01
    • 2013-02-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-02-17
    • 1970-01-01
    相关资源
    最近更新 更多