查找2个字符串的所有备用连接

时间:2014-01-05 03:21:38

标签: algorithm concatenation dynamic-programming combinatorics

所以我正在尝试编写一个函数来查找2个字符串的所有备用连接。说出x = '12'y = 'ab',然后预期结果为['ab12', 'a1b2', 'a12b', '1ab2', '1a2b', '12ab']。我写了以下程序:

# [ list of sequences where each seq is a concat of s and t ] 
def g( s, t ) :
    #
    if ( s == "" ) :
        return [ t ]
    elif ( t == "" ) :
        return [ s ]
    else :
        res = []
        for i in xrange( len(s) ) :
            for j in xrange( 1, len(t) + 1 ) :
                res.extend( [ s[:i] + t[:j] + x for x in g( s[i:], t[j:] ) ] )
                #
        res.append( s + t )
        return res

它输出正确的结果,但有些序列有重复:

In [22]: r = g( "12", "ab" )
         [ (x, r.count(x)) for x in set( r ) ]
Out[22]: [('ab12', 2), ('12ab', 1), ('1ab2', 2), ('a12b', 1), ('1a2b', 1), ('a1b2', 1)]

如何避免重复? (我不想检查是否已经添加了一个元素;我对一个"真正的"生成独特序列的方式感兴趣)

4 个答案:

答案 0 :(得分:1)

最好使用递归方法。剥离s中的第一个字符,找到剩余字符串的所有组合,并对t执行相同的操作:

def g1( s, t ):
        return [s[0]+x for x in g( s[1:], t )]

def g(s,t):
        if( s=="" ):
                return [t]
        elif t=="":
                return [s]
        else:
                return g1( s, t ) + g1( t, s )

答案 1 :(得分:0)

重复的来自哪里?

代码正在构建t两次的子字符串:循环遍历j的值,并始终在每次递归时选择s的空前缀。

对于j的每次迭代,您都会附加t[:j],然后附加一个以i = 0开头的递归调用,从而附加更多字符t。因此,t循环和递归将创建j中相同的字符子串。例如,“12ab”可以通过在第一级递归中以“1”开头,或者在第一层递归中以“12”开头来构建。

(重复的模式对于更长的字符串更明显,比如“abc”和“123”。)

修复原始解决方案

让我们修复原始解决方案。我们希望通过t循环或递归来构建j的每个附加子串。为了好玩,我会为每个人展示一个解决方案。

首先,让我们保持j循环。这意味着我们需要强制每次递归调用g,而不是以t的字符开头。但是我们还需要第一次调用g来生成以t字符开头的字符串。这是一种半黑客的方式:

def g( s, t, z=0 ) :
    if ( s == "" ) :
        return [ t ]
    elif ( t == "" ) :
        return [ s ]
    else :
        res = []
        for i in xrange( z, len(s) ) :
            for j in xrange( 1, len(t) + 1 ) :
                res.extend( [ s[:i] + t[:j] + x for x in g( s[i:], t[j:], 1) ])
        res.append( s + t )
        return res

g的每次调用都会遍历所有字符串,其前缀为s,后跟t的所有前缀,后跟递归(必须以s开头) 。第一次调用g是一种特殊情况,因此我们明确允许第一次调用允许t作为第一个前缀。

或者我们可以让递归构建t

的子串
def g( s, t, ) :
    if ( s == "" ) :
        return [ t ]
    elif ( t == "" ) :
        return [ s ]
    else :
        res = []
        for i in xrange( len(s) ) :
            res.extend( [ s[:i] + t[:1] + x for x in g( s[i:], t[1:] ) ] )
        res.append( s + t )
        return res

在此版本中,我们选择s的所有前缀,添加t中的字符,然后递归。由于我们将""计为s的前缀,因此将构建t大于1的子字符串。

另外,当递归有效时,您可以使用

for i in xrange( len(s) + 1) :

消除这条线

res.append( s + t )

答案 2 :(得分:0)

你要从0 ... len(a)+ len(b)-1中选择len(b)索引,以便出现b的字符(其他索引从a获取字符)。这建议使用itertools.combinations,它提供了这样的解决方案:

import itertools

def concats(a, b):
    for i in map(set, itertools.combinations(xrange(len(a) + len(b)), len(b))):
        its = iter(a), iter(b)
        yield ''.join(next(its[x in i]) for x in xrange(len(a) + len(b)))

print list(concats('abc', '12'))

答案 3 :(得分:0)

顺便说一下,我在Mathematica.SE上recently answered同样的问题(虽然不是字符串)。
我的解决方案使用了十五边形描述的算法:

f[u : {a_, x___}, v : {b_, y___}, c___] := f[{x}, v, c, a] ~Join~ f[u, {y}, c, b]

f[{x___}, {y___}, c___] := {{c, x, y}}

使用:

f[{1, 2}, {a, b}]
{{1, 2, a, b}, {1, a, 2, b}, {1, a, b, 2},
 {a, 1, 2, b}, {a, 1, b, 2}, {a, b, 1, 2}}

此代码几乎只使用 Mathematica pattern matching syntax,唯一的例外是Join