Orden inverso de palabras en una cadena in situ


17

La tarea

  • Se le da una cadena mutable que coincide [a-z]+( [a-z]+)*.
  • Debe mutarlo en la cadena que contiene las mismas palabras, pero en orden inverso, de modo que "hola a todos" se convierta en "a todos allí hola".
  • No se le permite usar más que una cantidad constante de memoria adicional (por lo que no debe copiar la cadena completa ni ninguna palabra completa en un búfer que acaba de asignar).
  • No hay limitaciones de tiempo. Ser irremediablemente ineficiente no dañará su puntaje.
  • Si su idioma de elección no permite la mutación de cadenas, las matrices de caracteres son un sustituto aceptable.

Tu puntuación

  • Su puntaje se cuenta únicamente en la cantidad de asignaciones que realiza para encadenar elementos (los puntajes pequeños son mejores). Si usa una función de biblioteca que escribe en la cadena, sus escrituras también cuentan.
  • Suponga que el número de asignaciones que necesita para la entrada s es n (s) . Entonces su puntaje es el máximo (pedante, supremum) sobre todas las entradas s (que coinciden con la expresión regular especificada anteriormente) de n (s) / longitud (s) . Si no puede calcular esto con precisión, puede usar el límite superior más bajo que pueda probar.
  • Puede romper un empate si puede probar que su algoritmo usa menos tareas asintóticamente (esto puede suceder incluso si tiene el mismo puntaje, ver más abajo). Si no puede hacer esto, puede romper un empate al mostrar que usa menos memoria adicional. Sin embargo, la primera condición de desempate siempre tiene prioridad.
  • Para algunas entradas, cada carácter debe cambiar, por lo que no es posible obtener una puntuación inferior a 1.
  • Puedo pensar en un algoritmo simple con puntaje 2 (pero no lo estoy ingresando).

Notas sobre suprema y lazos

  • Un supremum de un conjunto de números es el número más pequeño que no es más pequeño que ninguno de ellos. Esto es muy parecido al máximo de un conjunto, excepto que algunos conjuntos infinitos como {2/3, 3/4, 4/5, 5/6, ...} no tienen un elemento máximo único, pero aún tendrán un supremum, en este caso 1.
  • Si logra "guardar" solo un número constante de asignaciones sobre mi algoritmo de puntaje 2 (digamos), su puntaje seguirá siendo 2, porque se acercará arbitrariamente a 2 cuanto mayor sea su entrada. Sin embargo, ganas en el desempate si se trata de eso.

1
Estaría un poco triste si todo se redujera a envíos de puntaje de desempate 2 en su uso de memoria. Sobre todo publiqué esta pregunta preguntándome si alguien lograría anotar menos de 2.
Ben Millwood

1
No tengo una prueba formal, pero mi intuición me dice que con la restricción de espacio constante, es imposible hacerlo mejor que 2. Si solo cuenta las declaraciones de variables para el espacio, podría hacer trampa y hacer una función recursiva. pero eso solo disfraza el little-omega(1)espacio poniéndolo en la pila.
wrongu

2
@bacchusbeale sí, pero es una memoria extra constante.
Martin Ender

44
Si aplica estrictamente las reglas, entonces es imposible escribir dicho programa. La cadena puede tener una longitud arbitraria y necesitará al menos algún tipo de variable que almacene un índice. Entonces, solo tengo que hacer que la cadena sea lo suficientemente larga como para exceder los límites de su variable. Para almacenar con éxito al menos un índice, la memoria necesaria crecerá con la longitud de la cadena.
IchBinKeinBaum

3
@IchBinKeinBaum tiene razón, es imposible realizar esta tarea con O(1)espacio adicional. Necesita O(log n)espacio para almacenar una posición de índice, ya que un entero de k bits solo puede almacenar en ellos cadenas de una longitud de hasta 2^k. Limitar la longitud de las cadenas hace que el desafío sea bastante inútil, ya que cada algoritmo requeriría O(1)espacio de esta manera.
Dennis

Respuestas:


4

Python, Puntuación: 2 1.5 1.25

Esta es la combinación directa entre la respuesta de primo y mi respuesta. ¡Así que los créditos también para él!

La prueba aún está en progreso, ¡pero aquí está el código para jugar! Si puede encontrar un contraejemplo de puntaje mayor a 1.25 (o si hay un error), ¡hágamelo saber!

Actualmente el peor de los casos es:

aa ... aa dcb ... cbd

donde hay exactamente n de cada una de las letras "a", "b", "c" y "" (espacio), y exactamente dos "d" s. La longitud de la cadena es 4n + 2 y el número de asignaciones es 5n + 2 , dando una puntuación de 5/4 = 1.25 .

El algoritmo funciona en dos pasos:

  1. Encuentra ktal que string[k]y string[n-1-k]son límites de palabras
  2. Ejecute cualquier algoritmo de inversión de palabras string[:k]+string[n-1-k:](es decir, concatenación del primer ky último carácter k) con una pequeña modificación.

donde nes la longitud de la cadena.

La mejora que brinda este algoritmo proviene de la "pequeña modificación" en el Paso 2. Es básicamente el conocimiento de que en la cadena concatenada, los caracteres en la posición ky los k+1límites de las palabras (lo que significa que son espacios o el primer / último carácter de una palabra), y así podemos reemplazar directamente los caracteres en posición ky k+1con el carácter correspondiente en la cadena final, guardando algunas asignaciones. Esto elimina el peor caso del algoritmo de inversión de palabras del host

Hay casos en los que no podemos encontrarlos k, en ese caso, simplemente ejecutamos el "algoritmo de inversión de cualquier palabra" en toda la cadena.

