Principle:
算术编码的原理是非常直白的。在算术编码中我们考虑对[消息序列整体]进行编码,以这里的消息序列[210]为例,其落在概率区间[0.68,0.712]中。然后我们需要用二进制编码去表征这个区间,注意此时用二进制编码去表征时,需要表征的是其[最大子区间],不然就会产生信息表征的混叠。
比如在这个例子中,我们用10110去表征这个区间[0.68,0.712],这里的每一位二进制分别代表。如此我们就得到了消息序列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上的教程质量是真的很高,讲的非常清楚]