【问题标题】:Find blocks of same values in a collection and manipulate surrounding values在集合中查找相同值的块并操作周围的值
【发布时间】:2014-03-20 11:30:46
【问题描述】:

我真的不知道如何总结标题中的问题,对不起。 :)

假设我有一个包含数千个对象的集合(即 ObservableCollection)。这些对象由一个升序时间戳和一个FALSE 布尔值组成(非常简化)。

像这样:

[0] 0.01, FALSE
[1] 0.02, FALSE
[2] 0.03, FALSE
[3] 0.04, FALSE
...

现在,让我们假设在这个集合中,有些块的标志设置为TRUE

像这样:

[2345] 23.46, FALSE
[2346] 23.47, FALSE
[2347] 23.48, FALSE
[2348] 23.49, TRUE
[2349] 23.50, TRUE
[2350] 23.51, TRUE
[2351] 23.52, TRUE
[2352] 23.53, TRUE
[2353] 23.54, FALSE
[2354] 23.55, FALSE
...

我需要找到块并将块前后1.5秒内的所有标志设置为TRUE。 如何在保持合理性能的同时实现这一目标?

【问题讨论】:

  • 你不能在有人将属性设置为 TRUE 的那一刻,立即将周围对象的属性也更改为 TRUE 吗?
  • 不,很遗憾这是不可能的。
  • 为什么不在周围的对象中使用对集合的布尔值的引用,而不是使用私有布尔属性?
  • 考虑到无论数组中的元素数量如何(保持在可接受的范围内),您都必须保持恒定的时间,最好的解决方案是 imo、字典或任何其他键/值存储,用于常量O(1) 选择性能。但你需要以某种方式构建它。
  • 我无法更改对象的架构(请注意,我在问题中使用了非常简化的表示),因为这意味着我的软件将发生巨大变化。

标签: c# linq collections


【解决方案1】:

Matthias G 解决方案是正确的,尽管速度很慢 - 似乎具有 n 平方复杂度。

首先算法扫描输入 values 以通过 IsActive 过滤它们,检索时间戳并放入新列表 - 至少是 O(n)。然后它扫描构建的列表,在最坏的情况下,这可能是整个输入 - O(n),并且对于检索到的每个时间戳,它扫描输入 values 以修改它们中的适当值 - O(n^2)。 然后它会构建额外的列表,仅用于扫描一次并销毁。

我会提出一个有点类似于归并排序的解决方案。首先扫描输入值,并为每个活动项将适当的时间间隔推入队列。您可以延迟推送以查看下一个间隔是否与当前间隔重叠 - 然后延长间隔而不是推送。当输入列表完成后,最后推送最后一个延迟间隔。这样,您的队列将包含您想要修改的(几乎)最少数量的时间间隔。

然后再次扫描values 数据并将时间戳与队列中的第一个间隔进行比较。如果项目的时间戳落入当前间隔,则将该项目标记为活动。如果它超过了间隔,则从队列中删除间隔并将时间戳与下一个时间戳进行比较 - 依此类推,直到项目在间隔内或之前。您的输入数据按时间顺序排列,因此间隔也将按相同顺序排列。这允许在一次并行遍历两个列表中完成任务。

【讨论】:

  • 另一种方法很简单,即扫描整个向量两次。首先向上扫描,捕获每个 Active 项目的时间戳,并将最多比捕获的时间戳晚margin 的每个项目标记为 Active。然后向下扫描,捕获每个活动项目的时间戳,并将最多比捕获的时间戳早margin 的每个项目标记为活动。完毕。并且没有额外的列表或任何其他数据结构。
【解决方案2】:

假设你有这样的数据结构:

编辑:将时间戳更改为double

public class Value
{
    public double TimeStamp { get; set; }

    public bool IsActive { get; set; }
}

还有一个名为values 的对象列表。然后,您可以搜索活动数据集,并为每个数据集将其周围范围内的值标记为活动:

double range = 1.5;
var activeTimeStamps = values.Where(value => value.IsActive)
                             .Select(value => value.TimeStamp)
                             .ToList();
foreach (var timeStamp in activeTimeStamps)
{
    var valuesToMakeActive =
        values.Where
            (
                value =>
                    value.TimeStamp >= timeStamp - range &&
                    value.TimeStamp <= timeStamp + range
            );
    foreach (var value in valuesToMakeActive)
    {
        value.IsActive = true;
    }
}

无论如何,我想会有一个性能更好的解决方案..

【讨论】:

  • 这可能有效。不过,我的时间戳只是双精度值。我会尽快尝试。
  • 我将时间戳更改为双倍。方法是一样的。
  • 是的,这行得通。 :) 不是最快的,但它是一个开始。非常感谢!
猜你喜欢
  • 2021-12-31
  • 2021-01-14
  • 1970-01-01
  • 2020-06-20
  • 1970-01-01
  • 1970-01-01
  • 2017-11-02
  • 1970-01-01
  • 2016-01-27
相关资源
最近更新 更多