El código es largo para manejar estos cuatro casos al ejecutar el algoritmo de inversión de palabras en la cadena "concatenada":

  1. Cuando kno se encuentra ( f_long = -2)
  2. Cuando string[k] != ' ' and string[n-1-k] != ' '( f_long = 0)
  3. Cuando string[k] != ' ' and string[n-1-k] == ' '( f_long = 1)
  4. Cuando string[k] == ' ' and string[n-1-k] != ' '( f_long = -1)

Estoy seguro de que el código puede acortarse. Actualmente es largo porque al principio no tenía una imagen clara de todo el algoritmo. Estoy seguro de que uno puede diseñarlo para que se represente en un código más corto =)

Ejecución de muestra (primero es mío, segundo es primo):

Introduzca la cadena: a bc def ghij
"ghij def bc a": 9, 13, 0.692
"ghij def bc a": 9, 13, 0.692
Introduzca la cadena: ab cdefghijklmnopqrstuvw xyz
"zyxwvutsrqponmlkjihgf edc ab": 50, 50, 1.000
"zyxwvutsrqponmlkjihgf edc ab": 51, 50, 1.020
Introduzca la cadena: abcdefg hijklmnopqrstuvwx
"hijklmnopqrstuvwx gfedcb a": 38, 31, 1.226
"hijklmnopqrstuvwx gfedcb a": 38, 31, 1.226
Introduzca la cadena: a bc de fg hi jk lm no pq rs tu vw xy zc
"zc xy vw tu rs pq no lm jk hi fg de bc a": 46, 40, 1.150
"zc xy vw tu rs pq no lm jk hi fg de bc a": 53, 40, 1.325
Introduzca cadena: aaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaa AAAAAAAAAAAAAAAA dcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbd
"Dcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbd aaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaa a": 502, 402, 1.249
"Dcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbd aaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaa a": 502, 402, 1.249

Puede ver que el puntaje es casi el mismo, excepto por el peor caso del algoritmo de inversión de palabras del host en el tercer ejemplo, para el cual mi enfoque arroja un puntaje menor a 1.25

DEBUG = False

def find_new_idx(string, pos, char, f_start, f_end, b_start, b_end, f_long):
    if DEBUG: print 'Finding new idx for s[%d] (%s)' % (pos, char)
    if f_long == 0:
        f_limit = f_end-1
        b_limit = b_start
    elif f_long == 1:
        f_limit = f_end-1
        b_limit = b_start+1
    elif f_long == -1:
        f_limit = f_end-2
        b_limit = b_start
    elif f_long == -2:
        f_limit = f_end
        b_limit = b_start

    if (f_start <= pos < f_limit or b_limit < pos < b_end) and char == ' ':
        word_start = pos
        word_end = pos+1
    else:
        if pos < f_limit+1:
            word_start = f_start
            if DEBUG: print 'Assigned word_start from f_start (%d)' % f_start
        elif pos == f_limit+1:
            word_start = f_limit+1
            if DEBUG: print 'Assigned word_start from f_limit+1 (%d)' % (f_limit+1)
        elif b_limit <= pos:
            word_start = b_limit
            if DEBUG: print 'Assigned word_start from b_limit (%d)' % b_limit
        elif b_limit-1 == pos:
            word_start = b_limit-1
            if DEBUG: print 'Assigned word_start from b_limit-1 (%d)' % (b_limit-1)
        i = pos
        while f_start <= i <= f_limit or 0 < b_limit <= i < b_end:
            if i==f_limit or i==b_limit:
                cur_char = 'a'
            elif i!=pos:
                cur_char = string[i]
            else:
                cur_char = char
            if cur_char == ' ':
                word_start = i+1
                if DEBUG: print 'Assigned word_start from loop'
                break
            i -= 1

        if b_limit <= pos:
            word_end = b_end
            if DEBUG: print 'Assigned word_end from b_end (%d)' % b_end
        elif b_limit-1 == pos:
            word_end = b_limit
            if DEBUG: print 'Assigned word_end from b_limit (%d)' % (b_limit)
        elif pos < f_limit+1:
            word_end = f_limit+1
            if DEBUG: print 'Assigned word_end from f_limit+1 (%d)' % (f_limit+1)
        elif pos == f_limit+1:
            word_end = f_limit+2
            if DEBUG: print 'Assigned word_end from f_limit+2 (%d)' % (f_limit+2)
        i = pos
        while f_start <= i <= f_limit or 0 < b_limit <= i < b_end:
            if i==f_limit or i==b_limit:
                cur_char = 'a'
            elif i!=pos:
                cur_char = string[i]
            else:
                cur_char = char
            if cur_char == ' ':
                word_end = i
                if DEBUG: print 'Assigned word_end from loop'
                break
            i += 1
    if DEBUG: print 'start, end: %d, %d' % (word_start, word_end)
    word_len = word_end - word_start
    offset = word_start-f_start
    result = (b_end-offset-(word_end-pos)) % b_end
    if string[result] == ' ' and (b_start == -1 or result not in {f_end-1, b_start}):
        return len(string)-1-result
    else:
        return result

def process_loop(string, start_idx, f_start, f_end, b_start, b_end=-1, f_long=-2, dry_run=False):
    assignments = 0
    pos = start_idx
    tmp = string[pos]
    processed_something = False
    count = 0
    while pos != start_idx or not processed_something:
        count += 1
        if DEBUG and count > 20:
            print '>>>>>Break!<<<<<'
            break
        new_pos = find_new_idx(string, pos, tmp, f_start, f_end, b_start, b_end, f_long)
        if DEBUG:
            if dry_run:
                print 'Test:',
            else:
                print '\t',
            print 'New idx for s[%d] (%s): %d (%s)' % (pos, tmp, new_pos, string[new_pos])
        if dry_run:
            tmp = string[new_pos]
            if new_pos == dry_run:
                return True
        elif pos == new_pos:
            break
        elif tmp == string[new_pos]:
            pass
        else:
            tmp, string[new_pos] = string[new_pos], tmp
            assignments += 1
        pos = new_pos
        processed_something = True
    if dry_run:
        return False
    return assignments

