Ordem inversa de palavras em uma string no local

17

A tarefa

  • Você recebe uma sequência mutável que corresponde [a-z]+( [a-z]+)*.
  • Você deve modificá-lo para a string que contém as mesmas palavras, mas na ordem inversa, para que "olá lá todos" se torne "todos lá olá".
  • Você não tem permissão para usar mais do que uma quantidade constante de memória adicional (portanto, não é necessário copiar a cadeia inteira ou nenhuma palavra inteira em um buffer que você acabou de alocar).
  • Não há restrições de tempo. Ser irremediavelmente ineficiente não prejudicará sua pontuação.
  • Se o seu idioma de escolha não permitir a mutação de cadeias, matrizes de caracteres são um substituto aceitável.

Sua pontuação

  • Sua pontuação é contada apenas com o número de atribuições feitas aos elementos de sequência (as melhores pontuações são as melhores). Se você usar uma função de biblioteca que grava na cadeia, suas gravações também contam.
  • Suponha que o número de atribuições necessárias para a entrada s seja n (s) . Então sua pontuação é o máximo (pedanticamente, supremo) em todas as entradas s (correspondendo ao regex especificado acima) de n (s) / comprimento (s) . Se você não puder calcular isso com precisão, poderá usar o limite superior mais baixo que puder provar.
  • Você pode quebrar o empate se puder provar que seu algoritmo usa assintoticamente menos atribuições (isso pode acontecer mesmo se você tiver a mesma pontuação, veja abaixo). Se você não puder fazer isso, poderá quebrar o empate mostrando que usa menos memória adicional. No entanto, a primeira condição de desempate sempre tem precedência.
  • Para algumas entradas, todos os caracteres precisam mudar, portanto, não é possível marcar menos que 1.
  • Eu posso pensar em um algoritmo simples com pontuação 2 (mas não estou inserindo).

Notas sobre suprema e laços

  • Um supremo de um conjunto de números é o menor número que não é menor que qualquer um deles. Isso é muito parecido com o máximo de um conjunto, exceto que alguns conjuntos infinitos como {2/3, 3/4, 4/5, 5/6, ...} não têm um único elemento máximo, mas ainda terão um supremo, neste caso 1.
  • Se você conseguir "salvar" apenas um número constante de atribuições no meu algoritmo de pontuação 2 (digamos), sua pontuação ainda será 2, porque você ficará arbitrariamente perto de 2 quanto maior for a sua entrada. No entanto, você ganha no tie-break, se for o caso.
Ben Millwood
fonte
11
Eu ficaria um pouco triste se tudo isso se resumisse a envios de pontuação 2 empate no uso de memória. Eu, principalmente, postou esta pergunta perguntando se alguém conseguiria marcar menos de 2.
Ben Millwood
11
Não tenho uma prova formal, mas minha intuição me diz que, com a restrição de espaço constante, é impossível fazer melhor que 2. Se você contar apenas declarações variáveis ​​de espaço, eu poderia trapacear e criar uma função recursiva. mas isso apenas disfarça o little-omega(1)espaço, colocando-o na pilha.
11118 wrongu #
2
@bacchusbeale sim, mas é memória extra constante.
Martin Ender
4
Se você aplicar estritamente as regras, é impossível escrever um programa desse tipo. A cadeia pode ter um comprimento arbitrário e você precisará de pelo menos algum tipo de variável que armazene um índice. Então, eu apenas tenho que fazer a string longa o suficiente para exceder os limites da sua variável. Para armazenar com êxito pelo menos um índice, a memória necessária aumentará com o comprimento da string.
IchBinKeinBaum
3
@IchBinKeinBaum está certo, é impossível executar esta tarefa com O(1)espaço adicional. Você precisa de O(log n)espaço para armazenar uma posição de índice, uma vez que um número inteiro de k bits só pode armazenar nelas para cadeias de caracteres de comprimento até 2^k. Limitar o comprimento das strings torna o desafio bastante inútil, pois todo algoritmo exigiria O(1)espaço dessa maneira.
Dennis

Respostas:

4

Python, Pontuação: 2 1.5 1.25

Esta é a combinação direta entre a resposta do primo e a minha resposta. Então, créditos para ele também!

A prova ainda está em andamento, mas aqui está o código para brincar! Se você encontrar um exemplo contrário de pontuação maior que 1,25 (ou se houver um erro), avise-me!

Atualmente, o pior caso é:

aa ... aa dcb ... cbd

onde há exatamente n de cada uma das letras "a", "b", "c" e "" (espaço) e exatamente dois "d" s. O comprimento da sequência é 4n + 2 e o número de atribuições é 5n + 2 , resultando em uma pontuação de 5/4 = 1,25 .

