Membalik urutan kata dalam string inplace

17

Tugas

  • Anda diberi string yang dapat berubah yang cocok [a-z]+( [a-z]+)*.
  • Anda harus mengubahnya menjadi string yang berisi kata-kata yang sama, tetapi dalam urutan terbalik, sehingga "halo di sana semua orang" menjadi "semua orang di sana halo".
  • Anda tidak diizinkan menggunakan lebih dari jumlah memori tambahan yang konstan (jadi jangan menyalin seluruh string atau keseluruhan kata ke dalam buffer yang baru saja Anda alokasikan).
  • Tidak ada batasan waktu. Menjadi tidak efisien tanpa harapan tidak akan merugikan skor Anda.
  • Jika bahasa pilihan Anda tidak mengizinkan mutasi string, array karakter adalah pengganti yang dapat diterima.

Nilai Anda

  • Skor Anda dihitung murni pada jumlah tugas yang Anda buat untuk elemen string (skor kecil adalah yang terbaik). Jika Anda menggunakan fungsi pustaka yang menulis ke string, tulisannya juga dihitung.
  • Misalkan jumlah tugas yang Anda butuhkan untuk input s adalah n (s) . Maka skor Anda adalah maksimum (pedantically, supremum) di atas semua input s (cocok dengan regex yang ditentukan di atas) dari n (s) / panjang (s) . Jika Anda tidak dapat menghitung ini dengan tepat, Anda dapat menggunakan batas atas terendah yang dapat Anda buktikan.
  • Anda dapat memutus ikatan jika Anda dapat membuktikan bahwa algoritme Anda menggunakan penugasan yang asimptot lebih sedikit (ini dapat terjadi bahkan jika Anda memiliki skor yang sama, lihat di bawah). Jika Anda tidak dapat melakukan ini, Anda dapat memutuskan hubungan dengan menunjukkan bahwa Anda menggunakan lebih sedikit memori tambahan. Namun, kondisi tie-break pertama selalu diutamakan.
  • Untuk beberapa input setiap karakter perlu diubah, jadi tidak mungkin untuk mencetak kurang dari 1.
  • Saya dapat memikirkan algoritma sederhana dengan skor 2 (tapi saya tidak memasukinya).

Catatan tentang suprema dan ikatan

  • Supremum satu set angka adalah angka terkecil yang tidak lebih kecil dari angka itu. Ini sangat mirip dengan set maksimum, kecuali bahwa beberapa set infinite seperti {2/3, 3/4, 4/5, 5/6, ...} tidak memiliki elemen maksimum tunggal, tetapi masih akan memiliki supremum, dalam hal ini 1.
  • Jika Anda berhasil "menyimpan" hanya jumlah tugas yang konstan pada algoritme skor 2 saya (katakanlah), skor Anda akan tetap 2, karena Anda akan mendapatkan mendekati 2 sewenang-wenang semakin besar input yang Anda dapatkan. Namun, Anda menang pada tie-break jika itu yang terjadi.
Ben Millwood
sumber
1
Saya akan sedikit sedih jika semua ini datang ke pengajuan skor-2 tie-breaking pada penggunaan memori mereka. Saya sebagian besar memposting pertanyaan ini bertanya-tanya apakah ada yang berhasil mencetak skor kurang dari 2.
Ben Millwood
1
Saya tidak memiliki bukti formal, tetapi intuisi saya mengatakan kepada saya bahwa dengan pembatasan ruang konstan, tidak mungkin untuk melakukan lebih baik daripada 2. Jika Anda hanya menghitung deklarasi variabel untuk ruang, saya bisa menipu dan membuat fungsi rekursif. tapi itu hanya menyamarkan little-omega(1)ruang dengan meletakkannya di tumpukan.
salah
2
@ bacchusbeale ya, tapi ini memori ekstra yang konstan.
Martin Ender
4
Jika Anda benar-benar menegakkan aturan maka tidak mungkin untuk menulis program seperti itu. String bisa panjang sewenang-wenang dan Anda akan membutuhkan setidaknya beberapa jenis variabel yang menyimpan indeks. Jadi saya hanya perlu membuat string cukup panjang untuk melampaui batas variabel Anda. Untuk berhasil menyimpan setidaknya satu indeks, memori yang dibutuhkan Anda akan tumbuh dengan panjang string.
IchBinKeinBaum
3
@IchBinKeinBaum benar, tidak mungkin untuk melakukan tugas ini dengan O(1)ruang tambahan. Anda perlu O(log n)ruang untuk menyimpan posisi indeks, karena integer k-bit hanya dapat menyimpan di dalamnya untuk string panjang hingga 2^k. Membatasi panjang string membuat tantangan agak tidak ada gunanya, karena setiap algoritma akan membutuhkan O(1)ruang dengan cara ini.
Dennis

