Ordine inverso delle parole in una stringa al posto


17

L'obiettivo

  • Ti viene data una stringa mutabile che corrisponde [a-z]+( [a-z]+)*.
  • Devi mutarlo nella stringa che contiene le stesse parole, ma in ordine inverso, in modo che "ciao a tutti" diventi "tutti a ciao".
  • Non è consentito utilizzare più di una quantità costante di memoria aggiuntiva (quindi non copiare l'intera stringa o qualsiasi parola intera in un buffer appena allocato).
  • Non ci sono vincoli di tempo. Essere irrimediabilmente inefficienti non danneggerà il tuo punteggio.
  • Se la tua lingua preferita non consente la mutazione delle stringhe, le matrici di caratteri sono un sostituto accettabile.

Il tuo punteggio

  • Il tuo punteggio viene conteggiato esclusivamente sul numero di assegnazioni effettuate agli elementi stringa (i punteggi più piccoli sono i migliori). Se si utilizza una funzione di libreria che scrive nella stringa, contano anche le sue scritture.
  • Supponiamo che il numero di incarichi necessari per l'input s sia n (s) . Quindi il tuo punteggio è il massimo (pedanticamente, supremo) su tutti gli input s (corrispondenti alla regex sopra specificata) di n (s) / lunghezza (s) . Se non puoi calcolarlo con precisione, puoi usare il limite superiore più basso che puoi provare.
  • Puoi rompere il pareggio se puoi provare che il tuo algoritmo utilizza un numero asintoticamente inferiore di incarichi (ciò può accadere anche se hai lo stesso punteggio, vedi sotto). Se non riesci a farlo, puoi rompere il pareggio dimostrando che usi meno memoria aggiuntiva. Tuttavia, la prima condizione di tie-break ha sempre la precedenza.
  • Per alcuni input ogni personaggio deve cambiare, quindi non è possibile segnare meno di 1.
  • Mi viene in mente un semplice algoritmo con punteggio 2 (ma non lo sto inserendo).

Note su suprema e legami

  • Un supremum di un insieme di numeri è il numero più piccolo che non è più piccolo di nessuno di essi. Questo è molto simile al massimo di un set, tranne per il fatto che alcuni infiniti set come {2/3, 3/4, 4/5, 5/6, ...} non hanno un singolo elemento massimo, ma avranno comunque un supremum, in questo caso 1.
  • Se riesci a "salvare" solo un numero costante di assegnazioni rispetto al mio algoritmo di punteggio 2 (diciamo), il tuo punteggio sarà comunque 2, perché ti avvicinerai arbitrariamente a 2 più grande sarà il tuo input. Tuttavia, si vince al pareggio se si tratta di questo.

1
Sarei un po 'triste se tutto questo si riducesse a osservazioni di punteggio 2 vincolanti sull'uso della memoria. Per lo più ho pubblicato questa domanda chiedendomi se qualcuno sarebbe riuscito a segnare meno di 2.
Ben Millwood,

1
Non ho una prova formale, ma la mia intuizione mi dice che con la restrizione dello spazio costante, è impossibile fare meglio di 2. Se contate solo dichiarazioni variabili per lo spazio, potrei imbrogliare e fare una funzione ricorsiva. ma ciò nasconde lo little-omega(1)spazio solo mettendolo in pila.
wrongu,

2
@bacchusbeale sì, ma è memoria aggiuntiva costante.
Martin Ender,

4
Se si applicano rigorosamente le regole, è impossibile scrivere un tale programma. La stringa può essere di lunghezza arbitraria e sarà necessario almeno un tipo di variabile che memorizza un indice. Quindi devo solo rendere la stringa abbastanza lunga da superare i limiti della tua variabile. Per memorizzare correttamente almeno un indice, la memoria necessaria aumenterà con la lunghezza della stringa.
IchBinKeinBaum

3
@IchBinKeinBaum ha ragione, è impossibile eseguire questa attività con O(1)spazio aggiuntivo. È necessario O(log n)spazio per memorizzare una posizione di indice, poiché un numero intero a k bit può archiviare al suo interno solo per stringhe di lunghezza fino a 2^k. Limitare la lunghezza delle stringhe rende la sfida piuttosto inutile, poiché ogni algoritmo richiederebbe O(1)spazio in questo modo.
Dennis,

Risposte:


4

Python, Punteggio: 2 1,5 1,25

Questa è la combinazione diretta tra la risposta di primo e la mia risposta. Quindi crediti anche a lui!

La prova è ancora in corso, ma ecco il codice con cui giocare! Se riesci a trovare un contro esempio di punteggio maggiore di 1,25 (o se c'è un bug), fammi sapere!

Attualmente il caso peggiore è:

aa ... aa dcb ... cbd

dove ci sono esattamente n di ciascuna le lettere "a", "b", "c" e "" (spazio), ed esattamente due "d" s. La lunghezza della stringa è 4n + 2 e il numero di incarichi è 5n + 2 , con un punteggio di 5/4 = 1,25 .

L'algoritmo funziona in due passaggi:

  1. Trova ktale string[k]e string[n-1-k]sono i confini delle parole
  2. Esegui qualsiasi algoritmo di inversione delle parole su string[:k]+string[n-1-k:](ovvero, concatenazione del primo ke dell'ultimo kcarattere) con una piccola modifica.

dove nè la lunghezza della stringa.

Il miglioramento apportato da questo algoritmo deriva dalla "piccola modifica" del passaggio 2. È fondamentalmente la consapevolezza che nella stringa concatenata, i caratteri in posizione ke k+1sono i confini delle parole (il che significa che sono spazi o primo / ultimo carattere in una parola), e così possiamo sostituire direttamente i caratteri in posizione ke k+1con il carattere corrispondente nella stringa finale, salvando alcune assegnazioni. Ciò rimuove il caso peggiore dall'algoritmo di inversione delle parole dell'host

Ci sono casi in cui non riusciamo a trovarli k, in quel caso eseguiamo semplicemente "l'algoritmo di inversione di qualsiasi parola" sull'intera stringa.

Il codice è lungo per gestire questi quattro casi nell'esecuzione dell'algoritmo di inversione delle parole sulla stringa "concatenata":

  1. Quando knon viene trovato ( 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)

Sono sicuro che il codice può essere abbreviato. Attualmente è lungo perché all'inizio non avevo un'immagine chiara dell'intero algoritmo. Sono sicuro che uno può progettarlo per essere rappresentato in un codice più breve =)

Esempio di esecuzione (il primo è mio, il secondo è primo):

Inserisci stringa: a bc def ghij
"ghij def bc a": 9, 13, 0,692
"ghij def bc a": 9, 13, 0,692
Immettere la stringa: ab cdefghijklmnopqrstuvw xyz
"zyxwvutsrqponmlkjihgf edc ab": 50, 50, 1.000
"zyxwvutsrqponmlkjihgf edc ab": 51, 50, 1.020
Immettere la stringa: abcdefg hijklmnopqrstuvwx
"hijklmnopqrstuvwx gfedcb a": 38, 31, 1.226
"hijklmnopqrstuvwx gfedcb a": 38, 31, 1.226
Inserisci stringa: 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
Inserire la stringa: aaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaa dcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbd
"Dcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbd aaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaa un": 502, 402, 1.249
"Dcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbd aaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaa un": 502, 402, 1.249

Puoi vedere che il punteggio è quasi lo stesso, tranne nel caso peggiore dell'algoritmo di inversione di parole dell'host nel terzo esempio, per il quale il mio approccio produce un punteggio inferiore 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, Punteggio: 1,5

Il numero esatto di incarichi può essere approssimato dalla formula:

n <= 1.5 * lunghezza (stringa)

con il caso peggiore:

abcdefghi jklmnopqrstuvwxyzzz

con 55 assegnazioni su corda con lunghezza 37.

L'idea è simile alla mia precedente, è solo che in questa versione ho cercato di trovare prefisso e suffisso ai limiti delle parole con differenza di lunghezza al massimo 1. Quindi eseguo il mio algoritmo precedente su quel prefisso e suffisso (immaginandoli come concatenati) . Quindi continuare sulla parte non elaborata.

Ad esempio, per il caso peggiore precedente:

ab | ab | c

faremo prima l'inversione di parole su "ab" e "c" (4 incarichi) per essere:

c | ab | ab

Sappiamo che al confine c'era lo spazio (ci sono molti casi da gestire, ma puoi farlo), quindi non abbiamo bisogno di codificare lo spazio al confine, questo è il principale miglioramento rispetto all'algoritmo precedente .

Quindi finalmente corriamo sui quattro personaggi centrali per ottenere:

cba ab

in totale 8 incarichi, l'ottimale per questo caso, poiché tutti gli 8 caratteri sono cambiati.

Questo elimina il caso peggiore nel precedente algoritmo poiché viene eliminato il caso peggiore nel precedente algoritmo.

Guarda alcuni esempi (e confronto con la risposta di @ primo - la sua è la seconda riga):

Inserisci stringa: posso fare qualsiasi cosa
"tutto ciò che posso fare": 20, 17
"Qualsiasi cosa posso fare": 17, 17
Immettere la stringa: abcdef ghijklmnopqrs
"ghijklmnopqrs fedcb a": 37, 25
"ghijklmnopqrs fedcb a": 31, 25
Immettere la stringa: abcdef ghijklmnopqrst
"ghijklmnopqrst fedcb a": 38, 26
"ghijklmnopqrst fedcb a": 32, 26
Immettere la stringa: abcdefghi jklmnozzzzzzzzzzzzzzzzzzz
"jklmnozzzzzzzzzzzzzzzzz ihgfedcb a": 59, 41
"jklmnozzzzzzzzzzzzzzzzz ihgfedcb a": 45, 41
Immettere la stringa: abcdefghi jklmnopqrstuvwxyzzz
"jklmnopqrstuvwxyzzz ihgfedcb a": 55, 37
"jklmnopqrstuvwxyzzz ihgfedcb a": 45, 37
Immettere la stringa: ab ababababababac
"cababababababa ab": 30, 30
"cababababababa ab": 31, 30
Immettere la stringa: ab abababababababc
"cbababababababa ab": 32, 32
"cbababababababa ab": 33, 32
Immettere la stringa: abc d abc
"abc d abc": 0, 9
"abc d abc": 0, 9
Immettere la stringa: abc dca
"acd abc": 6, 9
"acd abc": 4, 9
Immettere la stringa: abc ababababababc
"cbabababababa abc": 7, 29
"cbabababababa abc": 5, 29

la risposta di primo è generalmente migliore, anche se in alcuni casi posso avere 1 punto di vantaggio =)

Anche il suo codice è molto più breve del mio, ahah.

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, Punteggio: asintoticamente 2, nel caso normale molto meno

vecchio codice rimosso a causa di vincoli di spazio

L'idea è di iterare attraverso ogni indice, e per ogni indice i, prendiamo il carattere, calcolare la nuova posizione j, memorizzare il carattere nella posizione j, assegnare il carattere ia j, e ripetere Tipiche all'indice j. Poiché abbiamo bisogno delle informazioni sullo spazio per calcolare la nuova posizione, codifico il vecchio spazio come la versione maiuscola della nuova lettera e il nuovo spazio come "@".


Se puoi ridurre il numero di parole nel caso peggiore in termini di lunghezza della stringa (diciamo, length(string)/3forzando ogni parola nel caso peggiore ad essere almeno della lunghezza 2 in qualche modo), allora il punteggio sarà inferiore a 2 (nell'esempio sopra sarà 1,67)
solo

1
Ho aggiunto un contatore di swap al mio; il tuo in realtà batte il mio nel caso peggiore (ma non nel caso generale). Ho bisogno di trovare un modo per risolverlo;)
primo

Riga 127: if any(process_loop(...) for j in range(...))non è necessario contare le assegnazioni di questi cicli di processo?
primo

Questo non fa alcun compito. Se vedi, il dry_runparametro è impostato su un valore diverso da zero (il valore su i). All'interno di process_loop, se dry_runè diverso da zero, non eseguirà alcuna assegnazione.
solo

1
Penso di avere un'immagine migliore ora. In sostanza, vengono combinati due metodi distinti con diverso comportamento nel caso peggiore, al fine di eliminare il caso peggiore per entrambi. Mi piace. Penso in generale, questo potrebbe essere l'approccio migliore da adottare, anche se sembra probabile che due (o più) metodi completamente diversi potrebbero essere combinati per ridurre ulteriormente il caso peggiore.
primo

14

Perl, punteggio 1.3̅

Per ogni carattere non spaziale viene eseguita un'assegnazione e per ogni carattere spaziale due assegnazioni. Poiché i caratteri spaziali non possono ammontare a più della metà del numero totale di caratteri, il punteggio peggiore è 1,5 .

L'algoritmo non è cambiato, ma posso dimostrare un limite superiore inferiore. Facciamo due osservazioni:

  1. Non è necessario scambiare gli spazi direttamente di fronte agli spazi.
  2. Le parole a carattere singolo direttamente di fronte agli spazi non vengono scambiate durante la fase principale, ma solo una volta alla fine.

Si può quindi vedere che il "caso peggiore" teorico con spazi asintoticamente 1/2 non è affatto il caso peggiore: 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

In effetti, è un buon caso.

Per impedire l'osservazione di uno e due sopra, ogni parola di un carattere dovrebbe essere riposizionata nel mezzo di una parola lunga tre o più caratteri. Ciò suggerirebbe un caso peggiore contenente spazi asintoticamente 1/3: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 equivalentemente, solo parole di due caratteri: 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

Poiché il caso peggiore contiene spazi asintoticamente 1/3, il punteggio del caso peggiore diventa 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;

Modifica: aggiunto un contatore di swap e il rapporto.

L'input è preso dallo stdin. Esempio di utilizzo:

$ 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

Metodo

Per iniziare, il primo carattere della stringa viene spostato nella sua destinazione finale. Il personaggio appena sostituito viene quindi spostato a destinazione, ecc. Questo continua fino a quando una delle due condizioni è soddisfatta:

  1. Il personaggio dovrebbe essere scambiato con uno spazio.
    Quando ciò si verifica, il personaggio non viene scambiato con lo spazio, ma piuttosto nella posizione speculare dello spazio. L'algoritmo continua da quella posizione.
  2. È stato raggiunto un ciclo.
    Quando il bersaglio ritorna nella posizione iniziale iniziale del ciclo corrente, deve essere trovato il successivo personaggio non scambiato (o meglio, qualsiasi personaggio non scambiato). Per fare ciò con costanti vincoli di memoria, tutti gli swap che sono stati fatti fino a questo punto vengono rintracciati.

Dopo questa fase, ogni personaggio non spaziale è stato spostato al massimo una volta. Per finire, tutti i personaggi dello spazio vengono scambiati con i personaggi nelle loro posizioni speculari, che richiedono due operazioni di assegnazione per spazio.


Wow, è fantastico. Puoi spiegare perché mettere il personaggio nella posizione speculare dello spazio dà una risposta corretta?
solo

1
@Niklas, non credo sia possibile. Perché per eseguire il backtracking sono necessarie le informazioni sulla posizione dello spazio. Se si sovrascrivono tali informazioni, non è possibile eseguire il backtracking.
solo

1
Faccio un confronto con il mio algoritmo nella mia risposta qui: codegolf.stackexchange.com/a/26952/16337
justhalf

1
@justhalf Nella stringa finale, tutti gli spazi saranno nella loro posizione speculare. Pertanto, possiamo tranquillamente utilizzare questa posizione per memorizzare il personaggio che sostituisce lo spazio e cambiarlo alla fine.
primo

1
Molto bene. Ho avuto un'idea simile ma non ho pensato di lasciare gli spazi al loro posto e quindi di rispecchiarli.
IchBinKeinBaum

7

Ruby, punteggio 2

Innanzitutto un algoritmo molto semplice. Prima inverte l'intera stringa e quindi inverte di nuovo ogni parola nella stringa. Nel caso peggiore (una parola, numero pari di caratteri) il punteggio diventa 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"

Perché non hai usato una funzione integrata di Ruby per invertire una stringa? Cambierebbe il punteggio?
AL

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

5

C ++: punteggio 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
L'ho provato. Funziona bene!
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
]

Non sono chiaro il punteggio per questo. Non esiste un'assegnazione diretta delle stringhe in questo codice. Tutto è gestito da quelloreverse/part che esegue l'inversione sul posto all'interno e inizialmente nel complesso della stringa.

Alcuni dettagli sul codice:

  • Passa attraverso la stringa ( series) fino a raggiungere iltail?

  • La prima volta in loop esegui il contrario completo della stringa - reverse/part series tail series(che è uguale a reverse series)

  • Quindi invertire ogni parola trovata su ulteriori iterazioni - reverse/part series find series space

  • Una volta che la parola esaurita trova, quindi ritorna in tail seriesmodo da invertire l'ultima parola nella stringa -reverse/part series tail series

Rebol consente l'attraversamento di una stringa tramite un puntatore interno . Puoi vederlo suseries: next f (sposta il puntatore su dopo lo spazio, quindi all'inizio della parola successiva) e series: head series(ripristina il puntatore alla testa).