def reverse(string, f_start, f_end, b_start, b_end=-1, f_long=-2):
    if DEBUG: print 'reverse: %d %d %d %d %d' % (f_start, f_end, b_start, b_end, f_long)
    if DEBUG: print
    if DEBUG: print ''.join(string)
    assignments = 0
    n = len(string)
    if b_start == -1:
        for i in range(f_start, f_end):
            if string[i] == ' ':
                continue
            if DEBUG: print 'Starting from i=%d' % i
            if any(process_loop(string, j, f_start, f_end, -1, f_end, dry_run=i) for j in range(f_start, i) if string[j] != ' '):
                continue
            if DEBUG:
                print
                print 'Finished test'
            assignments += process_loop(string, i, f_start, f_end, -1, f_end)
            if DEBUG: print
            if DEBUG: print ''.join(string)
        for i in range(f_start, (f_start+f_end-1)/2):
            if (string[i] == ' ' and string[n-1-i] != ' ') or (string[i] != ' ' and string[n-1-i] == ' '):
                string[i], string[n-1-i] = string[n-1-i], string[i]
                assignments += 2
    else:
        for i in range(f_start, f_end)+range(b_start, b_end):
            if string[i] == ' ' and i not in {f_end-1, b_start}:
                continue
            if DEBUG: print 'Starting from i=%d' % i
            if any(process_loop(string, j, f_start, f_end, b_start, b_end, f_long, i) for j in range(f_start, f_end)+range(b_start, b_end) if j<i and (string[j] != ' ' or j in {f_end-1, b_start})):
                continue
            assignments += process_loop(string, i, f_start, f_end, b_start, b_end, f_long)
            if DEBUG: print
            if DEBUG: print ''.join(string)
        for i in range(f_start, f_end-1):
            if (string[i] == ' ' and string[n-1-i] != ' ') or (string[i] != ' ' and string[n-1-i] == ' '):
                string[i], string[n-1-i] = string[n-1-i], string[i]
                assignments += 2
    return assignments

class SuperList(list):
    def index(self, value, start_idx=0):
        try:
            return self[:].index(value, start_idx)
        except ValueError:
            return -1

    def rindex(self, value, end_idx=-1):
        end_idx = end_idx % (len(self)+1)
        try:
            result = end_idx - self[end_idx-1::-1].index(value) - 1
        except ValueError:
            return -1
        return result

def min_reverse(string):
    assignments = 0
    lower = 0
    upper = len(string)
    while lower < upper:
        front = string.index(' ', lower) % (upper+1)
        back = string.rindex(' ', upper)
        while abs(front-lower - (upper-1-back)) > 1 and front < back:
            if front-lower < (upper-1-back):
                front = string.index(' ', front+1) % (upper+1)
            else:
                back = string.rindex(' ', back)
            if DEBUG: print lower, front, back, upper
        if front > back:
            break
        if DEBUG: print lower, front, back, upper
        if abs(front-lower - (upper-1-back)) > 1:
            assignments += reverse(string, lower, upper, -1)
            lower = upper
        elif front-lower < (upper-1-back):
            assignments += reverse(string, lower, front+1, back+1, upper, -1)
            lower = front+1
            upper = back+1
        elif front-lower > (upper-1-back):
            assignments += reverse(string, lower, front, back, upper, 1)
            lower = front
            upper = back
        else:
            assignments += reverse(string, lower, front, back+1, upper, 0)
            lower = front+1
            upper = back
    return assignments

def minier_find_new_idx(string, pos, char):
    n = len(string)
    try:
        word_start = pos - next(i for i, char in enumerate(string[pos::-1]) if char == ' ') + 1
    except:
        word_start = 0
    try:
        word_end = pos + next(i for i, char in enumerate(string[pos:]) if char == ' ')
    except:
        word_end = n
    word_len = word_end - word_start
    offset = word_start
    result = (n-offset-(word_end-pos))%n
    if string[result] == ' ':
        return n-result-1
    else:
        return result

def minier_process_loop(string, start_idx, dry_run=False):
    assignments = 0
    pos = start_idx
    tmp = string[pos]
    processed_something = False
    while pos != start_idx or not processed_something:
        new_pos = minier_find_new_idx(string, pos, tmp)
        #print 'New idx for s[%d] (%s): %d (%s)' % (pos, tmp, new_pos, string[new_pos])
        if pos == new_pos:
            break
        elif dry_run:
            tmp = string[new_pos]
            if new_pos == dry_run:
                return True
        elif tmp == string[new_pos]:
            pass
        else:
            tmp, string[new_pos] = string[new_pos], tmp
            assignments += 1
        pos = new_pos
        processed_something = True
    if dry_run:
        return False
    return assignments

def minier_reverse(string):
    assignments = 0
    for i in range(len(string)):
        if string[i] == ' ':
            continue
        if any(minier_process_loop(string, j, dry_run=i) for j in range(i) if string[j] != ' '):
            continue
        assignments += minier_process_loop(string, i)
    n = len(string)
    for i in range(n/2):
        if string[i] == ' ' and string[n-i-1] != ' ':
            string[i], string[n-i-1] = string[n-i-1], string[i]
            assignments += 2
        elif string[n-i-1] == ' ' and string[i] != ' ':
            string[i], string[n-i-1] = string[n-i-1], string[i]
            assignments += 2
    return assignments

