2012-10-11 44 views
6

La cuestión es imprimir todos los entrelazados posibles de dos cadenas dadas. Así que escribí un código de trabajo en Python que funciona de esta manera:Cómo puedo entrelazar o crear permutaciones únicas de dos cadenas (sin recurrencia)

def inter(arr1,arr2,p1,p2,arr): 
    thisarr = copy(arr) 
    if p1 == len(arr1) and p2 == len(arr2): 
     printarr(thisarr) 
    elif p1 == len(arr1): 
     thisarr.extend(arr2[p2:]) 
     printarr(thisarr) 
    elif p2 == len(arr2): 
     thisarr.extend(arr1[p1:]) 
     printarr(thisarr) 
    else: 
     thisarr.append(arr1[p1]) 
     inter(arr1,arr2,p1+1,p2,thisarr) 
     del thisarr[-1] 
     thisarr.append(arr2[p2]) 
     inter(arr1,arr2,p1,p2+1,thisarr) 
    return 

Viene en cada punto en una cadena, después durante una llamada recursiva, se considera el elemento actual como pertenecientes a la primera serie, y en el próxima llamada, que pertenece a la otra matriz. Así que si las cadenas de entrada son ab y cd, imprime abcd, acbd, cdab, cabd, etc. p1 y p2 son punteros a las matrices (porque las cadenas de Python son inmutables, que estoy usando matrices!). ¿Alguien me puede decir cuál es la complejidad de este código, y si puede mejorarse o no? He escrito un código similar para imprimir todas las combinaciones de longitud k de una matriz dada:

def kcomb(arr,i,thisarr,k): 
    thisarr = copy(thisarr) 
    j,n = len(thisarr),len(arr) 
    if n-i<k-j or j >k: 
     return 
    if j==k: 
     printarr(thisarr) 
     return 
    if i == n: 
     return 
    thisarr.append(arr[i]) 
    kcomb(arr,i+1,thisarr,k) 
    del thisarr[-1] 
    kcomb(arr,i+1,thisarr,k) 
    return 

Esto también funciona en el mismo principio. Entonces, en general, ¿cómo encontrar la complejidad de tales funciones y cómo las optimizo? ¿Es posible hacer esto por DP? Muestra de entrada-salida para el primer problema:

>>> arr1 = ['a','b','c'] 
>>> arr2 = ['d','e','f'] 
>>> inter(arr1,arr2,0,0,[]) 
abcdef 
abdcef 
abdecf 
abdefc 
adbcef 
adbecf 
adbefc 
adebcf 
adebfc 
adefbc 
dabcef 
dabecf 
dabefc 
daebcf 
daebfc 
daefbc 
deabcf 
deabfc 
deafbc 
defabc 
+0

puede por favor publicar el código para "printarr" –

+0

Sí, puedo, pero eso es trivial, simplemente toma la matriz como entrada, concatena todos los elementos en una cadena y la imprime! – SexyBeast

+1

Es difícil entender lo que estás haciendo. Puede publicar la entrada y salida esperada. – monkut

Respuesta

15

Su problema puede reducirse a la de la creación de todas las únicas permutaciones de una lista en particular. Diga A y B son las longitudes de las cadenas arr1 y arr2, respectivamente. A continuación, construir una lista como esta:

[0] * A + [1] * B 

Existe una correspondencia uno-a-uno (una biyección) de las permutaciones únicas de esta lista a todas las posibles intercalaciones de las dos cadenas arr1 y arr2. La idea es dejar que cada valor de la permutación especifique de qué cadena sacar el siguiente carácter. Aquí es una implementación de ejemplo que muestra cómo construir una intercalación de una permutación:

>>> def make_interleave(arr1, arr2, permutation): 
...  iters = [iter(arr1), iter(arr2)] 
...  return "".join(iters[i].next() for i in permutation) 
... 
>>> make_interleave("ab", "cde", [1, 0, 0, 1, 1]) 
'cabde' 

He encontrado this pregunta en la lista de correo pitón que se pregunta cómo resolver este problema de una manera eficiente. Las respuestas sugieren el uso de un algoritmo que se describe en Knuth's The Art of Computer Programming, Volume 4, Fascicle 2: Generating All Permutations. Encontré un pdf en línea del borrador here. El algoritmo también se describe en este wikipedia article.

