假设我正在使用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个任务?
更新: 通过一些答案,我不认为我找到了一个我喜欢的答案。我可以:
答案 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杂志上。我相信这是一种稀疏的阵列。但是我认为上下左右都有链接,所以任何给定的单元都有一个指向它上面的单元格的指针(不管有多少单元格),在它下面,在它的左边,以及它的右边。