def main():
    while True:
        str_input = raw_input('Enter string: ')
        string = SuperList(str_input)
        result = min_reverse(string)
        n = len(string)
        print '"%s": %d, %d, %.3f' % (''.join(string), result, n, 1.0*result/n)
        string = SuperList(str_input)
        result2 = minier_reverse(string)
        print '"%s": %d, %d, %.3f' % (''.join(string), result2, n, 1.0*result2/n)

if __name__ == '__main__':
    main()

Python, Puntuación: 1.5

El número exacto de tareas puede ser aproximado por la fórmula:

n <= 1.5 * longitud (cadena)

siendo el peor de los casos:

abcdefghi jklmnopqrstuvwxyzzz

con 55 asignaciones en cuerda con longitud 37.

La idea es similar a la anterior, es solo que en esta versión intenté encontrar el prefijo y el sufijo en los límites de las palabras con una diferencia de longitud como máximo 1. Luego ejecuté mi algoritmo anterior en ese prefijo y sufijo (imagínelos como concatenados) . Luego continúe en la parte no procesada.

Por ejemplo, para el peor de los casos anteriores:

ab | ab | C

primero haremos una inversión de palabras en "ab" y "c" (4 asignaciones) para ser:

c | ab | ab

Sabemos que en el límite solía ser espacio (hay muchos casos que deben manejarse, pero puede hacerlo), por lo que no necesitamos codificar el espacio en el límite, esta es la principal mejora del algoritmo anterior .

Luego, finalmente corremos en los cuatro personajes del medio para obtener:

cba ab

en total 8 asignaciones, lo óptimo para este caso, ya que los 8 caracteres cambiaron.

Esto elimina el peor caso en el algoritmo anterior ya que se elimina el peor caso en el algoritmo anterior.

Vea un ejemplo de ejecución (y una comparación con la respuesta de @ primo: la suya es la segunda línea):

Introduzca cadena: puedo hacer cualquier cosa
"Todo lo que puedo hacer": 20, 17
"Todo lo que puedo hacer": 17, 17
Introduzca la cadena: abcdef ghijklmnopqrs
"ghijklmnopqrs fedcb a": 37, 25
"ghijklmnopqrs fedcb a": 31, 25
Introduzca la cadena: abcdef ghijklmnopqrst
"ghijklmnopqrst fedcb a": 38, 26
"ghijklmnopqrst fedcb a": 32, 26
Introduzca la cadena: abcdefghi jklmnozzzzzzzzzzzzzzzzz
"jklmnozzzzzzzzzzzzzzzzz ihgfedcb a": 59, 41
"jklmnozzzzzzzzzzzzzzzzz ihgfedcb a": 45, 41
Introduzca la cadena: abcdefghi jklmnopqrstuvwxyzzz
"jklmnopqrstuvwxyzzz ihgfedcb a": 55, 37
"jklmnopqrstuvwxyzzz ihgfedcb a": 45, 37
Introduzca la cadena: ab ababababababac
"cababababababa ab": 30, 30
"cababababababa ab": 31, 30
Introduzca la cadena: ab abababababababc
"cbababababababa ab": 32, 32
"cbababababababa ab": 33, 32
Introduzca la cadena: abc d abc
"abc d abc": 0, 9
"abc d abc": 0, 9
Introduzca la cadena: abc dca
"acd abc": 6, 9
"acd abc": 4, 9
Introduzca la cadena: abc ababababababc
"cbabababababa abc": 7, 29
"cbabababababa abc": 5, 29

la respuesta de primo es generalmente mejor, aunque en algunos casos puedo tener 1 punto de ventaja =)

También su código es mucho más corto que el mío, jaja.

DEBUG = False

def find_new_idx(string, pos, char, f_start, f_end, b_start, b_end, f_long):
    if DEBUG: print 'Finding new idx for s[%d] (%s)' % (pos, char)
    if f_long == 0:
        f_limit = f_end-1
        b_limit = b_start
    elif f_long == 1:
        f_limit = f_end-1
        b_limit = b_start+1
    elif f_long == -1:
        f_limit = f_end-2
        b_limit = b_start
    elif f_long == -2:
        f_limit = f_end
        b_limit = b_start

    if (f_start <= pos < f_limit or b_limit < pos < b_end) and (char == ' ' or char.isupper()):
        word_start = pos
        word_end = pos+1
    else:
        if pos < f_limit+1:
            word_start = f_start
            if DEBUG: print 'Assigned word_start from f_start (%d)' % f_start
        elif pos == f_limit+1:
            word_start = f_limit+1
            if DEBUG: print 'Assigned word_start from f_limit+1 (%d)' % (f_limit+1)
        elif b_limit <= pos:
            word_start = b_limit
            if DEBUG: print 'Assigned word_start from b_limit (%d)' % b_limit
        elif b_limit-1 == pos:
            word_start = b_limit-1
            if DEBUG: print 'Assigned word_start from b_limit-1 (%d)' % (b_limit-1)
        i = pos
        if not (i < f_limit and b_limit < i):
            i -= 1
        while f_start <= i < f_limit or 0 < b_limit < i < b_end:
            if i!=pos:
                cur_char = string[i]
            else:
                cur_char = char
            if cur_char == ' ' or cur_char.isupper():
                word_start = i+1
                if DEBUG: print 'Assigned word_start from loop'
                break
            i -= 1

        if b_limit <= pos:
            word_end = b_end
            if DEBUG: print 'Assigned word_end from b_end (%d)' % b_end
        elif b_limit-1 == pos:
            word_end = b_limit
            if DEBUG: print 'Assigned word_end from b_limit (%d)' % (b_limit)
        elif pos < f_limit+1:
            word_end = f_limit+1
            if DEBUG: print 'Assigned word_end from f_limit+1 (%d)' % (f_limit+1)
        elif pos == f_limit+1:
            word_end = f_limit+2
            if DEBUG: print 'Assigned word_end from f_limit+2 (%d)' % (f_limit+2)
        i = pos
        if not (i < f_limit and b_limit < i):
            i += 1
        while f_start <= i < f_limit or 0 < b_limit < i < b_end:
            if i!=pos:
                cur_char = string[i]
            else:
                cur_char = char
            if cur_char == ' ' or cur_char.isupper():
                word_end = i
                if DEBUG: print 'Assigned word_end from loop'
                break
            i += 1
    if DEBUG: print 'start, end: %d, %d' % (word_start, word_end)
    word_len = word_end - word_start
    offset = word_start-f_start
    return (b_end-offset-(word_end-pos)) % b_end