O algoritmo funciona em duas etapas:

  1. Encontre ktais que string[k]e string[n-1-k]sejam limites de palavras
  2. Execute qualquer algoritmo de reversão de palavras string[:k]+string[n-1-k:](concatenação do primeiro ke do último kcaracteres) com pequenas modificações.

Onde né o comprimento da string.

O aprimoramento que esse algoritmo fornece vem da "pequena modificação" na Etapa 2. É basicamente o conhecimento de que na sequência concatenada, os caracteres na posição ke k+1são os limites das palavras (o que significa que são espaços ou o primeiro / último caractere de uma palavra), e, portanto, podemos substituir diretamente os caracteres na posição ke k+1pelo caractere correspondente na sequência final, economizando algumas atribuições. Isso remove o pior caso do algoritmo de reversão de palavras do host

Há casos em que não podemos realmente encontrar isso k; nesse caso, apenas executamos o "algoritmo de reversão de qualquer palavra" em toda a cadeia.

O código é longo para lidar com esses quatro casos ao executar o algoritmo de reversão de palavras na cadeia "concatenada":

  1. Quando knão é encontrado ( f_long = -2)
  2. Quando string[k] != ' ' and string[n-1-k] != ' '( f_long = 0)
  3. Quando string[k] != ' ' and string[n-1-k] == ' '( f_long = 1)
  4. Quando string[k] == ' ' and string[n-1-k] != ' '( f_long = -1)

Tenho certeza de que o código pode ser reduzido. Atualmente, é longo, porque eu não tinha uma imagem clara de todo o algoritmo no começo. Tenho certeza de que se pode projetá-lo para ser representado em um código mais curto =)

Exemplo de execução (primeiro é meu, segundo é primo):

Digite a string: a bc def ghij
"ghij def bc a": 9, 13, 0,692
"ghij def bc a": 9, 13, 0,692
Digite a string: ab cdefghijklmnopqrstuvw xyz
"zyxwvutsrqponmlkjihgf edc ab": 50, 50, 1.000
"zyxwvutsrqponmlkjihgf edc ab": 51, 50, 1.020
Digite a string: abcdefg hijklmnopqrstuvwx
"hijklmnopqrstuvwx gfedcb a": 38, 31, 1.226
"hijklmnopqrstuvwx gfedcb a": 38, 31, 1.226
Digite a string: a bc de fg oi jk lm no pq rs tu vw xy zc
"zc xy vw tu rs pq no lm jk oi fg de bc a": 46, 40, 1.150
"zc xy vw tu rs pq no lm jk oi fg de bc a": 53, 40, 1.325
Digite string: 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

Você pode ver que a pontuação é quase a mesma, exceto no pior caso do algoritmo de reversão de palavras do host no terceiro exemplo, para o qual minha abordagem gera pontuação menor que 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, Pontuação: 1.5

O número exato de atribuições pode ser aproximado pela fórmula:

n <= 1,5 * comprimento (string)

sendo o pior caso:

abcdefghi jklmnopqrstuvwxyzzz

com 55 atribuições em sequência com comprimento 37.

A idéia é semelhante à anterior, mas nesta versão tentei encontrar prefixo e sufixo nos limites das palavras com diferença de comprimento no máximo 1. Então, eu executo meu algoritmo anterior nesse prefixo e sufixo (imagine-os como sendo concatenados) . Depois continue na parte não processada.

Por exemplo, para o pior caso anterior:

ab ab c

primeiro faremos a reversão de palavras em "ab" e "c" (4 atribuições) para ser:

c ab | ab

Sabemos que no limite costumava ser espaço (há muitos casos a serem tratados, mas você pode fazer isso), portanto, não precisamos codificar o espaço no limite, esse é o principal aprimoramento do algoritmo anterior .

Finalmente, rodamos nos quatro caracteres do meio para obter:

cba ab

no total, 8 tarefas, o ideal para este caso, pois todos os 8 caracteres foram alterados.

Isso elimina o pior caso do algoritmo anterior, pois o pior caso do algoritmo anterior é eliminado.

Veja alguns exemplos de execução (e comparação com a resposta do @ primo - esta é a segunda linha):

