Python趣味题–求bytes的压缩显示
2023-4-3 15:18:10 Author: blog.nsfocus.net(查看原文) 阅读量:17 收藏

阅读: 15

一、背景介绍

出个Python小题,有兴趣的可以做一下,没啥技术难度,但坑点不少,心得比较细。

import hexdump
tmp=b’PK\3\4’+b’\0’*26+b’+(\xca\xcc+\xd1P\xf7H\xcd’+b’\xc9’*2+b’W\b\xcf/\xcaIQT\xd7\4\0PK\1\2’+b’\0’*6+b’\1’+b’\0’*9+b’\x17’+b’\0’*7+b’\v’+b’\0’*17+b’_’*2+b’main’+b’_’*2+b’.pyPK\5\6’+b’\0’*8+b’9’+b’\0’*3+b’5’+b’\0’*3
hexdump.hexdump(tmp)

00000000: 50 4B 03 04 00 00 00 00 00 00 00 00 00 00 00 00 PK…………..
00000010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 2B 28 …………..+(
00000020: CA CC 2B D1 50 F7 48 CD C9 C9 57 08 CF 2F CA 49 ..+.P.H…W../.I
00000030: 51 54 D7 04 00 50 4B 01 02 00 00 00 00 00 00 01 QT…PK………
00000040: 00 00 00 00 00 00 00 00 00 17 00 00 00 00 00 00 …………….
00000050: 00 0B 00 00 00 00 00 00 00 00 00 00 00 00 00 00 …………….
00000060: 00 00 00 5F 5F 6D 61 69 6E 5F 5F 2E 70 79 50 4B …__main__.pyPK
00000070: 05 06 00 00 00 00 00 00 00 00 39 00 00 00 35 00 ……….9…5.
00000080: 00 00 ..

tmp数据如上,print(repr(tmp))看到的内容太占地方,请实现一个repr_ex(),使得print(repr_ex(tmp))看到的显示同tmp变量表达式。

>>> print(repr(tmp))
b’PK\x03\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00+(\xca\xcc+\xd1P\xf7H\xcd\xc9\xc9W\x08\xcf/\xcaIQT\xd7\x04\x00PK\x01\x02\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x17\x00\x00\x00\x00\x00\x00\x00\x0b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00__main__.pyPK\x05\x06\x00\x00\x00\x00\x00\x00\x00\x009\x00\x00\x005\x00\x00\x00′

>>> print(repr_ex(tmp))
b’PK\3\4’+b’\0’*26+b’+(\xca\xcc+\xd1P\xf7H\xcd’+b’\xc9’*2+b’W\b\xcf/\xcaIQT\xd7\4\0PK\1\2’+b’\0’*6+b’\1’+b’\0’*9+b’\x17’+b’\0’*7+b’\v’+b’\0’*17+b’_’*2+b’main’+b’_’*2+b’.pyPK\5\6’+b’\0’*8+b’9’+b’\0’*3+b’5’+b’\0’*3

>>> hexdump.hexdump(eval(repr_ex(tmp)))
00000000: 50 4B 03 04 00 00 00 00 00 00 00 00 00 00 00 00 PK…………..
00000010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 2B 28 …………..+(
00000020: CA CC 2B D1 50 F7 48 CD C9 C9 57 08 CF 2F CA 49 ..+.P.H…W../.I
00000030: 51 54 D7 04 00 50 4B 01 02 00 00 00 00 00 00 01 QT…PK………
00000040: 00 00 00 00 00 00 00 00 00 17 00 00 00 00 00 00 …………….
00000050: 00 0B 00 00 00 00 00 00 00 00 00 00 00 00 00 00 …………….
00000060: 00 00 00 5F 5F 6D 61 69 6E 5F 5F 2E 70 79 50 4B …__main__.pyPK
00000070: 05 06 00 00 00 00 00 00 00 00 39 00 00 00 35 00 ……….9…5.
00000080: 00 00 ..

之前懒得自实现repr_ex(),想让ChatGPT帮写一个,未如愿。或许谁运气好,能让ChatGPT实现此需求,自实现亦可。

二、 HYS与ChatGPT的对话

这是小侯与ChatGPT的对话,最终收敛了一下,提供了接近原始需求的关键信息。整个过程挺有借鉴意义,收集并展示之。

————————————————————————–
2023/3/31 17:17:40 hys

实现Python代码,将

b’PK\x03\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00+’

转换成

b’PK\3\4’+b’\0’*26+b’+’
————————————————————————–
2023/3/31 17:17:49 ChatGPT

可以使用下面的Python代码

data = b’PK\x03\x04…\x00+’
new_data = data[:4] + b’\0’*26 + data[-1:]
print(new_data)
————————————————————————–
2023/3/31 17:18:52 hys

上面字符串中的’\x00’个数非定值,请修改代码
————————————————————————–
2023/3/31 17:19:02 ChatGPT

如果该字符串中的\x00个数是不定值,那么可以这样

data = b’PK\x03\x04…\x00+’
new_data = data[:4] + b’\0’*(len(data)-6) + data[-2:]
print(new_data)

这里使用了len(data)获取了字符串的长度,并将其减去固定部分的长度(6),即可得到需要填充\0的长度。
————————————————————————–
2023/3/31 17:19:24 hys

完整字符串长度也非定值,请修改代码
————————————————————————–
2023/3/31 17:19:34 ChatGPT

如果字符串的长度也是不定值,我们可以使用以下代码

data = b’PK\x03\x04…\x00+’
new_data = data[:4] + b’\0’*(len(data)-4-2) + data[-2:]
print(new_data)

此处使用len(data)-4-2计算出需要填充\0的长度,其中4为固定部分的长度(即PK\3\4),2为结束符号的长度。
————————————————————————–
2023/3/31 17:24:06 hys

我的目的是压缩字符串中相同的字符,比如’\x00’,出现了26次,就压缩为’\0’*26
————————————————————————–
2023/3/31 17:24:16 ChatGPT

要压缩字符串中相同的字符,可以使用计数器来实现。您可以循环遍历字符串中所有的字符,对于每个字符,都增加一个计数器。当您遇到一个新字符时,将上一个字符和它出现的次数添加到结果字符串中,并将计数器重置为1。

以下是一个Python代码示例,演示如何执行此操作

def compress_string(s):
result = ”
count = 1
prev_char = s[0]
for char in s[1:]:
if char == prev_char:
count += 1
else:
# Add previous character and its count to result string
result += prev_char * count
# Reset counter and previous character
count = 1
prev_char = char
# Add last character and its count to result string
result += prev_char * count
return result

您可以将该函数应用于您的字符串,并查看输出是否符合您的预期。
————————————————————————–
2023/3/31 17:28:05 hys

修改上面代码将压缩字符修改成压缩byte
————————————————————————–
2023/3/31 17:28:14 ChatGPT

好的,我来修改一下代码。请注意,虽然byte类型表示压缩后的数据更加精确,但是在使用过程中也需要注意数据类型的转换问题。同时也需要注意解压缩时使用的解压函数要求输入字节数组。以下是修改之后的代码:

import zlib

def compress_byte(data):
byte_data = bytes(data, ‘utf-8’)
compressed_data = zlib.compress(byte_data)
return compressed_data

def decompress_byte(compressed_data):
decompressed_data = zlib.decompress(compressed_data)
str_data = decompressed_data.decode(‘utf-8’)
return str_data

这个函数和原来的函数功能类似,不同的是现在输入的是一个字符串,输出的是一个经过压缩的字节数组。
————————————————————————–
2023/3/31 17:28:57 hys

目的是压缩byte数组中相同的byte,比如b’\x00’,出现了26次,就压缩为b’\0’*26
————————————————————————–
2023/3/31 17:29:16 ChatGPT

明白了,这个可以使用Python中的itertools.groupby()函数来实现。以下是修改后的代码:

import itertools
import zlib

def compress_byte(data):
compressed_data = bytearray()
for byte, group in itertools.groupby(data):
count = len(list(group))
if count > 1:
compressed_data.extend(bytes([byte]))
compressed_data.extend(str(count).encode(‘utf-8’))
else:
compressed_data.extend(bytes([byte])*count)
return zlib.compress(compressed_data)

def decompress_byte(compressed_data):
decompressed_data = zlib.decompress(compressed_data)
data = bytearray()
i = 0
while i < len(decompressed_data):
byte = decompressed_data[i]
if byte not in range(10): # ascii码表中数字的范围是48-57
count_str = ”
while i+1 < len(decompressed_data) and decompressed_data[i+1] in range(48, 58):
count_str += chr(decompressed_data[i+1])
i += 1
count = int(count_str)
data.extend(bytes([byte])*count)
else:
data.append(byte)
i += 1
return bytes(data)

在压缩字节流时,我们对于连续出现的相同字节采用了“字节计数”的方式压缩,即将这个连续段的第一个字节与重复次数编码成一个字节序列存储,这样可以显著减小数据流的大小。

在解压缩时,我们首先对压缩后的数据流进行反压缩,然后对每个字节进行解码。如果遇到一个非数字的字节,说明这是一个未被压缩的字节,我们将其添加到输出数据流中;如果遇到一个数字,说明接下来的若干个字节是重复出现的相同字节,我们根据数字确定它们的重复次数,然后将这些字节依次添加到输出数据流中。
————————————————————————–

纵观整个对话,有一些经验教训。若用自然语言描述,应尽可能严谨。最开始使用”字符串”这种术语,ChatGPT就按字符串处理,但实际要处理bytes。compress_string虽然不直接满足原始需求,但算法原理已展现出来,不偷懒的话,到这一步已经可以自修改。接下来错误使用术语”压缩byte”,致使ChatGPT误以为需要zlib登场。最后一次提问,精确描述了需求,ChatGPT终于抓住要点,指出itertools.groupby(),这是最接近原始需求的关键信息。虽然compress_byte不是我们想要的,但无所谓了,小侯后来用itertools.groupby()写了一个满足原始需求的实现。

三、HYS的实现

————————————————————————–
#! /usr/bin/env python
# -*- encoding: utf-8 -*-

#
# ChatGPT
#
# trained by HYS
# modified by scz
#

import itertools, hexdump

#
##########################################################################
#

#
# i是int型
#
def i2s ( i, next=None ) :
if ord( b’\r’ ) == i :
s = r’\r’
elif ord( b’\n’ ) == i :
s = r’\n’
elif ord( b’\t’ ) == i :
s = r’\t’
elif ord( b’\a’ ) == i :
s = r’\a’
elif ord( b’\b’ ) == i :
s = r”\b”
elif ord( b’\f’ ) == i :
s = r’\f’
elif ord( b’\v’ ) == i :
s = r’\v’
elif ord( b’\” ) == i :
s = r”\'”
elif ord( b’\”‘ ) == i :
s = r’\”‘
elif ord( b’\\’ ) == i :
s = r’\\’
elif i >= 0 and i < 8 :
if next is None :
s = r’\%u’ % i
else :
if next < ord( b’0′ ) or next > ord( b’7′ ) :
s = r’\%u’ % i
else :
s = r’\x%02x’ % i
elif i >= 0x20 and i <= 0x7e :
s = chr( i )
else :
s = r’\x%02x’ % i
return s
#
# end of i2s
#

#
# [k for k, g in itertools.groupby(b’aaaabbbccd’)]
# [list(g) for k, g in itertools.groupby(b’aaaabbbccd’)]
#
def repr_ex ( sth ) :
out = []
tmp = “b'”
sth = [list(g) for k, g in itertools.groupby(sth)]
i = 0
while i < len( sth ) :
count = len( sth[i] )
if count > 1 :
if len( tmp ) > 2 :
out.append( tmp + “‘” )
tmp = “b'”
out.append( “b'” + i2s( sth[i][0] ) + “‘*” + str(count) )
else :
if i+1 < len( sth ) :
tmp += i2s( sth[i][0], sth[i+1][0] )
else :
tmp += i2s( sth[i][0] )
i += 1
#
# end of while
#
if len( tmp ) > 2 :
out.append( tmp + “‘” )
return ‘+’.join( out )
#
# end of repr_ex
#

#
##########################################################################
#

tmp=b’PK\3\4’+b’\0’*26+b’+(\xca\xcc+\xd1P\xf7H\xcd’+b’\xc9’*2+b’W\b\xcf/\xcaIQT\xd7\4\0PK\1\2’+b’\0’*6+b’\1’+b’\0’*9+b’\x17’+b’\0’*7+b’\v’+b’\0’*17+b’_’*2+b’main’+b’_’*2+b’.pyPK\5\6’+b’\0’*8+b’9’+b’\0’*3+b’5’+b’\0’*3
hexdump.hexdump( tmp )
print( repr_ex( tmp ) )
hexdump.hexdump( eval( repr_ex( tmp ) ) )

#
# other test case
#
tmp=b’abcabc\x7f\v\v\v\0f’
tmp=b’0aabcc’
tmp=b’\x0057′
————————————————————————–

四、electroniccc的实现

这是某网友见题后自己给出的实现

————————————————————————–
#! /usr/bin/env python
# -*- encoding: utf-8 -*-

#
# https://gist.github.com/electroniccc/af0ff26740533c001886491ad67d04e3
#
# modified by scz
#

import hexdump

#
##########################################################################
#

#
# i是int型
#
def i2s ( i, next=None ) :
if ord( b’\r’ ) == i :
s = r’\r’
elif ord( b’\n’ ) == i :
s = r’\n’
elif ord( b’\t’ ) == i :
s = r’\t’
elif ord( b’\a’ ) == i :
s = r’\a’
elif ord( b’\b’ ) == i :
s = r”\b”
elif ord( b’\f’ ) == i :
s = r’\f’
elif ord( b’\v’ ) == i :
s = r’\v’
elif ord( b’\” ) == i :
s = r”\'”
elif ord( b’\”‘ ) == i :
s = r’\”‘
elif ord( b’\\’ ) == i :
s = r’\\’
elif i >= 0 and i < 8 :
if next is None :
s = r’\%u’ % i
else :
if next < ord( b’0′ ) or next > ord( b’7′ ) :
s = r’\%u’ % i
else :
s = r’\x%02x’ % i
elif i >= 0x20 and i <= 0x7e :
s = chr( i )
else :
s = r’\x%02x’ % i
return s
#
# end of i2s
#

def repr_ex ( sth ) :
ret = ”
i = 0
tmp = “b'”
while i < len( sth ) :
j = i + 1
for k in range( i+1, len( sth )+1 ) :
if k == len( sth ) or sth[k] != sth[i] :
j = k
break
#
# end of for
#
if 1 == j – i :
if j < len( sth ) :
tmp += i2s( sth[i], sth[j] )
else :
tmp += i2s( sth[i] )
i += 1
else:
if len( tmp ) > 2 :
tmp += “‘”
ret += ‘+’ + tmp
tmp = “b'”
#
# end of if
#
ret += “+b'” + i2s( sth[i] ) + “‘*” + str(j-i)
i = j
#
# end of while
#
if len( tmp ) > 2 :
tmp += “‘”
ret += ‘+’ + tmp

return ret[1:]
#
# end of repr_ex
#

#
##########################################################################
#

def repr_ex_test ( sth ) :
ret = “b'”
i = 0
while i < len( sth ) :
if i+1 < len( sth ) :
next = sth[i+1]
else :
next = None
ret += i2s( sth[i], next )
i += 1
#
# end of while
#
ret += “‘”
return ret
#
# end of repr_ex_test
#

#
##########################################################################
#

tmp=b’PK\3\4’+b’\0’*26+b’+(\xca\xcc+\xd1P\xf7H\xcd’+b’\xc9’*2+b’W\b\xcf/\xcaIQT\xd7\4\0PK\1\2’+b’\0’*6+b’\1’+b’\0’*9+b’\x17’+b’\0’*7+b’\v’+b’\0’*17+b’_’*2+b’main’+b’_’*2+b’.pyPK\5\6’+b’\0’*8+b’9’+b’\0’*3+b’5’+b’\0’*3
hexdump.hexdump( tmp )
print( repr_ex( tmp ) )
hexdump.hexdump( eval( repr_ex( tmp ) ) )

#
# other test case
#
tmp=b’abcabc\x7f\v\v\v\0f’
tmp=b’0aabcc’
tmp=b’\x0057′
————————————————————————–

版权声明
本站“技术博客”所有内容的版权持有者为绿盟科技集团股份有限公司(“绿盟科技”)。作为分享技术资讯的平台,绿盟科技期待与广大用户互动交流,并欢迎在标明出处(绿盟科技-技术博客)及网址的情形下,全文转发。
上述情形之外的任何使用形式,均需提前向绿盟科技(010-68438880-5462)申请版权授权。如擅自使用,绿盟科技保留追责权利。同时,如因擅自使用博客内容引发法律纠纷,由使用者自行承担全部法律责任,与绿盟科技无关。


文章来源: http://blog.nsfocus.net/python%e8%b6%a3%e5%91%b3%e9%a2%98-%e6%b1%82bytes%e7%9a%84%e5%8e%8b%e7%bc%a9%e6%98%be%e7%a4%ba/
如有侵权请联系:admin#unsafe.sh