def process_loop(string, start_idx, f_start, f_end, b_start, b_end=-1, f_long=-2, dry_run=False):
    assignments = 0
    pos = start_idx
    tmp = string[pos]
    processed_something = False
    count = 0
    while pos != start_idx or not processed_something:
        count += 1
        if count > 20:
            if DEBUG: print 'Break!'
            break
        new_pos = find_new_idx(string, pos, tmp, f_start, f_end, b_start, b_end, f_long)
        #if dry_run:
        #    if DEBUG: print 'Test:',
        if DEBUG: print 'New idx for s[%d] (%s): %d (%s)' % (pos, tmp, new_pos, string[new_pos])
        if pos == new_pos:
            break
        elif dry_run:
            tmp = string[new_pos]
            if new_pos == dry_run:
                return True
        elif tmp == string[new_pos]:
            pass
        elif tmp == ' ':
            if b_start!=-1 and new_pos in {f_end-1, b_start}:
                tmp, string[new_pos] = string[new_pos], tmp
            else:
                tmp, string[new_pos] = string[new_pos], '@'
            assignments += 1
        elif string[new_pos] == ' ':
            if b_start!=-1 and new_pos in {f_end-1, b_start}:
                tmp, string[new_pos] = string[new_pos], tmp
            else:
                tmp, string[new_pos] = string[new_pos], tmp.upper()
            assignments += 1
        else:
            tmp, string[new_pos] = string[new_pos], tmp
            assignments += 1
        pos = new_pos
        processed_something = True
    if dry_run:
        return False
    return assignments

def reverse(string, f_start, f_end, b_start, b_end=-1, f_long=-2):
    if DEBUG: print 'reverse: %d %d %d %d %d' % (f_start, f_end, b_start, b_end, f_long)
    if DEBUG: print
    if DEBUG: print ''.join(string)
    assignments = 0
    if b_start == -1:
        for i in range(f_start, (f_start+f_end)/2):
            if DEBUG: print 'Starting from i=%d' % i
            if any(process_loop(string, j, f_start, f_end, -1, f_end, dry_run=i) for j in range(f_start, i)):
                continue
            assignments += process_loop(string, i, f_start, f_end, -1, f_end)
            if DEBUG: print
            if DEBUG: print ''.join(string)
    else:
        for i in range(f_start, f_end):
            if DEBUG: print 'Starting from i=%d' % i
            if any(process_loop(string, j, f_start, f_end, b_start, b_end, f_long, i) for j in range(f_start, i)):
                continue
            assignments += process_loop(string, i, f_start, f_end, b_start, b_end, f_long)
            if DEBUG: print
            if DEBUG: print ''.join(string)
    for i in range(len(string)):
        if string[i] == '@':
            string[i] = ' '
            assignments += 1
        if string[i].isupper():
            string[i] = string[i].lower()
            assignments += 1
    return assignments

class SuperList(list):
    def index(self, value, start_idx=0):
        try:
            return self[:].index(value, start_idx)
        except ValueError:
            return -1

    def rindex(self, value, end_idx=-1):
        end_idx = end_idx % (len(self)+1)
        try:
            result = end_idx - self[end_idx-1::-1].index(value) - 1
        except ValueError:
            return -1
        return result

def min_reverse(string):
    # My algorithm
    assignments = 0
    lower = 0
    upper = len(string)
    while lower < upper:
        front = string.index(' ', lower) % (upper+1)
        back = string.rindex(' ', upper)
        while abs(front-lower - (upper-1-back)) > 1 and front < back:
            if front-lower < (upper-1-back):
                front = string.index(' ', front+1) % (upper+1)
            else:
                back = string.rindex(' ', back)
            if DEBUG: print lower, front, back, upper
        if front > back:
            break
        if DEBUG: print lower, front, back, upper
        if abs(front-lower - (upper-1-back)) > 1:
            assignments += reverse(string, lower, upper, -1)
            lower = upper
        elif front-lower < (upper-1-back):
            assignments += reverse(string, lower, front+1, back+1, upper, -1)
            lower = front+1
            upper = back+1
        elif front-lower > (upper-1-back):
            assignments += reverse(string, lower, front, back, upper, 1)
            lower = front
            upper = back
        else:
            assignments += reverse(string, lower, front, back+1, upper, 0)
            lower = front+1
            upper = back
    return assignments

def minier_find_new_idx(string, pos, char):
    n = len(string)
    try:
        word_start = pos - next(i for i, char in enumerate(string[pos::-1]) if char == ' ') + 1
    except:
        word_start = 0
    try:
        word_end = pos + next(i for i, char in enumerate(string[pos:]) if char == ' ')
    except:
        word_end = n
    word_len = word_end - word_start
    offset = word_start
    result = (n-offset-(word_end-pos))%n
    if string[result] == ' ':
        return n-result-1
    else:
        return result

def minier_process_loop(string, start_idx, dry_run=False):
    assignments = 0
    pos = start_idx
    tmp = string[pos]
    processed_something = False
    while pos != start_idx or not processed_something:
        new_pos = minier_find_new_idx(string, pos, tmp)
        #print 'New idx for s[%d] (%s): %d (%s)' % (pos, tmp, new_pos, string[new_pos])
        if pos == new_pos:
            break
        elif dry_run:
            tmp = string[new_pos]
            if new_pos == dry_run:
                return True
        elif tmp == string[new_pos]:
            pass
        else:
            tmp, string[new_pos] = string[new_pos], tmp
            assignments += 1
        pos = new_pos
        processed_something = True
    if dry_run:
        return False
    return assignments

