用于Excel克隆的正确数据结构

时间:2009-03-17 10:56:09

标签: c# data-structures .net-2.0

假设我正在使用C#进行Excel克隆。 我的网格表示如下:

private struct CellValue
{
    private int column;
    private int row;
    private string text;
}
private List<CellValue> cellValues = new List<CellValue>();

每次用户添加文本时,我只需将其打包为CellValue并将其添加到cellValues中。给定一个CellValue类型,我可以在O(1)时间内确定它的行和列,这很好。但是,给定一个列和一行,我需要循环遍历整个cellValues以查找该列和行中的文本,这非常慢。另外,给定一个文本,我也需要遍历整个事情。是否有任何数据结构我可以在O(1)时间内完成所有3个任务?

更新: 通过一些答案,我不认为我找到了一个我喜欢的答案。我可以:

  1. 不保留2个以上的CellValue副本,以避免同步它们。在C世界中,我会很好地使用指针。
  2. 可以动态添加行和列(与Excel不同)。

9 个答案:

答案 0 :(得分:4)

我会选择稀疏数组(链表的链表),以最小的存储空间提供最大的灵活性。

在此示例中,您有一个行的链接列表,每个元素都指向该行中单元格的链接列表(您可以根据需要反转单元格和行)。

 |
 V
+-+    +---+             +---+
|1| -> |1.1| ----------> |1.3| -:
+-+    +---+             +---+
 |
 V
+-+             +---+
|7| ----------> |7.2| -:
+-+             +---+
 |
 =

每个行元素都包含行号,每个单元格元素都有一个指向其行元素的指针,因此从单元格中获取行号为O(1)。

类似地,每个单元格元素都有其列号,也可以使用O(1)。

没有简单的方法可以让O(1)立即找到给定行/列的单元格,但是稀疏数组的速度和它要获得的速度一样快,除非你为每个可能的单元格预先分配信息,这样就可以了对数组进行索引查找 - 这在存储方面会非常浪费。

你可以做的一件事是使一个维度非稀疏,例如使列成为主数组(而不是链表)并将它们限制为1,000 - 这将使列查找编入索引(快速),然后搜索在稀疏的行上。

我不认为你永远得到O(1)进行文本查找只是因为文本可以在多个单元格中复制(与行/列不同)。我仍然相信稀疏数组将是搜索文本的最快方式,除非你维护另一个数组中所有文本值的排序索引(同样,这可以使它更快但是以大量内存为代价)。 p>

答案 1 :(得分:1)

我认为你应该使用其中一个索引集合使其工作得相当快,最完美的是KeyedCollection

您需要通过扩展此类来创建自己的集合。这样你的对象仍然会包含行和列(所以你不会丢失任何东西),但你可以搜索它们。可能你必须创建一个封装(行,列)的类并使其成为键(因此使其成为不可变的并覆盖equals并获取哈希代码)

答案 2 :(得分:1)

我要创建

 Collection<Collection<CellValue>> rowCellValues = new Collection<Collection<CellValue>>();

Collection<Collection<CellValue>> columnCellValues = new Collection<Collection<CellValue>>();

外部集合每行或每列有一个条目,由行或列号索引,内部集合包含该行或列中的所有单元格。应该将这些集合填充为创建新CellValue对象的过程的一部分。

rowCellValues[newCellValue.Row].Add(newCellValue);
columnCellValues[newCellValue.Column].Add(newCellValue);

答案 3 :(得分:1)

这种过早优化的气味。

也就是说,excel的一些特性对于选择一个好的结构非常重要。

首先,excel以适度非线性的方式使用细胞。解析公式的过程涉及以有效随机顺序遍历电子表格。该结构将需要一种易于查找随机密钥值的机制,由于循环引用而将其标记为脏,已解决或无法解析。还需要一些方法来了解何时没有剩余未解决的单元格,以便它可以停止工作。涉及链表的任何解决方案可能都不是最优的,因为它们需要线性扫描才能获得这些单元格。

另一个问题是excel一次显示一系列单元格。这看起来似乎微不足道,并且在很大程度上它是,但如果应用程序可以提取一次性绘制一系列单元格所需的所有数据,那肯定是理想的。其中一部分可能是跟踪行和列的显示高度和宽度,以便显示系统可以在该范围内迭代,直到收集到所需的单元格宽度和高度。以这种方式迭代的需要可能妨碍使用散列策略来稀疏存储单元。

最重要的是,电子表格的代表性模型存在一些弱点,可以通过略微不同的方法更有效地解决这些问题。

例如,列聚合有点笨重。列总数很容易在excel中实现,但它有一种神奇的行为,大部分时间都可以工作,但不是所有时间都可以。例如,如果您在聚合区域中添加一行,则对该聚合的进一步计算可能会继续有效,具体取决于您添加它的方式。如果你复制并插入一行(并替换值)一切正常,但如果你将单元格剪下并粘贴一行,那么事情就不会那么好了。

答案 4 :(得分:0)

鉴于数据是二维的,我会有一个2D数组来保存它。

答案 5 :(得分:0)

好吧,您可以将它们存储在三个词典中:两个Dictionary<int,CellValue>对象用于行和列,一个Dictionary<string,CellValue>用于文本。你必须谨慎地保持所有三个同步。

我不确定我是不是只会选择一个大的二维数组...

答案 6 :(得分:0)

如果它是一个精确的克隆,那么是一个由阵列支持的CellValue [256]数组列表。 Excel有256列,但行数可以增长。

答案 7 :(得分:0)

如果可以“动态”添加行和列,则不应将行/列存储为单元格的 numeric 属性,而应将其作为对行或列对象的引用

示例:

private struct CellValue
{
  private List<CellValue> _column;
  private List<CellValue> _row;
  private string text;

  public List<CellValue> column {
     get { return _column; }
     set {
         if(_column!=null) { _column.Remove(this); }
         _column = value;
         _column.Add(this);
        }
     }

  public List<CellValue> row {
     get { return _row; }
     set {
         if(_row!=null) { _row.Remove(this); }
         _row = value;
         _row.Add(this);
        }
     }
}

private List<List<CellValue>> MyRows    = new List<List<CellValue>>;
private List<List<CellValue>> MyColumns = new List<List<CellValue>>;

每个Row和Column对象都实现为CellValue对象的List。这些无序 - 特定行中单元格的顺序与列索引不对应,反之亦然。

每张工作表都有一个行列表和一列列,按工作表的顺序排列(如上图所示为MyRows和MyColumns)。

这将允许您重新排列和插入新的行和列,而无需循环和更新任何单元格。

删除行应循环遍历行上的单元格,并在删除行本身之前将其从各自的列中删除。对于列而言反之亦然。

要查找特定的行和列,找到相应的行和列对象,然后找到它们共同包含的CellValue。

示例:

public CellValue GetCell(int rowIndex, int colIndex) {
  List<CellValue> row = MyRows[rowIndex];
  List<CellValue> col = MyColumns[colIndex];
  return row.Intersect(col)[0];
  }

(我对.NET 3.5中的这些扩展方法有点模糊,但这应该在球场上。)

答案 8 :(得分:0)

如果我没记错的话,有一篇关于Visicalc如何做到这一点的文章,可能是在80年代早期的Byte杂志上。我相信这是一种稀疏的阵列。但是我认为上下左右都有链接,所以任何给定的单元都有一个指向它上面的单元格的指针(不管有多少单元格),在它下面,在它的左边,以及它的右边。