需要有关单词打包算法的帮助

时间:2010-08-16 23:40:49

标签: python algorithm

我有一个字母子列表列表,每个子列表中的字母数可以有所不同。列表和子列表是有序的。该结构可用于通过选择数字X来生成单词,从每个子列表中的位置X取一个字母并按顺序连接它们。如果数字X大于子列表的长度,它将会回绕。

给定一组单词,我需要找到一种方法将它们打包成这种最小的可能结构(即使用最短的子列表)。当然,最长单词中的字母数量必须与子字数一样多,而短单词将由空格/空格填充。

我不是CS毕业生,所以如果对问题的描述不完全清楚我会道歉。举一个简单的例子:假设我需要打包[ 'a ', 'an', 'if', 'is', 'in', 'on', 'of', 'i '],我可以使用以下结构:

[  
    [ 'i', 'o', 'a' ],  
    [ 's', 'n', 'f', ' ' ]  
]

这将使我能够产生以下词语:

0: is  
1: on  
2: af*  
3: i  
4: os*  
5: an  
6: if  
7: o *  
8: as*  
9: in  
10: of  
11: a

例如,如果你采取位置10,则通过连接第一个子列表中索引10%3(= 1)处的字母以及索引10%4处的字母(= 2)生成单词“of”。 )来自第二个子列表。

到目前为止,我最好的尝试是使用汉明距离矩阵来首先放置最“连接”的单词,然后放置它们最近的邻居,目标是每次插入时最小化变化。这是一次完全直观的尝试,我觉得必须有更好/更聪明的方法来解决这个问题。

澄清

这是我试图解决的实际问题,约束(大致)如下:
1.每个子列表的字符数应在100或更少的区域内 2.密钥空间应尽可能小(即虚假字的数量应该是最小的)。粗略地说,数以百万计的选项中的键空间是临界的。

我不知道一个好的解决方案甚至可能。以我现在使用的算法为例,我可以在150万个选项的密钥空间中插入大约200个单词(只是随机英语单词)。我想做得更好。

4 个答案:

答案 0 :(得分:3)

嗯,你说你对次优解决方案感兴趣,所以我会给你一个。它取决于字母表大小。例如,对于26个数组,大小将超过100(无论要编码的单词数量)。

众所周知,如果您有两个不同的素数ab以及非负整数klk < a,{ {1}}),您可以找到l < bn的号码n % a == k 例如,使用(n % b == l),您可以a = 7, a = 13, k = 6, l = 3n = 7 * 13 + 7 * 3 + 13 * 6n % 7 == 6

同样适用于任意数量的素数整数。

您可以像这样初始化数组。

n % 13 == 3

现在,假设你的话是'极客'。对于它,您需要数字X,例如['a', 'b', 'c', ... 'z', 'z', 'z', 'z', ...] # array size = 29 ['a', 'b', 'c', ... 'z', 'z', 'z', 'z', ...] # array size = 31 ['a', 'b', 'c', ... 'z', 'z', 'z', 'z', ...] # array size = 37 ['a', 'b', 'c', ... 'z', 'z', 'z', 'z', ...] # array size = 41 ... X % 29 == 6X % 31 == 4X % 37 == 4。你可以随时找到这样的X,如上所示。

因此,如果你有26个字母的字母,你可以创建宽度为149的矩阵(参见素数列表)并用它对任何单词进行编码。

答案 1 :(得分:2)

我们可以改进Nikita Rybek的回答,实际上并没有使列表成为一个主要长度,而只是将一个素数与列表相关联。这使我们不会使子列表超出必要的时间,因此保持较小的素数,这意味着更小的键空间和更有效的打包。使用此方法和下面的代码,我将58,110(小写)单词列表打包成464个字符。有趣的是,只有字母'alex'才能显示为单词中的第21个字母。密钥空间超过33位但是使用素数也不是绝对必要的,相关的数字只需要是互质的。这可能会减少。

import itertools
import operator
import math

# lifted from Alex Martelli's post at http://stackoverflow.com/questions/2068372/fastest-way-to-list-all-primes-below-n-in-python
def erat2( ):
    D = {  }
    yield 2
    for q in itertools.islice(itertools.count(3), 0, None, 2):
        p = D.pop(q, None)
        if p is None:
            D[q*q] = q
            yield q
        else:
            x = p + q
            while x in D or not (x&1):
                x += p
            D[x] = p