def minier_reverse(string):
    # primo's answer for comparison
    assignments = 0
    for i in range(len(string)):
        if string[i] == ' ':
            continue
        if any(minier_process_loop(string, j, dry_run=i) for j in range(i) if string[j] != ' '):
            continue
        assignments += minier_process_loop(string, i)
    n = len(string)
    for i in range(n/2):
        if string[i] == ' ' and string[n-i-1] != ' ':
            string[i], string[n-i-1] = string[n-i-1], string[i]
            assignments += 2
        elif string[n-i-1] == ' ' and string[i] != ' ':
            string[i], string[n-i-1] = string[n-i-1], string[i]
            assignments += 2
    return assignments

def main():
    while True:
        str_input = raw_input('Enter string: ')
        string = SuperList(str_input)
        result = min_reverse(string)
        print '"%s": %d, %d' % (''.join(string), result, len(string))
        string = SuperList(str_input)
        result2 = minier_reverse(string)
        print '"%s": %d, %d' % (''.join(string), result2, len(string))

if __name__ == '__main__':
    main()

Python, Puntuación: asintóticamente 2, en caso normal mucho menos

código antiguo eliminado debido a restricciones de espacio

La idea es iterar a través de cada índice, y para cada índice i, que toma el carácter, calcular la nueva posición j, memorizar el carácter en la posición j, asigne el carácter en ia j, y repita con carácter en el índice j. Como necesitamos la información de espacio para calcular la nueva posición, codifico el espacio antiguo como la versión en mayúscula de la nueva letra y el nuevo espacio como '@'.


Si puede reducir el número de palabras en el peor de los casos en términos de longitud de la cadena (por ejemplo, al length(string)/3forzar que cada palabra en el peor de los casos tenga al menos una longitud 2 de alguna manera), entonces la puntuación será menor que 2 (en el ejemplo anterior será 1.67)
solo

1
Agregué un contador de intercambio al mío; el tuyo realmente vence al mío en el peor de los casos (pero no en el caso general). Necesito encontrar una manera de arreglar eso;)
primo

Línea 127: if any(process_loop(...) for j in range(...))¿No deberían contarse las asignaciones de estos bucles de proceso?
primo

Eso no hace ninguna tarea. Si ve, el dry_runparámetro se establece en un valor distinto de cero (el valor en i). Dentro de process_loop, si dry_runno es cero, no hará ninguna asignación.
justo

1
Creo que tengo una mejor imagen ahora. En esencia, se combinan dos métodos distintos con un comportamiento diferente en el peor de los casos, para eliminar el peor de los casos. Me gusta. Creo que, en general, este puede ser el mejor enfoque, aunque parece probable que se puedan combinar dos (o más) métodos completamente diferentes para reducir aún más el peor de los casos.
primo

14

Perl, puntaje 1.3̅

Para cada carácter no espacial se realiza una asignación, y para cada carácter espacial dos asignaciones. Debido a que los caracteres de espacio no pueden sumar más de la mitad del número total de caracteres, el puntaje del peor de los casos es 1.5 .

El algoritmo no ha cambiado, pero puedo demostrar un límite superior inferior. Hagamos dos observaciones:

  1. No es necesario intercambiar los espacios directamente frente a los espacios.
  2. Las palabras de un solo carácter directamente a través de los espacios no se intercambian durante la fase principal, sino solo una vez al final.

Entonces se puede ver que el 'peor caso' teórico con 1/2 espacios asintóticamente no es el peor de los casos: ab c d e f g h i ...

$ echo ab c d e f g h i j k l m n o p q r s t u v w x y z|perl reverse-inplace.pl
z y x w v u t s r q p o n m l k j i h g f e d c ab
swaps: 51; len: 50
ratio: 1.02

De hecho, es un caso bastante bueno.

Para evitar la observación uno y dos anteriores, cada palabra de un carácter necesitaría reposicionarse en el medio de una palabra de tres o más caracteres de largo. Esto sugeriría el peor de los casos que contiene asintóticamente 1/3 de espacios:a bcd a bcd a ... bc

$ echo a bcd a bcd a bcd a bcd a bcd a bc|perl reverse-inplace.pl
bc a bcd a bcd a bcd a bcd a bcd a
swaps: 45; len: 34
ratio: 1.32352941176471

O, de manera equivalente, solo palabras de dos caracteres: a bc de fg hi jk ...

$ echo a bc de fg hi jk lm no pq rs tu vx xy|perl reverse-inplace.pl
xy vx tu rs pq no lm jk hi fg de bc a
swaps: 49; len: 37
ratio: 1.32432432432432

Como el peor de los casos contiene 1/3 de espacios asintóticamente, el puntaje del peor de los casos se convierte en 1.3̅ .

#!perl -l
use warnings;

$words = <>;
chomp($words);
$len = length($words);
$words .= ' ';
$spaces = 0;
# iterate over the string, count the spaces
$spaces++ while $words =~ m/ /g;

$origin = 0;
$o = vec($words, $origin, 8);
$cycle_begin = $origin;
$swaps = 0;