Vedi la serie per maggiori informazioni.

Esempio di utilizzo nella console di 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"

Al primo passaggio ogni personaggio viene riposizionato una volta, e al secondo passaggio ogni personaggio non spaziale viene riposizionato di nuovo. Per un input arbitrariamente grande con relativamente pochi spazi, il punteggio si avvicina al 2.
primo

2

C: Punteggio 2

Questo capovolge l'intera stringa una volta, quindi inverte ogni parola.

#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
Questa è una risposta di solo codice. Valuta di aggiungere una spiegazione di ciò che accade nel tuo codice.
Giustino,

1

Python: punteggio 2

Quasi identico all'algoritmo di Howard, ma fa i passaggi di inversione al contrario (prima capovolge le parole poi capovolge l'intera stringa). Aggiuntivi utilizzati memoria è 3 variabili byte-size: i, j, e t. Tecnicamente, finde lenstanno usando alcune variabili interne, ma potrebbero riutilizzarle altrettanto facilmente ioj senza alcuna perdita di funzione.

modifica rapida: salvataggio delle assegnazioni di stringhe scambiando solo quando i caratteri differiscono, quindi posso prendere alcuni punti extra dalla 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

Lotto

Devo ammettere di non comprendere appieno il punteggio (penso che sia due) .. ma dirò - fa la cosa .

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

Ingresso è preso come primo valore di input standard, e quindi ha bisogno di essere circondato dalle virgolette -
chiamata: script.bat "hello there everyone"
fuori: everyone there hello.

Forse qualcun altro può segnarmi (supponendo che io, in qualche altro modo, non mi abbia squalificato).


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

Ho la strana sensazione di aver perso qualcosa ...


3
Sì, questo non è a posto, perché non si modifica la stringa di input. Poiché ciò non è possibile in JavaScript, è necessario emulare le stringhe con una matrice di caratteri (ad esempio numeri interi di punti di codice o stringhe a carattere singolo).
Martin Ender,

Più precisamente, utilizza un sacco di spazio aggiuntivo.
Ben Millwood,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.