Jawaban:

4

Python, Nilai: 2 1,5 1,25

Ini adalah kombinasi langsung antara jawaban primo dan jawaban saya. Jadi kredit untuknya juga!

Buktinya masih dalam proses, tapi ini kode untuk bermain! Jika Anda dapat menemukan contoh balasan skor lebih besar dari 1,25 (atau jika ada bug), beri tahu saya!

Saat ini kasus terburuk adalah:

aa ... aa dcb ... cbd

di mana ada persis n dari masing-masing huruf "a", "b", "c", dan "" (spasi), dan tepat dua "d" s. Panjang string adalah 4n + 2 dan jumlah tugas adalah 5n + 2 , memberikan skor 5/4 = 1,25 .

Algoritma ini bekerja dalam dua langkah:

  1. Temukan kitu string[k]dan string[n-1-k]merupakan batasan kata
  2. Jalankan algoritma pembalikan kata apa pun pada string[:k]+string[n-1-k:](yaitu, rangkaian karakter pertama kdan terakhir k) dengan modifikasi kecil.

di mana npanjang tali.

Peningkatan yang diberikan algoritma ini berasal dari "modifikasi kecil" pada Langkah 2. Pada dasarnya adalah pengetahuan bahwa dalam string gabungan, karakter pada posisi kdan k+1merupakan batas kata (yang berarti spasi atau karakter pertama / terakhir dalam sebuah kata), dan jadi kita bisa langsung mengganti karakter di posisi kdan k+1dengan karakter yang sesuai di string terakhir, menyimpan beberapa tugas. Ini menghapus kasus terburuk dari algoritma pembalikan kata host

Ada kasus-kasus di mana kita tidak dapat menemukan itu k, dalam hal itu, kita hanya menjalankan "algoritma pembalikan kata" pada seluruh string.

Kode ini panjang untuk menangani keempat kasus ini dalam menjalankan kata pembalikan algoritma pada string "concatenated":

  1. Kapan ktidak ditemukan ( f_long = -2)
  2. Kapan string[k] != ' ' and string[n-1-k] != ' '( f_long = 0)
  3. Kapan string[k] != ' ' and string[n-1-k] == ' '( f_long = 1)
  4. Kapan string[k] == ' ' and string[n-1-k] != ' '( f_long = -1)

Saya yakin kode ini dapat dipersingkat. Saat ini lama karena saya tidak memiliki gambaran yang jelas tentang keseluruhan algoritma pada awalnya. Saya yakin orang dapat mendesainnya untuk diwakili dalam kode yang lebih pendek =)

Contoh dijalankan (pertama adalah milik saya, kedua adalah milik primo):

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

Anda dapat melihat bahwa skornya hampir sama, kecuali untuk kasus terburuk dari algoritma pembalikan kata host pada contoh ketiga, yang mana pendekatan saya menghasilkan skor kurang dari 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, Nilai: 1,5

Jumlah tugas yang tepat dapat diperkirakan dengan rumus:

n <= 1,5 * panjang (string)

dengan kasus terburuk adalah:

abcdefghi jklmnopqrstuvwxyzzz

dengan 55 tugas pada string dengan panjang 37.

Idenya mirip dengan yang saya sebelumnya, hanya saja dalam versi ini saya mencoba untuk menemukan awalan dan akhiran pada batas kata paling banyak 1. Lalu saya menjalankan algoritma saya sebelumnya pada awalan dan akhiran itu (bayangkan mereka digabungkan) . Kemudian lanjutkan pada bagian yang belum diproses.