# this possibly terinates one iteration early,
# if the last char is a one-cycle (i.e. moves to its current location)
# one-cycles previous to the last are iterated, but not swapped.
while ($i++ < $len - $spaces || !$was_cycle) {
  $w_start = rindex($words, ' ', $origin);
  $w_end = index($words, ' ', $origin);
  $pos = ($origin - $w_start) - 1;
  $target = $len - ($w_end - $pos);
  $t = vec($words, $target, 8);

  if ($t == 32) {
    $target = $len - $target - 1;
    $t = vec($words, $target, 8);
  }

  # char is already correct, possibly a one-cycle
  if ($t != $o) {
    $swaps += 1;
    vec($words, $target, 8) = $o;
  }

  $origin = $target;
  $o = $t;
  if ($origin == $cycle_begin) {
    if ($i < $len - $spaces) {
      # backtrack through everything we've done up to this point
      # to find the next unswapped char ...seriously.
      $origin += 1;
      if (vec($words, $origin, 8) == 32) {
        $origin += 1;
      }
      $bt_origin = 0;
      $bt_cycle_begin = 0;
      while ($bt_cycle_begin < $origin) {
        $w_start = rindex($words, ' ', $bt_origin);
        $w_end = index($words, ' ', $bt_origin);
        $pos = ($bt_origin - $w_start) - 1;
        $target = $len - ($w_end - $pos);
        $t = vec($words, $target, 8);

        if ($t == 32) {
          $target = $len - $target - 1;
          $t = vec($words, $target, 8);
        }

        if ($target == $bt_cycle_begin) {
          $bt_origin = ++$bt_cycle_begin;
          if (vec($words, $bt_origin, 8) == 32) {
            $bt_origin = ++$bt_cycle_begin;
          }
        } else {
          $bt_origin = $target;
        }

        if ($target == $origin) {
          $origin += 1;
          if (vec($words, $origin, 8) == 32) {
            $origin += 1;
          }
          $bt_origin = $bt_cycle_begin = 0;
        }
      }
    }

    $cycle_begin = $origin;
    $o = vec($words, $origin, 8);
    $was_cycle = 1;
  } else {
    $was_cycle = 0;
  }
}

for $i (0..$len/2-1) {
  $mirror = $len - $i - 1;
  $o = vec($words, $i, 8);
  $m = vec($words, $mirror, 8);
  # if exactly one is a space...
  if (($o == 32) ^ ($m == 32)) {
    $swaps += 2;
    vec($words, $mirror, 8) = $o;
    vec($words, $i, 8) = $m;
  }
}

chop($words);
print $words;
print "swaps: $swaps; len: $len";
print 'ratio: ', $swaps/$len;

Editar: se agregó un contador de intercambio y la relación.

La entrada se toma de stdin. Uso de la muestra:

$ echo where in the world is carmen sandiego|perl reverse-inplace.pl
sandiego carmen is world the in where
swaps: 35; len: 37
ratio: 0.945945945945946

Método

Para comenzar, el primer carácter de la cadena se mueve a su destino final. El personaje recién reemplazado se mueve a su destino, etc. Esto continúa hasta que se cumpla una de dos condiciones:

  1. El personaje debe intercambiarse con un espacio.
    Cuando esto ocurre, el personaje no se intercambia con el espacio, sino más bien en la posición de espejo del espacio. El algoritmo continúa desde esa posición.
  2. Se ha alcanzado un ciclo.
    Cuando el objetivo regresa a la posición inicial del ciclo actual, se necesita encontrar el siguiente carácter sin cambiar (o más bien, cualquier carácter sin cambiar). Para hacer esto bajo constantes restricciones de memoria, todos los intercambios que se han realizado hasta este punto se remontan.

Después de esta fase, cada personaje no espacial se ha movido como máximo una vez. Para finalizar, todos los caracteres de espacio se intercambian con los caracteres en sus posiciones espejo, lo que requiere dos operaciones de asignación por espacio.


Wow eso es genial. ¿Puedes explicar por qué poner el personaje en la posición de espejo del espacio da la respuesta correcta?
justo

1
@ Niklas, no creo que sea posible. Porque para hacer el retroceso necesita la información de posición del espacio. Si anula esa información, no puede hacer el seguimiento.
justo

1
Hago una comparación con mi algoritmo en mi respuesta aquí: codegolf.stackexchange.com/a/26952/16337
solo

1
@justhalf En la cadena final, todos los espacios estarán en su posición reflejada. Por lo tanto, podemos usar esta posición de forma segura para almacenar el personaje que reemplaza el espacio y cambiarlo al final.
primo

1
Bien hecho. Tuve una idea similar, pero no pensé en dejar los espacios en su lugar y luego reflejarlos.
IchBinKeinBaum

7

Ruby, puntaje 2

Como iniciador, un algoritmo muy básico. Primero invierte toda la cadena y luego invierte cada palabra de la cadena nuevamente. En el peor de los casos (una palabra, número par de caracteres) la puntuación se convierte en 2.

def revstring(s, a, b)
  while a<b
    h = s[a]
    s[a] = s[b]
    s[b] = h
    a += 1
    b -= 1
  end
  s
end

def revwords(s)
  revstring(s, 0, s.length-1)
  a = 0
  while a<s.length
    b = a+1
    b += 1 while b<s.length and s[b]!=" "
    revstring(s, a, b-1)
    a = b+1
  end
  s
end

Uso:

> revwords("hello there everyone")
"everyone there hello"

¿Por qué no usaste una función incorporada de Ruby para invertir una cadena? ¿Cambiaría la puntuación?
AL

use, s [a], s [b] = s [b], s [a]
Thaha kp

5

C ++: Puntuación 2

#include<iostream>
#include<algorithm>

void rev(std::string& s)
{
    std::reverse(s.begin(),s.end());
    std::string::iterator i=s.begin(),j=s.begin();
    while(i!=s.end())
    {
        while(i!=s.end()&&(*i)==' ')
            i++;
        j=i;
        while(i!=s.end()&&(*i)!=' ')
            i++;
        std::reverse(j,i);
    }
}

