一种失败的Transformation Function

2020.06.15

毕设答辩完已经歇了有几天,本文总结一下之前设计 string-wise transformation function 的失败经历。这种设计前前后后花了我一周的时间,代码集中于 eyja-ot-core 的 TextOperation。无奈最后实在过于复杂和低效,全部放弃而转向现在的 SimpleTextOperation 了。


在 TextOperation 的设计前,我一度是参照 ot.js 来理解操作转换的控制算法层。ot.js 及其相关文章设计了客户端的多种状态及其转换来控制并发问题。服务端对客户端的操作上传有一个答复信号,从而协调服务端和客户端的上下文问题。客户端的未发送操作可能出现堆积。此外,ot.js 还有一个 buffer 的设计。buffer 的存在使得操作的传送更为高效。堆积和 buffer 诱导产生了操作的组合问题。即出现了将多个操作组合为单个的compose 方法。


由于操作能够进行组合,那么将操作简单划分为插入操作和删除操作类型将变得不太合适。在 ot.js 中,通过数字表示操作类型和操作内容,利用数组和下标以较为巧妙的机制实现了操作的 compose 和 apply. 基于上述内容,在设计 TextOperation 时,我提出了如下设计目标:

  • 使用 int 等数字标识操作类型和操作内容既不直观也难以扩展。设计插入和删除操作的数据结构时,应采用更直观清楚的表示方式;
  • 编辑操作内部采用 Guava 的 Range 和 RangeSet;
  • 基于新的数据结构,设计实现 compose、apply、transform.

新的数据结构如下所述。插入操作为 TextOperation 的内部类,包裹了单个插入元素的位置和内容。每个 TextOperation 内有一个不一定连续的删除区间 deleteRangeSet。

TextOperation

通信和 buffer 中均可使用单个 TextOperation. 由于这一设计不再基于单个编辑元素,必须规定单个 TextOperation 内插入和删除元素之间的上下文关系。为了语义更加清晰以及 apply 方法的快速实现,我定义了单个操作 TextOperation 内若既有删除又有插入,将先依次执行插入后删除。因此插入列表内上下文关系为
$$
C(op[i]) = C(op[i - 1]) \cup op[i], i \in [0, n)
$$
删除操作的上下文
$$
C(O_d) = C(op[n - 1])\cup op[n - 1]
$$
其中 n 为插入操作列表的长度。此外,为方便实现,额外规定同一个 TextOperation 内插入操作和删除区间的范围不会互相重叠,插入列表内的插入元素之间也不会互相重叠。

如此设计,apply 操作自然较为简单,执行每个插入操作后利用 RangeSet 的差集排除掉删除的区间即可。但 compose 和 transform 将出现较大问题。


首先,合并来自另一个操作的插入和删除时,必须基于本操作的删除区间,针对外部 Op 的每一个插入元素执行 ET_ID,随后还要针对本操作的插入列表最后一个 insert 元素进行合并以确保插入操作之间是相互独立的。合并删除区间时,则要移入每一个删除子区间。插入和删除移入后,由于新的删除区间可能删除了原操作的部分插入内容,还要对插入元素和删除区间作修正以确保不会互相影响。在这个过程中,我又写了 getOriginInsertList 方法获取基于同一个原始上下文全部插入元素的方法,又提高了时空复杂度。


转换操作同样较为复杂。转换前,我同样把两个操作的插入元素和删除区间转换为原始的同一上下文。转换插入操作时,需针对另一个操作的所有插入元素和删除子区间移动插入位置坐标。删除区间的移动更为复杂,需考虑两个子区间的重叠,若有重叠必须截断为两个子区间再分别进行移动。


此外,compose 与为操作新增插入/删除元素的方法 insert,delete 存在语义不清的问题,降低了客户端接口可读性。


总的来说,尽管新的数据结构较为清晰,但它使得复合类型的编辑操作的转换和组合难以设计和实现。compose 和 transform 内部有时使用相同上下文的插入删除元素,有时又使用有上下文依赖关系的编辑元素。而无论哪种方式都有较高的复杂度。此外,TextOperation 不仅设计实现成本高,测试时也必须考虑编辑操作的各种情况花大力气写单元测试才行。


TextOperation 耗去了我诸多时间成本,它的相关设计和测试中无疑是不合算的。不过,从另一个方面来说,TextOperation 的设计测试经历使我在理解 COT 和相关转换函数时更有自信,我能够以更轻松的视角对待 COT 的底层转换函数问题。相比于 TextOperation,面向 COT 的新的 SimpleTextOperation 无疑是简单却有效的。