Digite a string: eu posso fazer qualquer coisa
"qualquer coisa que eu possa": 20, 17
"qualquer coisa que eu possa": 17, 17
Digite a string: abcdef ghijklmnopqrs
"ghijklmnopqrs fedcb a": 37, 25
"ghijklmnopqrs fedcb a": 31, 25
Digite a string: abcdef ghijklmnopqrst
"ghijklmnopqrst fedcb a": 38, 26
"ghijklmnopqrst fedcb a": 32, 26
Digite a string: abcdefghi jklmnozzzzzzzzzzzzzzzzz
"jklmnozzzzzzzzzzzzzzzzz ihgfedcb a": 59, 41
"jklmnozzzzzzzzzzzzzzz ihgfedcb a": 45, 41
Digite a string: abcdefghi jklmnopqrstuvwxyzzz
"jklmnopqrstuvwxyzzz ihgfedcb a": 55, 37
"jklmnopqrstuvwxyzzz ihgfedcb a": 45, 37
Digite a string: ab ababababababac
"cababababababa ab": 30, 30
"cababababababa ab": 31, 30
Digite a string: ab abababababababc
"cbababababababa ab": 32, 32
"cbababababababa ab": 33, 32
Digite a string: abc d abc
"abc d abc": 0, 9
"abc d abc": 0, 9
Digite a string: abc dca
"acd abc": 6, 9
"acd abc": 4, 9
Digite a string: abc ababababababc
"cbabababababa abc": 7, 29
"cbabababababa abc": 5, 29

A resposta do primo é geralmente melhor, embora em alguns casos eu possa ter 1 ponto de vantagem =)

Também o código dele é muito mais curto que o meu, haha.

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, Score: assintoticamente 2, no caso normal, muito menos

código antigo removido devido a restrição de espaço

A idéia é iterar através de cada índice e, para cada índice i, pegamos o caractere, calculamos a nova posição j, memorizamos o caractere na posição j, atribuímos o caractere ia je repetimos com o caractere no índice j. Como precisamos das informações de espaço para calcular a nova posição, codifico o espaço antigo como a versão em maiúscula da nova letra e o novo espaço como '@'.

justhalf
fonte
Se você pode reduzir o número de palavras, na pior das hipóteses, em termos de comprimento da sequência (digamos, length(string)/3forçando cada palavra na pior das hipóteses a ter pelo menos 2 de alguma forma), a pontuação será menor que 2 (no exemplo acima, será 1,67)
justhalf
11
Eu adicionei um contador de swap ao meu; o seu realmente supera o meu no pior dos casos (mas não no geral). Preciso encontrar uma maneira de consertar isso;)
primo 13/05
Linha 127: As if any(process_loop(...) for j in range(...))atribuições desses loops de processo não precisariam ser contadas?
Primo 14/05
Isso não faz nenhuma tarefa. Se você vir, o dry_runparâmetro está definido como diferente de zero (o valor para i). Dentro de process_loop, se dry_runfor diferente de zero, ele não fará nenhuma atribuição.
justhalf
11
Eu acho que tenho uma imagem melhor agora. Em essência, dois métodos distintos com diferentes comportamentos de pior caso são combinados, a fim de eliminar o pior caso para ambos. Eu gosto disso. Eu acho que, em geral, essa pode ser a melhor abordagem a ser adotada, embora pareça provável que dois (ou mais) métodos completamente diferentes possam ser combinados para reduzir ainda mais o pior caso.
Primo
14

Perl, pontuação 1.3̅

Para cada caractere não espacial, é realizada uma atribuição e, para cada caractere espacial, duas atribuições. Como os caracteres espaciais não podem representar mais da metade do número total de caracteres, a pior pontuação é 1,5 .

O algoritmo não mudou, mas posso provar um limite superior inferior. Vamos fazer duas observações:

  1. Os espaços diretamente em frente aos espaços não precisam ser trocados.
  2. Palavras de caractere único diretamente através dos espaços não são trocadas durante a fase principal, mas apenas uma vez no final.

Pode-se então ver que o 'pior caso' teórico com espaços assintoticamente 1/2 não é o pior caso: 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 fato, é um bom caso.

Para impedir a observação um e dois acima, cada palavra de um caractere precisaria ser reposicionada no meio de uma palavra com três ou mais caracteres. Isso sugeriria o pior caso que contenha assintoticamente 1/3 de espaço: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

Ou, equivalentemente, apenas palavras de dois 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 o pior caso contém 1/3 de espaços assintoticamente, o pior caso se torna 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;

Edit: Adicionado um contador de swap e a proporção.

A entrada é retirada do stdin. Uso da amostra:

$ 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 começar, o primeiro caractere da sequência é movido para seu destino final. O caractere recém-substituído é então movido para seu destino, etc. Isso continua até que uma das duas condições seja atendida:

  1. O personagem deve ser trocado por um espaço.
    Quando isso ocorre, o personagem não é trocado pelo espaço, mas pela posição de espelho do espaço. O algoritmo continua a partir dessa posição.
  2. Um ciclo foi alcançado.
    Quando o alvo retorna à posição inicial inicial do ciclo atual, é necessário encontrar o próximo caractere não trocado (ou melhor, qualquer caractere não trocado). Para fazer isso sob constantes restrições de memória, todos os swaps feitos até esse momento são rastreados.