int main()
{
    std::string s;
    getline(std::cin,s);
    rev(s);
    std::cout<<s;
}

2
Lo probé ¡Funciona bien!
bacchusbeale

2

Rebol

reverse-words: function [
    "Reverse the order of words. Modifies and returns string (series)"
    series [string!] "At position (modified)"
  ][
    first-time: on
    until [
        reverse/part series f: any [
            if first-time [tail series]
            find series space
            tail series
        ]
        unless first-time [series: next f]
        first-time: off
        tail? series
    ]

    series: head series
]

No tengo claro el puntaje para esto. No hay asignación directa de cadenas en este código. Todo lo maneja el reverse/partque realiza la inversión en el lugar dentro e inicialmente en general la cadena.

Algunos detalles sobre el código:

  • Pase por la cadena ( series) hasta que alcance eltail?

  • Primera vez en el bucle, haga el reverso completo de la cadena - reverse/part series tail series(que es lo mismo que reverse series)

  • Luego invierta cada palabra encontrada en otras iteraciones: reverse/part series find series space

  • Una vez que la palabra agotada se encuentra, regresa tail seriespara que invierta la última palabra de la cadena.reverse/part series tail series

Rebol permite atravesar una cadena a través de un puntero interno . Puedes ver esto enseries: next f (mover el puntero al espacio después del comienzo de la siguiente palabra) y series: head series(restablece el puntero de nuevo a la cabeza).

Ver serie para más información.

Ejemplo de uso en la consola Rebol:

>> reverse-words "everyone there hello"
== "hello there everyone"

>> x: "world hello"
== "world hello"

>> reverse-words x
== "hello world"

>> x
== "hello world"

>> reverse-words "hello"
== "hello"

En la primera pasada, cada personaje se reposiciona una vez, y en la segunda pasada, cada personaje no espacial se vuelve a colocar nuevamente. Para una entrada arbitrariamente grande con relativamente pocos espacios, la puntuación se aproxima a 2.
primo

2

C: Puntuación 2

Esto simplemente voltea la cadena completa una vez y luego invierte cada palabra.

#include <stdio.h>
#include <string.h>

void reverse(char *s,unsigned n){
    char c;
    unsigned i=0,r=1;
    while(i < n){ //no swap function in C 
        c=s[i];
        s[i++]=s[n];
        s[n--]=c;
    }
}

unsigned wordlen(char *s){
    unsigned i=0;
    while (s[i] != ' ' && s[i]) ++i;
    return i;
}

int main(int argc, char **argv) {
    char buf[]="reverse this also";
    char *s=buf;
    unsigned wlen=0,len=strlen(s)-1;
    reverse(s,len);  //reverse entire string
    while(wlen<len){  // iterate over each word till end of string
      wlen=wordlen(s);
      reverse(s,wlen-1);
      s+=wlen+1;
      len-=wlen;
    }
    printf("%s\n",buf);
    return 0;
}

3
Esta es una respuesta de solo código. Considere agregar una explicación de lo que sucede en su código.
Justin

1

Python: puntaje 2

Casi idéntico al algoritmo de Howard, pero realiza los pasos de inversión a la inversa (primero voltea las palabras y luego voltea toda la cadena). Utiliza la memoria adicional es 3 variables byte-size: i, j, y t. Técnicamente, findy lenestán utilizando algunas variables internas, pero podrían reutilizar ioj sin ninguna pérdida de función.

edición rápida: guardar en asignaciones de cadenas intercambiando solo cuando los caracteres difieren, por lo que puedo obtener algunos puntos adicionales de la nota # 2.

from sys import stdin

def word_reverse(string):
    # reverse each word
    i=0
    j=string.find(' ')-1
    if j == -2: j=len(string)-1
    while True:
        while i<j:
            if string[i] != string[j]:
                t = string[i]
                string[i] = string[j]
                string[j] = t
            i,j = i+1,j-1
        i=string.find(' ', i)+1
        if i==0: break
        j=string.find(' ', i)-1
        if j == -2: j=len(string)-1
    # reverse the entire string
    i=0
    j=len(string)-1
    while i<j:
        if string[i] != string[j]:
            t = string[i]
            string[i] = string[j]
            string[j] = t
        i,j = i+1,j-1
    return string

for line in stdin.readlines():
    # http://stackoverflow.com/a/3463789/1935085
    line = line.strip() # no trailing newlines ore spaces to ensure it conforms to '[a-z]+( [a-z]+)*'
    print word_reverse(bytearray(line))

1

Lote

Admitiré que no entiendo completamente la puntuación (creo que son dos) ... pero diré: hace lo correcto .

@echo off

setLocal enableDelayedExpansion
set c=
set s=

for %%a in (%~1) do set /a c+=1 & echo %%a >> f!c!

for /L %%a in (!c!, -1, 1) do (
    set /p t=<f%%a
    set s=!s!!t!
    del f%%a
)

echo !s!

La entrada se toma como primer valor de entrada estándar y, por lo tanto, debe estar entre comillas:
llame a:script.bat "hello there everyone"
Salida: everyone there hello.

Quizás alguien más pueda calificarme (suponiendo que, de alguna otra manera, no me haya descalificado).


-2

Javascript

function reverseWords(input) {
    if (input.match(/^[a-z]+( [a-z]+)*$/g)) {
        return input.split(' ').reverse().join(' ');
    }
}

Uso:

> reverseWords('hello there everyone');
'everyone there hello'

Tengo la extraña sensación de que me perdí algo ...


3
Sí, esto no está en su lugar, porque no modifica la cadena de entrada. Como eso no es posible en JavaScript, debe emular cadenas con una matriz de caracteres (es decir, enteros de puntos de código o cadenas de un solo carácter).
Martin Ender

Más concretamente, utiliza muchísimo espacio adicional.
Ben Millwood
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.