Aquí está mi propia implementación anotada del algoritmo next_permutation, como una función generadora de python.

def unique_permutations(seq): 
    """ 
    Yield only unique permutations of seq in an efficient way. 

    A python implementation of Knuth's "Algorithm L", also known from the  
    std::next_permutation function of C++, and as the permutation algorithm 
    of Narayana Pandita. 
    """ 

    # Precalculate the indices we'll be iterating over for speed 
    i_indices = range(len(seq) - 1, -1, -1) 
    k_indices = i_indices[1:] 

    # The algorithm specifies to start with a sorted version 
    seq = sorted(seq) 

    while True: 
        yield seq 

     # Working backwards from the last-but-one index,          k 
        # we find the index of the first decrease in value.  0 0 1 0 1 1 1 0 
        for k in k_indices: 
            if seq[k] < seq[k + 1]: 
                break 
        else: 
      # Introducing the slightly unknown python for-else syntax: 
      # else is executed only if the break statement was never reached. 
      # If this is the case, seq is weakly decreasing, and we're done. 
            return 

     # Get item from sequence only once, for speed 
        k_val = seq[k] 

        # Working backwards starting with the last item,        k     i 
        # find the first one greater than the one at k    0 0 1 0 1 1 1 0 
        for i in i_indices: 
            if k_val < seq[i]: 
                break 

        # Swap them in the most efficient way 
        (seq[k], seq[i]) = (seq[i], seq[k])            #       k     i 
                                              # 0 0 1 1 1 1 0 0 

     # Reverse the part after but not       k 
     # including k, also efficiently.      0 0 1 1 0 0 1 1 
     seq[k + 1:] = seq[-1:k:-1] 

Cada rendimiento del algoritmo tiene una complejidad amortizado de O (1), de acuerdo con this pregunta, pero de acuerdo con RICI que comentó más adelante, esto es sólo el caso si todos los números son únicos, que son sin duda no en este caso.

En cualquier caso, el número de los rendimientos proporciona un límite inferior para la complejidad del tiempo, y que viene dado por

 
(A + B)!/(A! * B!) 

Luego de encontrar la complejidad en tiempo real que necesitamos para resumir la complejidad media de cada rendimiento con la complejidad de construir la cadena resultante basada en la permutación. Si multiplicamos esta suma con la fórmula anterior obtenemos la complejidad total del tiempo.

+1

¡Por favor explique el código! – SexyBeast

+0

@cupidvogel Como dije, el algoritmo se explica en el mismo blog donde está el código. –

+0

@lazyr: ese código solo se amortiza O (1) por permutaciones de secuencias de elementos únicos.Si la secuencia tiene muchas repeticiones de un valor, se convierte en (creo) O (n) (para cada permutación). (Es el ejercicio 6 en el fascículo Knuth.) – rici

0

¿El permutations trabajo para usted? O bien, ¿es esta práctica de codificación?

>>> from itertools import permutations 
>>> s1 = "ab" 
>>> s2 = "cd" 
>>> all_values = [c for c in s1 + s2] 
>>> ["".join(r) for r in permutations(all_values)] 
['abcd', 'abdc', 'acbd', 'acdb', 'adbc', 'adcb', 'bacd', 'badc', 'bcad', 'bcda', 'bdac', 'bdca', 'cabd', 'cadb', 'cbad', 'cbda', 'cdab', 'cdba', 'dabc', 'dacb', 'dbac', 'dbca', 'dcab', 'dcba'] 
+0

No, no, estoy tratando de desarrollar un código de trabajo sin recurrir a estas funciones incorporadas. Además, su salida no es correcta, en muchas de las cadenas de salida, 'b' viene antes de' a', mientras que 'a' siempre debe aparecer antes que' b'. Eso es entrelazado, no permutación! – SexyBeast

+0

Creo que aquí se requiere un subconjunto de las salidas permutadas, donde las 2 cadenas de entrada están intercaladas en orden. Entonces, abdc no es una salida válida – Dhara

+0

@Cupidvogel ¿No le interesa en absoluto una solución que use itertools? –

0

Esto es lo que creo que estamos tratando de hacer:

from itertools import product, chain 