Misalnya, untuk kasus terburuk sebelumnya:

ab | ab | c

pertama-tama kita akan melakukan pembalikan kata pada "ab" dan "c" (4 tugas) menjadi:

c | ab | ab

Kita tahu bahwa di perbatasan dulu ruang (ada banyak kasus yang harus ditangani, tetapi Anda bisa melakukannya), jadi kami tidak perlu menyandikan ruang di batas, ini adalah peningkatan utama dari algoritma sebelumnya .

Lalu akhirnya kita jalankan di empat karakter tengah untuk mendapatkan:

cba ab

dalam total 8 tugas, optimal untuk kasus ini, karena semua 8 karakter berubah.

Ini menghilangkan kasus terburuk dalam algoritma sebelumnya karena kasus terburuk dalam algoritma sebelumnya dihilangkan.

Lihat beberapa contoh dijalankan (dan perbandingan dengan jawaban @ primo - ini adalah baris kedua):

Masukkan string: saya bisa melakukan apa saja
"Apa pun yang bisa aku": 20, 17
"apapun yang aku bisa": 17, 17
Masukkan string: abcdef ghijklmnopqrs
"ghijklmnopqrs fedcb a": 37, 25
"ghijklmnopqrs fedcb a": 31, 25
Masukkan string: abcdef ghijklmnopqrst
"ghijklmnopqrst fedcb a": 38, 26
"ghijklmnopqrst fedcb a": 32, 26
Masukkan string: abcdefghi jklmnozzzzzzzzzzzzzzzzz
"jklmnozzzzzzzzzzzzzzzzz ihgfedcb a": 59, 41
"jklmnozzzzzzzzzzzzzzzzz ihgfedcb a": 45, 41
Masukkan string: abcdefghi jklmnopqrstuvwxyzzz
"jklmnopqrstuvwxyzzz ihgfedcb a": 55, 37
"jklmnopqrstuvwxyzzz ihgfedcb a": 45, 37
Masukkan string: ab ababababababac
"cababababababa ab": 30, 30
"cababababababa ab": 31, 30
Masukkan string: ab abababababababc
"cbababababababa ab": 32, 32
"cbababababababa ab": 33, 32
Masukkan string: abc d abc
"abc d abc": 0, 9
"abc d abc": 0, 9
Masukkan string: abc dca
"acd abc": 6, 9
"acd abc": 4, 9
Masukkan string: abc ababababababc
"cbabababababa abc": 7, 29
"cbabababababa abc": 5, 29

jawaban primo umumnya lebih baik, walaupun dalam beberapa kasus saya dapat memiliki 1 poin keunggulan =)

Juga kodenya jauh lebih pendek daripada milikku, 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: asymptotically 2, dalam kasus normal jauh lebih sedikit

kode lama dihapus karena kendala ruang

Idenya adalah untuk iterate melalui setiap indeks, dan untuk setiap indeks i, kita mengambil karakter, menghitung posisi baru j, menghafal karakter pada posisi j, menetapkan karakter di iuntuk j, dan ulangi dengan karakter pada indeks j. Karena kita memerlukan informasi ruang untuk menghitung posisi baru, saya menyandikan ruang lama sebagai versi huruf besar dari surat baru, dan ruang baru sebagai '@'.

justhalf
sumber
Jika Anda dapat mengurangi jumlah kata dalam kasus terburuk menjadi dalam hal panjang string (katakanlah, length(string)/3dengan memaksa setiap kata dalam kasus terburuk setidaknya memiliki panjang 2), maka skor akan kurang dari 2 (dalam contoh di atas akan menjadi 1,67)
justhalf
1
Saya menambahkan konter swap ke tambang; milikmu benar-benar mengalahkan milikku untuk kasus terburuk (tetapi bukan kasus umum). Perlu menemukan cara untuk memperbaikinya;)
primo
Baris 127: if any(process_loop(...) for j in range(...))Tidakkah tugas dari loop proses ini perlu dihitung?
Primo
Itu tidak melakukan tugas apa pun. Jika Anda melihat, dry_runparameternya diatur ke non-nol (nilai ke i). Di dalam process_loop, jika dry_runbukan nol, itu tidak akan melakukan tugas apa pun.
justhalf
1
Saya pikir saya memiliki gambaran yang lebih baik sekarang. Pada dasarnya, dua metode berbeda dengan perilaku kasus terburuk yang berbeda digabungkan, untuk menghilangkan kasus terburuk untuk keduanya. Saya suka itu. Saya pikir secara umum, ini mungkin merupakan pendekatan terbaik untuk diambil, meskipun tampaknya dua (atau lebih) metode yang sama sekali berbeda dapat digabungkan untuk mengurangi kasus terburuk lebih jauh.
Primo
14