# taken from http://en.literateprograms.org/Extended_Euclidean_algorithm_(Python)
def eea(u, v):
    u1 = 1; u2 = 0; u3 = u
    v1 = 0; v2 = 1; v3 = v
    while v3 != 0:
        q = u3 / v3
        t1 = u1 - q * v1
        t2 = u2 - q * v2
        t3 = u3 - q * v3
        u1 = v1; u2 = v2; u3 = v3
        v1 = t1; v2 = t2; v3 = t3
    return u1, u2, u3

def assign_moduli(lists):
    used_primes = set([])
    unused_primes = set([])
    moduli = [0]*len(lists)
    list_lens = [len(lst) for lst in lists]
    for i, length in enumerate(list_lens):
        for p in erat2():
            if length <= p and p not in used_primes:
                used_primes.add(p)
                moduli[i] = p
                break
            elif p not in used_primes:
                unused_primes.add(p)
    return moduli



class WordEncoder(object):
    def __init__(self):
        self.lists = [[]] # the list of primedlists
        self.words = {} # keys are words, values are number that retrieves word
        self.moduli = [] # coprime moduli that are used to assign unique keys to words

    def add(self, new_words):
        added_letter = False # flag that we need to rebuild the keys
        for word in new_words:
            word = word.rstrip() # a trailing blank space could hide the need for a key rebuild
            word_length, lists_length = len(word), len(self.lists)
            # make sure we have enough lists
            if word_length > lists_length:
                self.lists.extend([' '] for i in xrange(word_length - lists_length))
            # make sure that each letter is in the appropriate list
            for i, c in enumerate(word):
                if c in self.lists[i]: continue
                self.lists[i].append(c)
                added_letter = True
            self.words[word] = None
        # now we recalculate all of the keys if necessary
        if not added_letter:
            return self.words
        else:
            self._calculate_keys()

    def _calculate_keys(self):
        # were going to be solving a lot of systems of congruences
        # these are all of the form x % self.lists[i].modulus == self.lists[i].index(word[i]) with word padded out to 
        # len(self.lists). We will be using the Chinese Remainder Theorem to do this. We can do a lot of the calculations
        # once before we enter the loop because the numbers that we need are related to self.lists[i].modulus and not
        # the indexes of the necessary letters
        self.moduli = assign_moduli(self.lists)
        N  = reduce(operator.mul, self.moduli)
        e_lst = []
        for n in self.moduli:
             r, s, dummy = eea(n, N/n)
             e_lst.append(s * N / n)
        lists_len = len(self.lists)
        #now we begin the actual recalculation 
        for word in self.words:
             word += ' ' * (lists_len - len(word))
             coords = [self.lists[i].index(c) for i,c in enumerate(word)]
             key = sum(a*e for a,e in zip(coords, e_lst)) % N  # this solves the system of congruences
             self.words[word.rstrip()] = key

class WordDecoder(object):
    def __init__(self, lists):
       self.lists = lists
       self.moduli = assign_moduli(lists)

    def decode(self, key):
        coords = [key % modulus for modulus in self.moduli]
        return ''.join(pl[i] for pl, i in zip(self.lists, coords))    


with open('/home/aaron/code/scratch/corncob_lowercase.txt') as f:
    wordlist = f.read().split()

encoder = WordEncoder()
encoder.add(wordlist)

decoder = WordDecoder(encoder.lists)

for word, key in encoder.words.iteritems():
    decoded = decoder.decode(key).rstrip()
    if word != decoded:
        print word, decoded, key
        print "max key size: {0}. moduli: {1}".format(reduce(operator.mul, encoder.moduli), encoder.moduli)
        break
else:
    print "it works"
    print "max key size: {0}".format(reduce(operator.mul, encoder.moduli))
    print "moduli: {0}".format(encoder.moduli)
    for i, l in enumerate(encoder.lists):
        print "list {0} length: {1}, {2} - \"{3}\"".format(i, len(l), encoder.moduli[i] - len(l), ''.join(sorted(l)))
    print "{0} words stored in {1} charachters".format(len(encoder.words), sum(len(l) for l in encoder.lists))

答案 2 :(得分:0)

我不认为我完全理解你的问题,但是我不久前偶然发现了prezip。 Prezip是一种通过利用许多单词共享一个共同前缀的事实来压缩一组有序单词的方法。

由于您没有引用任何排序约束,我建议您创建一个您想要的有序单词集。然后做类似于prezip正在做的事情。结果是一组经过压缩和排序的单词,您可以通过索引来引用它。

答案 3 :(得分:0)

我认为您正在寻找此http://en.wikipedia.org/wiki/Trie或此http://en.wikipedia.org/wiki/Radix_tree

希望它有所帮助。