def interleave(a, b): 
    if len(b) > len(a): 
     a, b = b, a 

    boolean = (True, False) 
    for z in range(len(a) - len(b) + 1): 
     pairs = list(zip(a[z:], b)) 
     for vector in product(*[boolean] * max(map(len, (a, b)))): 
      permutation = (pair if i else reversed(pair) 
          for i, pair in zip(vector, pairs)) 
      yield a[:z] + ''.join(chain.from_iterable(permutation)) 
+0

¿Puede echar un vistazo a mi solución y contarme la complejidad? Estoy buscando una solución no recursiva sin usar 'itertools'. – SexyBeast

+0

Bueno, podría modificar esto fácilmente para no usar itertools. 'producto' en este caso simplemente itera sobre todos los valores entre' 0' y '2^len (a)' en binario, probablemente podrías hacer 'range (2^len (a)):' y convertir a binario. Sin embargo, me acabo de dar cuenta de que este código deja de lado ciertos intercalados. Sin embargo, se puede modificar para incluirlos fácilmente. Quizás pueda volver a visitar esto en unas pocas horas. –

1

bien, después de un trabajo, y utilizar el consejo de otras respuestas. principalmente lazyr. (Y ahora se han convertido en una clase) los __all_perms es de: https://stackoverflow.com/a/104436/1561176

class Interleave(): 

    def __init__(self, A, B): 
     self.A = A 
     self.B = B 
     self.results = list(self.__interleave()) 

    # from https://stackoverflow.com/a/104436/1561176 
    def __all_perms(self, elements): 
     if len(elements) <=1: 
      yield elements 
     else: 
      for perm in self.__all_perms(elements[1:]): 
       for i in range(len(elements)): 
        #nb elements[0:1] works in both string and list contexts 
        yield perm[:i] + elements[0:1] + perm[i:] 

    def __sequences(self): 
     return list(sorted(set(
      ["".join(x) for x in self.__all_perms(['a'] * len(self.A) + ['b'] * len(self.B))]))) 

    def __interleave(self): 
     for sequence in self.__sequences(): 
      result = "" 
      a = 0 
      b = 0 
      for item in sequence: 
       if item == 'a': 
        result+=self.A[a] 
        a+=1 
       else: 
        result+=self.B[b] 
        b+=1 
      yield result 

    def __str__(self): 
     return str(self.results) 

    def __repr__(self): 
     return repr(self.results) 

y aquí es el uso de:

>>> A = ['a', 'b', 'c'] 
>>> B = ['d', 'e', 'f'] 
>>> Interleave(A, B) 
['abcdef', 'abdcef', 'abdecf', 'abdefc', 'adbcef', 'adbecf', 'adbefc', 'adebcf', 'adebfc', 'adefbc', 'dabcef', 'dabecf', 'dabefc', 'daebcf', 'daebfc', 'daefbc', 'deabcf', 'deabfc', 'deafbc', 'defabc'] 

También, puede acceder a los miembros de la Clase como:

>>> inter = Interleave(A, B) 
>>> inter.results 
['abcdef', 'abdcef', 'abdecf', 'abdefc', 'adbcef', 'adbecf', 'adbefc', 'adebcf', 'adebfc', 'adefbc', 'dabcef', 'dabecf', 'dabefc', 'daebcf', 'daebfc', 'daefbc', 'deabcf', 'deabfc', 'deafbc', 'defabc'] 
>>> inter.A 
['a', 'b', 'c'] 
>>> inter.B 
['d', 'e', 'f'] 
+1

¿Puede explicar el código, así como su complejidad? – SexyBeast

+0

@Cupidvogel bien, el 'all_perms()' es de una respuesta: http://stackoverflow.com/a/104436/1561176 y entonces no comenzaré a discutirlo aquí ya que se discute allí. '_sequences()' simplemente llama a 'all_perms()' y luego convierte las listas resultantes en cadenas, las convierte en únicas y las ordena, antes de convertirlas a una lista y enviarlas al 'interleave()' que lee las listas y simplemente agrega valores de 'A' o 'B' según el orden en que se le dé a partir de la secuencia. –

+2

Tenga en cuenta que este algoritmo genera * todas * permutaciones y luego filtra los duplicados, a diferencia del algoritmo en mi respuesta. Para dos cadenas de longitud 10, por ejemplo, '__all_perms' produce 2432902008176640000 veces, mientras que' next_permutation' solo produce las 184756 veces necesarias. –

Cuestiones relacionadas