如何对一组相似的字符串进行压缩?

48 阅读4分钟

我们有一个 Python 脚本,该脚本维护着大量字符串。这些字符串实际上是回归测试运行的日志文件名。文件的数量非常大,以至于脚本开始达到虚拟地址空间限制。

这些字符串几乎没有熵,所以我想压缩它们。问题是我考虑过的压缩库(例如 Python - Compress Ascii String 中讨论的库)似乎单独压缩每个字符串(存储解压缩所需的所有信息)。常识表明,将整个字符串集压缩成单个全局 blob/字典,然后使用某种简短的引用来引用各个字符串,在空间方面会更有效。

以下是一些(伪)代码来阐明这个想法:

class TestResult:
    def __init__(self):
        self._Id         = None
        ....
        self.__appLogList = []
        return
....
    def setAppLogList(self, currentAppLogList):
        self.__appLogList = currentAppLogList
        # what I want to do here instead is 
        # for each s in currentAppLogList
        # add s to global compressed blob and
        # store reference in __appLogList

    def getAppLogList(self):
        return self.__appLogList
        self.__appLogList = currentAppLogList
        # what I want to do here instead is 
        # currentAppLogList = []
        # for each ref in __appLogList
        # decompress ref into string s and add s to currentAppLogList
        # return currentAppLogList

# for each test
# add new TestResult to result map
# setAppLogList

# for selected tests
# getAppLogList and access logs

1、解决方案:

答案1: 这种方法利用了这些字符串实际上是具有一个或多个公共子组件的文件路径这一事实,从而可以“压缩”字符串。我们将通过构建一个树形数据结构来消除这种冗余,该结构本质上是一个字典的字典。它的创建应该相对较快,因为它使用了内置的reduce()函数。

现在更新后的版本具有以下属性——与以前的那个不同——现在可以对创建的内容进行腌制,这意味着它可以稍后从文件中完整保存并读回。

就像我的另一个答案一样,这并没有做你所说的“真正的”压缩,但我想这样做可能对你想要完成的事情来说有点过分了,因为这可以解决你的问题,而且速度相对较快,而且更容易维护、增强和扩展。

import collections
import cPickle as pickle  # use C version for performance
import operator
import os

class DefaultDict(collections.defaultdict):
    """  pickle-able defaultdict """
    def __reduce__(self):
        args = (self.default_factory,) if self.default_factory else tuple()
        return type(self), args, None, None, self.iteritems()

def Tree(): return DefaultDict(Tree)
class Leaf(object): pass

def print_tree(d, level=0, indent=' '*4):
    """ Tree structure pretty printer """
    if not d:
        print indent * level + '<empty>'
    else:
        for key, value in sorted(d.iteritems()):
            print indent * level + str(key)
            if isinstance(value, dict):
                print_tree(value, level+1, indent)
            elif not isinstance(value, Leaf):
                print indent * (level+1) + str(value)

# create Tree structure from paths
tree = Tree()
LEAF = Leaf()
with open('log_file_paths.txt') as inf:
    for line in inf:
        path = line.strip().split(os.sep)
        reduce(operator.getitem, path[:-1], tree)[path[-1]] = LEAF
print_tree(tree)

# save tree to a file
with open('file_tree.pk', 'wb') as outf:
    pickle.dump(tree, outf, pickle.HIGHEST_PROTOCOL)

# try reading it back in
del tree
with open('file_tree.pk', 'rb') as inf:
    tree = pickle.load(inf)
print_tree(tree)  # display reconstituted tree

答案2: 这是我能想到的最简单的方法。它通过在字典中只存储每个唯一的目录路径一次来“压缩”数据。任何给定目录中的所有文件都存储在一个列表中。出于测试目的,下面的示例代码只读取一个输入文件,其中每行包含一个完整的文件路径。

from collections import defaultdict
import os

directories = defaultdict(list)

with open('log_file_paths.txt') as inf:
    for path in (line.strip() for line in inf):
        dir_path, file_name = os.path.split(path)
        directories[dir_path].append(file_name)

for path in directories:
    print path
    for file_name in directories[path]:
        print '   ', file_name

答案3: 我没有找到可以直接应用于此问题的时间限制的现成实现。似乎目前还不存在。以下是我考虑的一些方向。 后缀数组是建议的字典树的当代替代品。后缀数组可用于非常有效地实现子字符串搜索。然而,目前尚不清楚如何将它们用于我的问题。后缀数组的概述:Puglisi, S. J.、Smyth, W. F. 和 Turpin, A. H. (2007)。后缀数组构造算法的分类。ACM Computing Surveys (CSUR),39(2),4。 这项研究直接适用于我的问题:Brisaboa, N. R.、Cánovas, R.、Claude, F.、Martínez-Prieto, M. A. 和 Navarro, G. (2011)。压缩字符串字典。在实验算法中(第 136-147 页)。施普林格柏林海德堡。很遗憾,没有实现可以下载和重用。 为了计算语法,所有字符串都必须在内存中。我可能先收集前 1000 个字符串并计算语法;或者我可以进行完整的运行(直到内存故障),记录所有字符串并计算语法。可能的问题:字符串随着时间的推移而演变(名称包括日期/时间)。不是最佳的压缩率;此外,目前尚不清楚如果有一个不符合预先计算的语法的字符串会发生什么。也许这样的字符串根本不会被压缩。此解决方案不如先前的研究有效,并且仍需要大量的编码和测试工作。