Perl, skor 1.3̅

Untuk setiap karakter non-spasi satu tugas dilakukan, dan untuk masing-masing karakter spasi dua tugas. Karena karakter spasi tidak dapat berjumlah lebih dari setengah jumlah total karakter, skor kasus terburuk adalah 1,5 .

Algoritma tidak berubah, tetapi saya dapat membuktikan batas atas yang lebih rendah. Mari kita buat dua pengamatan:

  1. Ruang langsung di seberang ruang tidak perlu ditukar.
  2. Kata-kata karakter tunggal langsung di seberang spasi tidak ditukar selama fase utama, tetapi hanya sekali di akhir.

Maka dapat dilihat bahwa 'kasus terburuk' teoretis dengan asimtotik 1/2 spasi sama sekali bukan kasus terburuk: 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

Sebenarnya, ini kasus yang cukup bagus.

Untuk mencegah pengamatan satu dan dua di atas, setiap kata satu karakter perlu direposisi ke tengah kata yang panjangnya tiga atau lebih karakter. Ini menyarankan kasus terburuk yang mengandung 1/3 spasi asimptotik: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

Atau setara, hanya kata-kata dua karakter: 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

Karena kasus terburuk berisi 1/3 spasi asimptot, skor kasus terburuk menjadi 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;

Sunting: Menambahkan penghitung swap, dan rasionya.

Input diambil dari stdin. Penggunaan sampel:

$ 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

metode

Untuk memulai, karakter pertama dari string dipindahkan ke tujuan akhirnya. Karakter yang baru diganti kemudian dipindahkan ke tujuannya, dll. Ini berlanjut sampai salah satu dari dua kondisi terpenuhi:

  1. Karakter harus ditukar dengan spasi.
    Ketika ini terjadi, karakter tidak ditukar dengan ruang, tetapi lebih ke posisi cermin ruang. Algoritma berlanjut dari posisi itu.
  2. Satu siklus telah tercapai.
    Ketika target kembali ke posisi awal awal dari siklus saat ini, karakter yang tidak ditukar berikutnya (atau lebih tepatnya, karakter yang tidak ditukar akan melakukan) perlu ditemukan. Untuk melakukan ini di bawah batasan memori konstan, semua swap yang telah dibuat sampai saat ini dilacak kembali.

Setelah fase ini, setiap karakter non-spasi telah dipindahkan paling banyak satu kali. Untuk menyelesaikan, semua karakter spasi ditukar dengan karakter di posisi cermin mereka, membutuhkan dua operasi penugasan per ruang.

primo
sumber
Wow, itu keren. Bisakah Anda menjelaskan mengapa menempatkan karakter ke posisi cermin ruang memberikan jawaban yang benar?
justhalf
1
@ Niklas, saya pikir itu tidak mungkin. Karena untuk melakukan backtracking Anda memerlukan informasi posisi ruang. Jika Anda mengganti informasi itu, Anda tidak dapat melakukan backtracking.
justhalf
1
Saya membuat perbandingan terhadap algoritma saya dalam jawaban saya di sini: codegolf.stackexchange.com/a/26952/16337
justhalf
1
@justhalf Di string terakhir, semua spasi akan berada di posisi cermin mereka. Oleh karena itu, kita dapat menggunakan posisi ini dengan aman untuk menyimpan karakter yang menggantikan ruang, dan mengubahnya di akhir.
Primo
1
Sudah selesai dilakukan dengan baik. Saya memiliki ide yang sama tetapi tidak berpikir untuk meninggalkan ruang di tempat dan kemudian cermin mereka.
IchBinKeinBaum
7

