标题: Python趣味题--求bytes的压缩显示 创建: 2023-03-31 16:32 更新: 2023-04-03 10:55 链接: https://scz.617.cn/python/202303311632.txt -------------------------------------------------------------------------- 目录: ☆ 背景介绍 ☆ HYS与ChatGPT的对话 ☆ HYS的实现 ☆ electroniccc的实现 ☆ i2s() -------------------------------------------------------------------------- ☆ 背景介绍 出个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' -------------------------------------------------------------------------- ☆ i2s() 小侯让ChatGPT优化一下我写的i2s(),ChatGPT返回如下实现 -------------------------------------------------------------------------- def i2s_not_good ( i, next=None ) : if chr(i) in '\r\n\t\a\b\f\v\'\"\\' : s = repr( chr(i) )[1:-1] elif i >= 0x20 and i <= 0x7e : s = chr(i) elif i >= 0 and i < 8 : if next is None or next < ord( b'0' ) or next > ord( b'7' ) : s = r'\%u' % i else: s = r'\x%02x' % i else : s = r'\x%02x' % i return s -------------------------------------------------------------------------- i2s_not_good()能用,不会致错,但就测试用例而言,有微妙的坑。留个小作业,不 实际运行并观察结果的前提下,你能看出i2s_not_good()并不等价于i2s()吗,请指 出坑点何在。我并未第一时间看出坑点,是用测试用例发现的。 实际运行自然一目了然,这种13躲自个被窝里装去,千万别来,一旦被判定是这种13 货,直接拉黑,不信邪的可以试试。