在期望运行重复值时压缩List,同时保持索引查找

时间:2013-03-25 19:28:40

标签: c# algorithm list optimization data-structures

简短版本:

我有一个List对象,其中包含重复值运行中存在的多个重复值(double),其中散布着更改值的运行。我希望减少此List对象占用的内存空间,而不会损害索引和值之间的关联。我还希望尽可能保持接近O(1)算法的查找时间,使用索引作为查找。例如,如果您有一个包含{0,0.1,0.1,0.1,0.2}元素的列表,那么如果给出索引1,2或3,则新对象/实体将始终返回0.1。我希望我需要创建我自己的对象(可能实现IList),或者使用现有的对象。我知道如何实现这个算法会使算法O(log(m)),其中,m是相同值的运行次数(在我的例子中,只有1次运行)。但是,如果可能的话,我宁愿不自己动手。

C#是否存在这样的对象,还是我需要自己滚动?

动机/长版:

我有一个桌面应用程序正在做一些重大的科学计算。计算生成大量数据,并根据时间组织数据。也就是说,对于时间50,存在变量x,y和z的值。对于时间51,存在变量x,y和z的另一个值。我有一个List,其中包含运行计算的所有时间。每个变量都有一个List,其索引与时间List的索引相同。也就是说,如果查看时间数组的索引234,则可能会得到时间46(秒)。然后,在该变量的List的索引234处找到时间46(秒)处的每个变量的计算。

大约有100,000个这样的变量(因此有100,000个列表),但只有一个时间列表。我还希望增加更多变量。这显然是一个内存问题。 (目前至少约200 MB的原始空间:-))。这也应该解释为什么我想使用索引作为在某个时间找到某个变量的值的方法。

变量在前x个槽中只有0是很典型的。或者在索引y之后,变量保持不变直到结束。我想说,值是常量的周期数的最坏情况可能在单个列表中大约为30,但更典型地在2到5之间。每个数组中的总值的数量通常可能大约为250.

编辑:

请注意,我希望添加的变量比100,000更多,所以这个问题比200 MB更大。为了解释更多这方面的动机,我的应用程序目前运行大约1 GB以上,我看到200 MB是减少内存使用率的最低成果。

EDIT2:

我意识到对我的解释有一个非常重要的编辑 - 我已在上面编辑它并在此处解释它。列表可能已在其中运行,但它们也具有值从索引到索引更改的部分。所以我可能有一个更好的列表示例如下:

0 0 0 0 0 0 ....(50重复0)... 0.1 0.2 0.4 0.5 0.6 ...(50多个变化值)... 200.45 200.45 200.45 200.55 ...(50多个重复值)......等等。

2 个答案:

答案 0 :(得分:5)

我假设您的O(log(m))想法基本上是创建一个二叉搜索树,使用索引范围来排序结果。

我绝对会采用这种解决方案。如果每个列表最多只有30次运行,那么你真的不需要担心它与m一起扩展的方式,因为m永远不会特别大......你可能会发现任何实际情况下的任何固定时间解决方案实际上都比搜索树方法更糟糕。

事实上,我可能最初寻找一个简单的运行列表(每次运行是一个索引范围和一个值)和一个O(m)查找...如果你的< em>典型的大小是2-5,那么它不会特别糟糕,并且实现起来会更简单。一旦你有一个简单的方法工作,然后你可以优化。

事实上,我开始时根本就没有做这个“运行”版本。除非您需要在特别有限的手机上运行,​​否则200MB左右的数据集确实不算太大。应用程序实际运行哪些机器?您是否有理由相信他们无法负担您的应用程序的半个千兆字节?

同样值得注意的是,二元搜索树或运行列表的开销可能意味着您无法保存尽可能多的预期。

基本上,我按此顺序执行:

  • 阵列
  • 运行列表
  • 二进制搜索树

对每一步的表现(时间和空间)进行基准测试,并确保你有足够好的具体目标。

编辑:使用已编辑的版本,您可能希望使用某种界面IPortion

int MinIndexInclusive { get; }
int MaxIndexExclusive { get; }
double FindValue(int index);

有两个实现:ArrayPortionTreePortionTreePortion的每个节点都有左侧和右侧,每个节点都是另一个IPortion - 可以让ArrayPortion嵌入TreePortion,例如。

或者稍微简单一点,你可以保持不变,并且List<IPortion> IPortion每个ArrayPortion都是RunPortionRunPortion {{1}}只知道单个值及其索引边界。然后,您可以在列表上进行二进制搜索以找到正确的部分,并询问它在索引处的值。

答案 1 :(得分:1)

在我看来,您可以使用List<T>和二分搜索来完成此操作。您不需要存储运行列表。您真正需要存储的是时间变化时的索引和值。

所以,有一个简单的结构:

struct ValueChange
{
    public int TimeIndex;  // or whatever type you use for the index
    public double Value;
    // Add constructor here
}

(是的,我知道结构中的可变值很糟糕。为了简洁起见,我用这种方式编码。在实际代码中,那些是带有私有后备字段的只读属性。)

然后你有List<ValueChange>。每当值更改时,您将其中一个附加到列表中。你可以很容易地判断这个值是否变化了:

if (currentValue != theList[theList.Count-1].Value)
{
    theList.Add(new ValueChange(timeIndex, currentValue));
}

当您想要查找特定时间索引的值时,您可以对时间索引进行二进制搜索。如果您要查找的索引不存在,List.BinarySearch的返回值将告诉您包含您要查找的值的项目的索引。

任何类型的游程压缩的缺点当然是短时间运行将其转变为数据扩展器而不是压缩器。在这种特殊情况下,您需要一个总体运行长度平均值为2才能实现收支平衡。也就是说,如果您想表示N个时间段的值,则不能有超过N / 2个值的更改,因为ValueChange结构的大小是double的两倍。