使用Burgers Wheeler变换的后缀数组算法

时间:2016-01-06 18:10:59

标签: c++ algorithm suffix-array burrows-wheeler-transform

我已经成功地为compression testbed我写的BWT阶段(使用常规字符串排序)实现了。我可以应用BWT然后反BWT变换,输出匹配输入。现在我想加速使用后缀数组创建BW索引表。我找到了两个相对简单的,假设快速的O(n)算法用于后缀数组创建,DC3SA-IS都带有C ++ / C源代码。我尝试使用这些源代码(开箱即用的编译SA-IS源代码也可以找到here),但无法获得正确的后缀数组/ BWT索引表。这就是我所做的:

  1. T =输入数据,SA =输出后缀数组,n = T的大小,K =字母大小,BWT = BWT索引表

  2. 我处理8位字节,但两种算法都需要一个零字节形式的唯一标记/ EOF标记(DC3需要3,SA-IS需要一个),因此我将所有输入数据转换为32位整数,将所有符号增加1并附加sentinel零字节。这是T。

  3. 我创建了一个整数输出数组SA(DC3的大小为n,KA-IS的大小为n +)并应用算法。我得到的结果类似于我的排序BWT变换,但有些值是奇数(见更新1)。两种算法的结果也略有不同。 SA-IS算法在前面产生一个超额索引值,因此所有结果都需要被一个索引(SA [i] = SA [i + 1])左侧复制。

  4. 要将后缀数组转换为正确的BWT索引,我从后缀数组值中减去1,做一个模数并且应该有BWT索引(根据this):BWT [i] = (SA [I] -1)%N。

  5. 这是我提供SA算法并转换为BWT的代码。您应该能够或多或少地从论文中插入SA构造代码:

    std::vector<int32_t> SuffixArray::generate(const std::vector<uint8_t> & data)
    {
        std::vector<int32_t> SA;
        if (data.size() >= 2)
        {
            //copy data over. we need to append 3 zero bytes, 
            //as the algorithm expects T[n]=T[n+1]=T[n+2]=0
            //also increase the symbol value by 1, because the algorithm alphabet is [1,K]
            //(0 is used as an EOF marker)
            std::vector<int32_t> T(data.size() + 3, 0);
            std::copy(data.cbegin(), data.cend(), T.begin());
            std::for_each(T.begin(), std::prev(T.end(), 3), [](int32_t & n){ n++; });
            SA.resize(data.size());
            SA_DC3(T.data(), SA.data(), data.size(), 256);
    
            OR
    
            //copy data over. we need to append a zero byte, 
            //as the algorithm expects T[n-1]=0 (where n is the size of its input data)
            //also increase the symbol value by 1, because the algorithm alphabet is [1,K] 
            //(0 is used as an EOF marker)
            std::vector<int32_t> T(data.size() + 1, 0);
            std::copy(data.cbegin(), data.cend(), T.begin());
            std::for_each(T.begin(), std::prev(T.end(), 1), [](int32_t & n){ n++; });
            SA.resize(data.size() + 1); //crashes if not one extra byte at the end
            SA_IS((unsigned char *)T.data(), SA.data(), data.size() + 1, 256, 4); //algorithm expects size including sentinel
            std::rotate(SA.begin(), std::next(SA.begin()), SA.end()); //rotate left by one to get same result as DC3
            SA.resize(data.size());
        }
        else
        {
            SA.push_back(0);
        }
        return SA;
    }
    
    void SuffixArray::toBWT(std::vector<int32_t> & SA)
    {
        std::for_each(SA.begin(), SA.end(), [SA](int32_t & n){ n = ((n - 1) < 0) ? (n + SA.size() - 1) : (n - 1); });
    }
    

    我做错了什么?

    更新1
    将算法应用于诸如“yabbadabbado”/“这是一个测试”的短期测试文本数据时。 /“abaaba”或一个大文本文件(来自坎特伯雷语料库的alice29.txt)他们工作正常。实际上甚至不需要toBWT()函数 将算法应用于包含完整8位字节字母(可执行文件等)的文件中的二进制数据时,它们似乎无法正常工作。将算法的结果与常规BWT索引的结果进行比较,我注意到前面的错误索引(在我的例子中为4)。索引的数量(不可避免地?)对应于算法的递归深度。索引指向原始源数据最后出现0的位置(在构建T之前我将它们转换为1)...

    更新2
    当我对常规BWT数组和后缀数组进行二进制比较时,有更多不同的值。这可能是预期的,因为空中排序不一定与标准排序相同,但由数组转换的结果数据应该是相同的。事实并非如此。

    更新3
    我尝试修改一个简单的输入字符串,直到两个算法都“失败”。更改字符串的两个字节后“这是一个测试”。到255或0(从746869732069732061 20 74657374 2E h到例如746869732069732061 FF 74657374 FF h,最后一个字节必须改变!)索引和转换后的字符串不再正确。似乎足以将字符串的最后一个字符更改为字符串中已经出现的字符,例如“这是一项测试”7468697320697320612074657374 73 h。然后交换两个索引和两个转换字符串的字符(比较使用SA的常规排序BWT和BWT)。

    我发现必须将数据转换为32位的整个过程有点尴尬。如果有人有一个更好的解决方案(纸,更好,一些源代码)来直接从一个256字母字母的字符串生成一个后缀数组,我会很高兴。

1 个答案:

答案 0 :(得分:2)

我现在想出来了。我的解决方案是双重的。有些人建议使用图书馆,我由Yuta Mori做SAIS-lite 真正的解决方案是复制和连接输入字符串并在此字符串上运行SA生成。保存输出字符串时,需要过滤掉原始数据大小之上的所有SA索引。这不是一个理想的解决方案,因为你需要分配两倍的内存,复制两次并对双倍数据进行转换,但它仍然比std :: sort快50-70%。如果你有更好的解决方案,我很乐意听到它 您可以找到更新后的代码here