web-dev-qa-db-ja.com

2つの文字列をインターリーブするすべての可能な方法

私はPythonで任意の2つの任意の文字列をインターリーブするすべての可能な方法を生成しようとしています。

例:2つの文字列が'ab'および'cd'、取得したい出力は次のとおりです。

['abcd', 'acbd', 'acdb', 'cabd', 'cadb', 'cdab']

aは常にbの前にあります(およびcdの前にあります)。私はこれに対する解決策を見つけるのに苦労しています。以下に示すようにitertoolsを試しました:

import itertools

def shuffle(s,t):
    string = s+t
    for i in itertools.permutations(string):
        print(''.join(i))

shuffle('ab','cd')

しかし、予想通り、これはab(およびcd)の順序に関係なく、可能なすべての順列を返します。

19
Seb

アイデア

インターリーブする2つの文字列をsおよびtとします。再帰を使用して、これら2つの文字列をインターリーブするすべての可能な方法を生成します。

任意の時点で、iの最初のs文字とjの最初のt文字をインターリーブして、文字列res、次に、次のステップのためにそれらをインターリーブする2つの方法があります-

  1. si+1番目の文字をresに追加します
  2. tj+1番目の文字をresに追加します

両方の文字列のすべての文字が使用されるまでこの再帰を続け、次のコードのように、この結果を文字列lisのリストに格納します。

コード

def interleave(s, t, res, i, j, lis):
    if i == len(s) and j == len(t):
        lis.append(res)
        return
    if i < len(s):
        interleave(s, t, res + s[i], i + 1, j, lis)
    if j < len(t):
        interleave(s, t, res + t[j], i, j + 1, lis)

l = []
s = "ab"
t = "cd"
interleave(s, t, "", 0, 0, l)
print l

出力

['abcd', 'acbd', 'acdb', 'cabd', 'cadb', 'cdab']

同じ文字列を2回生成することはないため、この実装は(少なくとも漸近的に)可能な限り効率的です。

17
Banach Tarski

他のいくつかの解決策は既に投稿されていますが、それらのほとんどはインターリーブされた文字列(またはそれに相当するもの)の完全なリストをメモリに生成し、メモリ使用量が入力長の関数として指数関数的に増加します。きっともっと良い方法があるに違いない。

長さaおよびbの2つのシーケンスをインターリーブするすべての方法を列挙することは、基本的にすべてを列挙することと同じですa + b可能性のあるビット整数bビットセット。このような各整数は、0ビットごとに最初のシーケンスの要素に、1ビットごとに2番目のシーケンスの要素に置き換えることで得られる、シーケンスをインターリーブするための異なる方法に対応しています。

便利なことに 同じビット数のセットで次の整数を計算する を計算するための賢明で効率的な方法があります。これを使用して、そのような整数をすべて生成できます。最初にそれをしましょう:

_def bit_patterns(m, n):
    """Generate all m-bit numbers with exactly n bits set, in ascending order.
    See http://www.geeksforgeeks.org/next-higher-number-with-same-number-of-set-bits/
    """
    patt = (1 << int(n)) - 1
    if patt == 0: yield 0; return  # loop below assumes patt has at least one bit set!
    while (patt >> m) == 0:
        yield patt
        lowb = patt & -patt  # extract the lowest bit of the pattern
        incr = patt + lowb   # increment the lowest bit
        diff = patt ^ incr   # extract the bits flipped by the increment
        patt = incr + ((diff // lowb) >> 2)  # restore bit count after increment
_

これで、このジェネレーターを使用して、2つのシーケンスをインターリーブするすべての方法を生成できます。

_def interleave(a, b):
    """Generate all possible ways to interleave two sequences."""
    m = len(a) + len(b)
    n = len(a)
    for pattern in bit_patterns(m, n):
        seq = []
        i = j = 0
        for k in range(m):
            bit = pattern & 1
            pattern >>= 1
            seq.append(a[i] if bit else b[j])
            i += bit
            j += 1-bit
        yield seq
_

できる限り一般的になるようにするために、このコードは任意のシーケンス型を取り、リストを返すことに注意してください。文字列はPythonのシーケンスなので、うまく渡すことができます。生成されたリストを文字列に変換するには、それらの要素を連結できます。 "".join()を使用すると、次のようになります。

_foo = "ABCD"
bar = "1234"
for seq in interleave(foo, bar):
    print("".join(seq))
_

完全な非再帰的な効率的なジェネレーターベースのソリューションで、長い入力でもメモリをほとんど使用せず、各出力を1回だけ生成します(したがって、非効率的な重複排除手順は必要ありません)。また、Python 2と3の両方でも機能します。

13
Ilmari Karonen

非常に非効率的ですが機能しています:

def shuffle(s,t):
    if s=="":
        return [t]
    Elif t=="":
        return [s]
    else:
        leftShuffle=[s[0]+val for val in shuffle(s[1:],t)]
        rightShuffle=[t[0]+val for val in shuffle(s,t[1:])]
        leftShuffle.extend(rightShuffle)
        return leftShuffle

print(shuffle("ab","cd"))
9
JeD

aのインデックスをbと比較し、cdと比較するだけで、aのインデックスがbのインデックスより大きく、cのインデックスはdのインデックスより大きい。

def interleave(s, t):
    mystring = s + t
    return [el for el in [''.join(item) for item in permutations(mystring) if  item.index('a') < item.index('b') and item.index('c') < item.index('d')]]

デモ:

>>> from itertools import permutations
>>> s = 'ab'
>>> t = 'cd'
>>> [el for  el in [''.join(item) for item in permutations(s+t) if item.index('a') < item.index('b') and item.index('c') < item.index('d')]]
['abcd', 'acbd', 'acdb', 'cabd', 'cadb', 'cdab']
4
styvane

スポーツのためだけに

明示的な条件や述語のないソリューション

(つまり、ifキーワードなし):

from itertools import chain, repeat, permutations
from copy import deepcopy


def shuffle(*strings):
    # Treat the strings as pools from which to draw elements in order.
    # Convert the strings to lists, so that drawn items can be removed:
    pools = (list(string) for string in strings)

    # From each pool, we have to draw as many times as it has items:
    pools_to_draw_from = chain.from_iterable(
        repeat(pool, len(pool)) for pool in pools
    )

    # Because itertools.permutations treats elements as unique based on their
    # position, not on their value and because pools_to_draw_from has repeated
    # repeated items, we would get repeated permutations, if we would not
    # filter them out with `unique`.
    possible_drawing_orders = unique(permutations(pools_to_draw_from))

    # For each drawing order, we want to draw (and thus remove) items from our
    # pools. Subsequent draws within the same drawing order should get the
    # respective next item in the pool, i.e., see the modified pool. But we don't
    # want the pools to be exhausted after processing the first drawing ordering.
    #
    # Deepcopy preserves internal repetition and thus does exactly what we need.
    possible_drawing_orders = (deepcopy(pdo) for pdo in possible_drawing_orders)

    # Draw from the pools for each possible order,
    # build strings and return them in a list:
    return [''.join(_draw(p)) for p in possible_drawing_orders]


def _draw(drawing_order):
    return (pool_to_draw_from.pop(0) for pool_to_draw_from in drawing_order)

これにはヘルパー関数が必要です。

from operator import itemgetter
from itertools import groupby

def unique(iterable, key=None):
    # Other than unique_everseen from
    # https://docs.python.org/3/library/itertools.html#itertools-recipes, this
    # works for iterables of non-hashable elements, too.
    return unique_justseen(sorted(iterable, key=key), key)


def unique_justseen(iterable, key=None):
    """
    List unique elements, preserving order. Remember only the element just seen.
    """
    # from https://docs.python.org/3/library/itertools.html#itertools-recipes
    return map(next, map(itemgetter(1), groupby(iterable, key)))

一意でない順列の数が多い場合、sortedの呼び出しのため、おそらくこれはかなり非効率的です。非一意の値の一意の順列を取得する代替方法については、 一意の値を持つ順列 を参照してください。

TL; DR?

問題ない。このアプローチを煮詰めて、この嫌悪することができます。

from itertools import chain, repeat, permutations
from copy import deepcopy

def shuffle(*strings):
    return list({''.join(l.pop(0) for l in deepcopy(p)) for p in permutations(chain.from_iterable(repeat(list(s), len(s)) for s in strings))})

(以前に一意性を保証する代わりに、結果に集合内包を使用する。)

2
das-g