LawrencePeng's Blog

专注收集代码小精灵

SuccinctStore深度解析 — CSA实现

  • 前提
    • SuccinctStore依靠Compressed Suffix Array这一数据结构进行构建,实际上这也是因为CSA在空间使用上会优于CST。不过,如果没有从理论上明白它的一些时空复杂度,我相信是没人刚用的。要是突然我能构造出一个异常的输入样本,导致糟糕的怎么办?所以我们先来看下它的时间空间复杂度吧。
    • 时间复杂度:O(n),实际上有很多好的CSA的构建算法都能达到这个上界,做竞赛的同学们可能会比较熟悉DC3算法。
    • 空间复杂度:O(n),这个也没什么悬念的?真的吗?对于CSA来说,我们需要更加紧的上界,这里就要提出k-th order empirical entropy了。
      • zero-th order empirical entropy
        • 要理解k-th order empirical entropy,首先要理解zero-th order empirical entropy。
        • zero-th order empirical entropy是单独压缩每个单词最后能压缩的大小,可以看出,它并没有利用到上下文信息。
      • k-th order empirical entropy,高级的压缩器能利用这一上下文并根据这一上下文选出不同的codeword,k是这个上下文的大小,k越大,当然压缩率会越高,但是这个上下文表示所需要的空间也会相应变大,一般我们需要有个empirical的选值。公式大家可以g娘一下。
      • CSA的空间复杂度为O(nHk(T)) + o(n)
  • 实现
    • 目标
      • f = compress(file)
      • append(f, buffer)
      • buffer = extract(f, offset, len)
      • cnt = count(f, str)
      • offsets = search(f, str)
      • offsets = rangesearch(f, str1, str2)
      • ranges = wildcardsearch(f, prefix, suffix, dist)
      • offsets = regex(f, pattern)
    • Flat Text To Semi-structured Data
      • 一些简单的拓展就能让我们实现更加复杂的数据模型。
    • Suffix Array 运用
      • 我们维护两个数组AoS2Input和Input2AoS,不难看出这两个互为反函数。
      • 当我们要search某Pattern p的时候,binary search p所在的两个端点,返回成区间形式即可。
      • 当我们要extract的时候,利用Input2AoS,直接是一个O(1)的操作。
    • 压压压
      • 原先的实现会需要维护O(nlog + n^2)的空间。所以考虑引入新的数组NextCharIdx,NextCharIdx(idx) -> NextIndexInAoS。
      • 这样我们只需要mark每个单词出现的首个index即刻,后面需要定位的时候follow NextCharIdx,通过binary search到对应的字符Offset即可。
      • 对于AoS2Input和Input2AoS两个输出,我们采用取样的方法减小其大小。默认选择这两个数组的大小为原数组的1/32。
      • 因为NextCharIdx本身是单调递增的,所以我们可以选择一些编码方式做DeltaCompression, 这里我使用的是Elias Gamma Code。