【问题标题】:How can I generate de Bruijn sequences iteratively?如何迭代地生成 de Bruijn 序列?
【发布时间】:2019-07-03 16:59:03
【问题描述】:

我正在寻找一种迭代而不是递归生成 de Bruijn 序列的方法。我的目标是逐个字符地生成它。

我发现 some example code in Python 用于生成 de Bruijn 序列并将其翻译成 Rust。我还不能很好地理解这种技术来创建我自己的方法。

翻译成Rust:

fn gen(sequence: &mut Vec<usize>, a: &mut [usize], t: usize, p: usize, k: usize, n: usize) {
    if t > n {
        if n % p == 0 {
            for x in 1..(p + 1) {
                sequence.push(a[x])
            }
        }
    } else {
        a[t] = a[t - p];
        gen(sequence, a, t + 1, p, k, n);
        for x in (a[t - p] + 1)..k {
            a[t] = x;
            gen(sequence, a, t + 1, t, k, n);
        }
    }
}

fn de_bruijn<T: Clone>(alphabet: &[T], n: usize) -> Vec<T> {
    let k = alphabet.len();
    let mut a = vec![0; n + 1];
    let vecsize = k.checked_pow(n as u32).unwrap();
    let mut sequence = Vec::with_capacity(vecsize);
    gen(&mut sequence, &mut a, 1, 1, k, n);
    sequence.into_iter().map(|x| alphabet[x].clone()).collect()
}

然而,这不能迭代生成——它经历了一大堆递归和迭代,不可能解开成一个单一的状态。

【问题讨论】:

  • 为什么需要迭代生成它们?似乎使用递归和 Rust 的生成器/协程将是一个简单的解决方案。
  • @Richard 我希望有一些可以稳定运行的东西,因为我不确定生成器在发布之前是否会保持不变。但是,如果我找不到任何看起来不错的主意。

标签: algorithm rust


【解决方案1】:

考虑这种方法:

  1. 从每个项链类别中选择第一个(按字典顺序)代表

    Here is Python code 用于生成包含 d 个 (binary) 项链的代表(可以对所有 d 值重复)。 Sawada article link

  2. 按字典顺序对代表进行排序

  3. 对每个代表进行周期性减少(如果可能):如果字符串是周期性的s = p^m,如010101,则选择01

    要查找句点,可以使用string doublingz-algorithm(我希望编译语言会更快)

  4. 串联减少

    n=3,k=2 的示例:
    排序代表:000, 001, 011, 111
    减少:0, 001, 011, 1
    结果:00010111

Jörg Arndt 的书 "Matters Computational" 第 18 章描述了相同的基本方法(使用 C 代码)

wiki中提到了类似的方式

另一种构造涉及连接在一起,在 字典顺序,长度除以n的所有Lyndon词

您可能会寻找有效的方法来生成适当的林登词

【讨论】:

    【解决方案2】:

    我对 Rust 不熟悉,所以我用 Python 对其进行了编程和测试。由于发帖者是从 Python 程序翻译问题中的版本,我希望这不会是一个大问题。

    # the following function treats list a as
    # k-adic number with n digtis
    # and increments this number returning
    # the index of the leftmost digit changed
    def increment_a7(a, k, n):
        digit= n-1
        a[digit]+= 1
        while a[digit] >= k and digit> 0:
            #a[digit]= 0
            a[digit]= a[0]+1
            a[digit-1]+= 1
            digit-= 1
        return digit
    
    # the following function adds a to the sequence
    # and takes into account, that the beginning of a
    # could overlap with the end of sequence
    # in that case, it just removes the overlapping digits
    # from a before adding the remaining digits to sequence
    def append_to_sequence(sequence, a, n):
        # here we can assume safely, that a
        # does not overlap completely with sequence[-n:]
        i= -1
        for i in range(n-1, -1, -1):
            found= True
            # check if the last i digits in sequence
            # overlap with the first i digits in a
            for j in range(i):
                if a[j] != sequence[-i+j]:
                    # no, they don't overlap
                    found= False
                    break
            if found:
                # yes they overlap, so no need to 
                # continue the check with a smaller i
                break
        # now we can just append everything from
        # digit i (digit 0 - i-1 are swallowed)
        sequence.extend(a[i:])    
        return n-i
    
    # during the operation we have to keep track of
    # the k-adic numbers a, that already occured in
    # the sequence. We store them in a set called used
    # everytime we add something to the sequence
    # we have to update it and add one entry for each
    # digit inserted
    def update_used(sequence, used, n, num_inserted):
        l= len(sequence)
        for i in range(num_inserted):
            used.add(tuple(sequence[-n-i:l-i]))
    
    # the main work is done in the following function
    # it creates and returns the generated sequence
    def gen4(k, n):
        a= [0]*n
        sequence= a[:]
        used= set()
        # create a fake sequence to add the segments obtained by the cyclic nature
        fake= ([k-1] * (n-1))
        for i in range(n-1):
            fake.append(0)
            update_used(fake, used, n, 1)
        update_used(sequence, used, n, 1)
        valid= True
        while valid:
            # a is still a valid k-adic number
            # this means the generation process
            # has not ended
            # so construct a new number from the n-1
            # last digits of sequence
            # followed by a zero
            a= sequence[-n+1:]
            a.append(0)
            while valid and tuple(a) in used:
                # the constructed k-adict number a
                # was already used, so increment it
                # and try again
                increment_a(a, k, n)
                valid= a[0]<k
            if valid:
                # great, the number is still valid
                # and is not jet part of the sequence
                # so add it after removing the overlapping
                # digits and update the set with the segments
                # we already used
                num_inserted= append_to_sequence(sequence, a, n)
                update_used(sequence, used, n, num_inserted)
        return sequence
    

    我通过使用gen 的原始版本生成一些序列来测试上面的代码,而这个使用相同的参数。对于我测试的所有参数集,两个版本的结果都是一样的。

    请注意,此代码的效率低于原始版本,尤其是在序列变长的情况下。我猜集合操作的成本对运行时间有非线性影响。

    如果您愿意,可以进一步改进它,例如使用更有效的方式来存储使用的段。您可以使用多维数组来代替对 k-adic 表示(a-list)进行操作。

    【讨论】:

    • 谢谢 - 这行得通,但我希望有一个不是蛮力的解决方案(这种解决方案的复杂性很快就会爆炸)
    • @Plasma_000 啊抱歉。所以这不是挑战吗?加油!
    猜你喜欢
    • 2021-08-19
    • 2017-04-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多