Ruby, skor 2

Sebagai starter, algoritma yang sangat mendasar. Pertama membalik seluruh string dan kemudian membalikkan setiap kata dalam string lagi. Dalam kasus terburuk (satu kata, bahkan jumlah karakter) skor menjadi 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

Pemakaian:

> revwords("hello there everyone")
"everyone there hello"
Howard
sumber
Mengapa Anda tidak menggunakan fungsi bawaan Ruby untuk membalikkan string? Apakah itu akan mengubah skor?
AL
use, s [a], s [b] = s [b], s [a]
Thaha kp
5

C ++: Skor 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
sumber
2
Saya mengujinya. Bekerja dengan baik!
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
]

Saya tidak jelas tentang penilaian untuk ini. Tidak ada tugas string langsung dalam kode ini. Semuanya ditangani oleh orang reverse/partyang melakukan pembalikan di dalam dan awalnya pada seluruh string.

Beberapa detail pada kode:

  • Loop melalui string ( series) hingga mencapaitail?

  • Pertama kali dalam loop melakukan kebalikan penuh dari string - reverse/part series tail series(yang sama dengan reverse series)

  • Kemudian balikkan setiap kata yang ditemukan pada iterasi lebih lanjut - reverse/part series find series space

  • Setelah kata habis ditemukan, lalu kembali tail seriessehingga akan membalik kata terakhir dalam string -reverse/part series tail series

Rebol memungkinkan melintasi string melalui pointer internal . Anda dapat melihatnya di series: next f(pindahkan pointer ke spasi setelah mulai dari kata berikutnya) dan series: head series(reset pointer kembali ke kepala).

Lihat Seri untuk info lebih lanjut.

Contoh penggunaan di konsol 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
sumber
Pada pass pertama setiap karakter diposisikan ulang satu kali, dan pada pass kedua setiap karakter non-spasi diposisikan kembali. Untuk masukan sewenang-wenang besar dengan relatif sedikit ruang, skor mendekati 2.
primo
2

C: Skor 2

Ini hanya membalik seluruh string sekali kemudian membalikkan setiap kata.

#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
sumber
3
Ini adalah jawaban hanya kode. Pertimbangkan untuk menambahkan penjelasan tentang apa yang terjadi dalam kode Anda.
Justin
1

Python: skor 2

Hampir identik dengan algoritma Howard, tetapi melakukan langkah pembalikan secara terbalik (pertama membalik kata kemudian membalik seluruh string). Digunakan memori tambahan adalah 3 variabel byte-size: i, j, dan t. Secara teknis, finddan lenmenggunakan beberapa variabel internal, tetapi mereka dapat dengan mudah digunakan kembali iatau jtanpa kehilangan fungsi.

edit cepat: menghemat penugasan string dengan hanya bertukar ketika karakter berbeda, jadi saya bisa mengambil beberapa poin tambahan dari catatan # 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))
salah
sumber
1

Batch

Saya akui tidak sepenuhnya memahami skor (saya pikir itu dua) .. tapi saya akan mengatakan - itu yang terjadi .

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

Input diambil sebagai nilai input standar pertama, dan karenanya perlu dikelilingi oleh tanda kutip -
panggilan: script.bat "hello there everyone"
keluar: everyone there hello.

Mungkin orang lain bisa menilai saya (dengan asumsi saya belum, dengan cara lain, mendiskualifikasi diri saya).

hapus clemeat
sumber
-2

Javascript

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

Pemakaian:

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

Saya mendapatkan perasaan aneh saya melewatkan sesuatu ...

Cepat
sumber
3
Ya, ini tidak ada di tempat, karena Anda tidak mengubah string input. Karena itu tidak mungkin di JavaScript, Anda perlu meniru string dengan array karakter (mis. Integer titik kode atau string karakter tunggal).
Martin Ender
Lebih tepatnya, ini menggunakan banyak dan banyak ruang tambahan.
Ben Millwood