Após esta fase, cada caractere não espacial foi movido no máximo uma vez. Para finalizar, todos os caracteres de espaço são trocados pelos caracteres em suas posições de espelho, exigindo duas operações de atribuição por espaço.

primo
fonte
Uau isso é legal. Você pode explicar por que colocar o personagem na posição de espelho do espaço dá a resposta correta?
Just
11
@ Niklas, acho que não é possível. Como para voltar atrás, você precisa das informações de posição do espaço. Se você substituir essas informações, não poderá voltar atrás.
Just
11
Eu faço uma comparação com o meu algoritmo na minha resposta aqui: codegolf.stackexchange.com/a/26952/16337
justhalf
11
@justhalf Na sequência final, todos os espaços estarão na posição espelhada. Portanto, podemos usar com segurança essa posição para armazenar o personagem que substitui o espaço e trocá-lo no final.
primo
11
Bem feito. Eu tive uma idéia parecida, mas não pensei em deixar os espaços no lugar e espelhá-los.
IchBinKeinBaum
7

Ruby, pontuação 2

Como iniciante, um algoritmo muito básico. Primeiro, ela reverte toda a sequência e depois reverte cada palavra na sequência novamente. Na pior das hipóteses (uma palavra, número par de caracteres), a pontuação passa a 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"
Howard
fonte
Por que você não usou uma função interna do Ruby para reverter uma string? Isso mudaria a pontuação?
AL
use, s [a], s [b] = s [b], s [a] #
Thaha kp 14/05
5

C ++: Pontuação 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;
}
Anmol Singh Jaggi
fonte
2
Eu testei. Funciona bem!
23414 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
]

Não estou claro a pontuação para isso. Não há atribuição direta de string neste código. Tudo é tratado por aquele reverse/partque inverte in-loco e inicialmente no conjunto a cadeia.

Alguns detalhes no código:

  • Passe pela string ( series) até atingir otail?

  • Na primeira vez em loop, faça o reverso completo da string - reverse/part series tail series(que é igual a reverse series)

  • Em seguida, inverta todas as palavras encontradas em outras iterações - reverse/part series find series space

  • Quando a palavra esgotada encontrar, retorne tail seriespara que ela inverta a última palavra na string -reverse/part series tail series

Rebol permite atravessar uma corda através de um ponteiro interno . Você pode ver isso em series: next f(mova o ponteiro para depois do espaço, iniciando a próxima palavra) e series: head series(redefine o ponteiro de volta à cabeça).

Veja Série para mais informações.

Exemplo de uso no console 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"
draegtun
fonte
Na primeira passagem, cada caractere é reposicionado uma vez e, na segunda passagem, cada caractere não espacial é reposicionado. Para uma entrada arbitrariamente grande com relativamente poucos espaços, a pontuação se aproxima de 2.
primo 13/05
2

C: Pontuação 2

Isso apenas inverte a string inteira uma vez e depois inverte cada palavra.

#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;
}
technosaurus
fonte
3
Esta é uma resposta apenas de código. Considere adicionar uma explicação do que acontece no seu código.
1155 Justin
1

Python: pontuação 2

Quase idêntico ao algoritmo de Howard, mas executa as etapas de reversão ao contrário (primeiro inverte as palavras e depois inverte toda a cadeia). Usada memória adicional é de 3 variáveis byte-size: i, j, e t. Tecnicamente, finde lenestão usando algumas variáveis ​​internas, mas elas podem ser reutilizadas com a mesma facilidade iou jsem perda de função.

edição rápida: economizando em atribuições de string trocando apenas quando os caracteres diferem, para que eu consiga alguns pontos extras da 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))
wrongu
fonte
1

Lote

Admito que não entendi completamente a pontuação (acho que são duas) .. mas vou dizer - faz a coisa .

@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!

A entrada é tomado como primeiro valor da entrada padrão, e por isso precisa ser colocado entre aspas -
chamada: script.bat "hello there everyone"
out: everyone there hello.

Talvez alguém possa me pontuar (supondo que eu não tenha me desqualificado de alguma outra maneira).

desgrudar
fonte
-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'

Tenho a estranha sensação de que perdi algo ...

Spedwards
fonte
3
Sim, isso não está no lugar, porque você não modifica a sequência de entrada. Como isso não é possível no JavaScript, você precisa emular seqüências de caracteres com uma matriz de caracteres (por exemplo, números inteiros de ponto de código ou seqüências de caracteres simples).
Martin Ender
Mais ao ponto, ele usa muito e muito espaço adicional.
Ben Millwood 15/05