【问题标题】:Text editor with multipe undo/redo in c在 c 中具有多个撤消/重做的文本编辑器
【发布时间】:2020-11-28 04:39:45
【问题描述】:

我正在开始一个学校项目:我们必须用 c 编写一个高效的文本编辑器。要命令这个,我可以使用:

  • (row1,row2)c
  • (row1,row2)d
  • (row1,row2)p
  • (数字)u
  • (数字)r

这些命令用于在 row1 和 row2 之间更改文本(c),删除 row1 和 row2 之间的文本(d,文本将替换为单个点),在 row1 和 row2 之间的 stdout 行上打印( p)、undo (number) times 或 redo (number times)(最后两个命令不影响打印,只影响 c 和 d)。 一开始我在想我可以使用什么数据结构。 我认为,对于行,使用带有行数的单个链接列表和第二个列表(用于文本本身)。 这是因为代码必须在时间和空间上高效 但在我的情况下,我没有找到实现撤消/重做的好方法:我在想两个创建两个堆栈,一个用于撤消,一个用于重做。我给它的每个命令都插入到撤消堆栈中,如果我撤消某些操作,我会删除撤消堆栈中的第一个操作并将其放入重做堆栈中

但是我不知道如何编写这些命令:我想保存一个补充命令,所以我可以运行这个命令并在前面的语句中返回。然后,当我撤消时,我在重做堆栈中创建补充命令,并删除每个新命令以释放空间

我希望它是可以理解的,我只是想听听您对这种可能的结构的看法 注意,理论上我只能在 c11 中使用 stdlib 和 stdio 进行编码,但如果需要,我可以复制和修改其他库的函数

---更新--- 我在想是否最好使用 R/B 树来保持行结构。这是因为它需要 O(log(n)) 来搜索第 X 行并对其进行编辑,而不是 O(n)

唯一的问题是,当我必须在一个命令中更改许多行(例如 1,521c)时,搜索每一行需要更长的时间

也许一种混合可能是一个不错的选择:我使用 RBT 结构来查找起始行地址,然后我使用列表结构来查找其他的。所以这棵树的每个节点都有 2 个 RBT 地址和 1 个列表地址

【问题讨论】:

    标签: optimization data-structures text-editor c11 undo-redo


    【解决方案1】:

    你的设计理念是正确的。

    需要的部分是如何表示undoredo 条目。

    • redo 条目可以是一个结构,指示要替换的文本范围和替换它的文本。这里的“span”给出了文本的偏移量,因为它可能是一个空的span(只是一个位置),这表明使用半开区间[start .. end)startlength。这可以表示任何单个文本更改操作。理论上,替换文本可以是长度为 0 的字符串;如果还没有,预计未来的任务可能会添加功能请求。
    • undo 条目可以是相同的结构,描述如您所记的补充文本替换操作。

    另一个设计决策是如何表示文档文本。最简单的是字符的顺序缓冲区,在这种情况下,每次插入都需要在确保内存缓冲区足够大后将所有后续文本向下移动。

    另一种选择是文本行列表,每行是一个单独的内存节点。这样,插入、删除和替换行就不必在内存中移动大部分文本,只需移动一些行节点指针。此外,对于行替换命令,重做/撤消条目可以仅列出要替换为其他行指针的行指针范围。

    【讨论】:

    • 可能我会使用第二个,因为每一行可以有1024个字符,所以我认为最好使用列表并从最后一个字符读取输入(所以我在头部添加列出其他字符,我认为比添加尾部更好)
    • 注意,如果我想改变一行,我必须重写所有的行。这就是为什么我想做一个列表来表示文档中的行(带有数字和指向文本本身的指针)并将所有字符保存在一行中。对于撤消/重做,由于另一个答复,我在想用两个单独的堆栈保存每个命令及其“解毒剂”;它在撤消/重做操作中会非常快,但它会占用两倍的空间。我最初的想法是只保存命令的“解毒剂”,并且为了重做,在用户给出撤消命令时生成命令
    【解决方案2】:

    假设您只创建一个堆栈(数组?)并将其称为“历史记录”。发出命令时,将它们的“解毒剂”(或指向它们的指针)添加到堆栈中,并将指针/计数器调整到最后一个命令。当您的用户退后(“撤消”)时,将每个命令替换为它的“解毒剂”(首先将其放在那里的代码可以重复使用),以便后续“重做”,并根据需要重新定位计数器.您必须为已删除的文本分配存储空间,并将其链接到堆栈位置(二维(指针?)数组,或者可能是结构?)。如果您的堆栈已满,请删除最旧的 - 它现在“超出范围”,并相应地移动所有内容......或者......只需分配更多内存......;-)

    只是一个想法......

    请记住,如果它工作正常,那就没有错。也许只是不是最有效或最有效的方式......

    不要忘记在“保存”时清除堆栈,最重要的是,在“终止”时释放所有分配的内存。

    迈克。

    【讨论】:

    • 我不能使用数组,因为我必须接受无限撤消。这就是为什么我想使用一个堆栈(或只是一个链表)来撤销和重做。所以我不能接受一个完整的堆栈。所以我只需要考虑如何为每个命令编写“解毒剂”,对吧?
    • 如果我为一个命令和命令本身保存两个堆栈“解毒剂”怎么办?撤消/重做请求会非常快,但我必须将所需空间增加一倍
    • 无限撤消 - 然后你需要一个无限大的存储用户的操作日志。假设您为适度的“日志”分配内存并根据需要重新分配()。请参阅 (stackoverflow.com/questions/3536153/c-dynamically-growing-array)。堆栈是一个数组,尽管按顺序(通常)访问后进先出(LIFO)..只是(在您的脑海中-计算机不在乎)“垂直”排列。记住,你正在编码(YCT),所以你可以让它以任何你想要的方式工作——甚至“颠倒”(最近在列表的“底部”)。 “解药”编码?正确的。 2堆? YCT-您的选择。
    • 刚刚看到你的更新。我一直理解撤消/重做应用于最后一个操作(查看任何商业软件)。如果用户刚刚输入(比如说)第 423 行,然后删除第 24 行,Undo() 将恢复第 24 行,undo() 再次删除第 423 行。Redo() 恢复第 423 行。无需搜索 - 你的“最后” action'(指针、索引、计数器,无论你怎么称呼它)都会随时更新——它总是“在那里”。
    • 顺便说一句,您可能希望在第一个操作(程序关闭?)之前“陷阱”(AKA 'catch')撤消,然后在最后一个操作之后重做(然后变成“重复”)取决于你(和你的导师)的幽默感......
    猜你喜欢
    • 2017-12-29
    • 2012-06-12
    • 1970-01-01
    • 1970-01-01
    • 2012-12-21
    • 1970-01-01
    • 1970-01-01
    • 2021-03-22
    相关资源
    最近更新 更多