Arithmetic Code 算术编码

Principle:

算术编码的原理是非常直白的。在算术编码中我们考虑对[消息序列整体]进行编码,以这里的消息序列[210]为例,其落在概率区间[0.68,0.712]中。然后我们需要用二进制编码去表征这个区间,注意此时用二进制编码去表征时,需要表征的是其[最大子区间],不然就会产生信息表征的混叠。

比如在这个例子中,我们用10110去表征这个区间[0.68,0.712],这里的每一位二进制分别代表2^{-1},2^{-2},...,2^{-n}。如此我们就得到了消息序列210的编码:101100

解码过程中,为了找到唯一的消息序列,需要用到消息的先验知识

1. 预先知道消息序列的长度

2. 消息序列含有EOF,这样就可以确定什么时候应该终止迭代。

Code for encoder in Arithmetic Code

symbolProbDistribute = (0.2, 0.4, 0.4) 
messageSequence = [2, 1, 0]
decimalCode = ''

def calProbDistrict(distinctLen = 1.0, 
                    distinctStart = 0.0,
                    messageSequence = messageSequence, 
                    symbolProbDistribute = symbolProbDistribute) -> tuple:
    # 设计思路: recursion 把每一轮的起始端点 与 区间长度 传给下一轮的分割

    if len(messageSequence) == 0:
        return (distinctStart, distinctStart + distinctLen)
    
    symbol = messageSequence.pop(0)
    t = symbol
    
    while t > 0:
        t -= 1
        distinctStart += symbolProbDistribute[t] * distinctLen

    distinctLen *= symbolProbDistribute[symbol]    
    (distinctStart, distinctEnd) = calProbDistrict(distinctLen, distinctStart) 
    return (distinctStart, distinctEnd)


def calDecimalFromBinary(binaryCode: str) -> float:
    decimalLst = list(map(int, binaryCode))
    return sum([pow(1/2, i + 1) * decimalLst[i] for i in range(len(decimalLst))])


def arithmeticCode(messageDistinct) -> str:
    messageDistinctLen = messageDistinct[1] - messageDistinct[0]
    messageDistinctStart, messageDistinctEnd = messageDistinct

    decimalDistinctCode = ''

    while True:
        decimalDistinctStart = str(decimalDistinctCode) + '0'
        decimalDistinctEnd = str(decimalDistinctCode) + '1'
        # print(decimalDistinctStart, decimalDistinctEnd)
        start = calDecimalFromBinary(decimalDistinctStart)
        end =  calDecimalFromBinary(decimalDistinctEnd)
        
        if start >= messageDistinctStart:
            if end <= messageDistinctEnd:
                return decimalDistinctStart
            else:
                decimalDistinctCode = decimalDistinctStart
                continue

        else:
            if end >= messageDistinctEnd:
                decimalDistinctCode = decimalDistinctStart
                continue
            else:
                decimalDistinctCode = decimalDistinctEnd
                continue
        

if __name__ == '__main__':
    massDistinct = calProbDistrict()
    print(arithmeticCode(massDistinct))

复现时的一些心路历程

1. 在生成信码区间时,我用的是递归的策略。在二进制编码时,我用的是循环。个人感觉两者主要区别在[是否程序有一个[以栈、队列的形式维护的数据结构 作为终止的标志]。

比如在生成信码时,我们是把[2,1,0]这三个信码逐一放进函数去编的,当最后一位0被移出栈,递归也就结束。

但在二进制编码时,我们是通过一个[比较]的操作来结束循环的,即二进制区间[第一次成为]待求区间的子区间时,以此作为循环结束的标志。这个时候用递归就显得有些鸡肋而且没有必要。

Reference:

https://www.youtube.com/watch?v=7vfqhoJVwuc  [上面那张图的来源,不得不说youtube上的教程质量是真的很高,讲的非常清楚]

口水仗系列:recursion vs. for-loop - 知乎 (zhihu.com)

猜你喜欢

转载自blog.csdn.net/Blossomers/article/details/125915551
今日推荐