Bir dizgideki sözcüklerin sırasını tersine çevirme


17

Görev

  • Size uygun bir değişken dize verilir [a-z]+( [a-z]+)*.
  • Bunu aynı kelimeleri içeren dizeye dönüştürmelisiniz, ancak ters sırada, "merhaba orada herkes" "oradaki herkes merhaba" olur.
  • Sabit miktarda ek bellek kullanmanıza izin verilmez (bu nedenle, dizenin tamamını veya herhangi bir kelimeyi yeni ayırdığınız bir ara belleğe kopyalamanız gerekmez).
  • Zaman kısıtlaması yoktur. Umutsuzca verimsiz olmak puanınıza zarar vermez.
  • Seçtiğiniz dil dizelerin değiştirilmesine izin vermiyorsa, karakter dizileri kabul edilebilir bir alternatiftir.

Puanın

  • Puanınız tamamen dize elemanlarına yaptığınız varsayımların sayısına bağlıdır (küçük puanlar en iyisidir). Dizeye yazan bir kitaplık işlevi kullanırsanız, onun yazma işlemi de sayılır.
  • Eğer giriş için gereken atamaların sayısı varsayalım ler ise n (ler) . O zaman puanınız n (s) / uzunluk (lar) ın tüm girişleri (yukarıda belirtilen normal ifadeyle eşleşir) üzerinden maksimum (bilgiçliksel olarak, üstünlük ) olur . Bunu tam olarak hesaplayamıyorsanız, ispatlayabileceğiniz en düşük üst sınırı kullanabilirsiniz.
  • Algoritmanızın asimptotik olarak daha az atama kullandığını kanıtlayabilirseniz, bir bağı kesebilirsiniz (bu, aynı puana sahip olsanız bile olabilir, aşağıya bakın). Bunu yapamazsanız, daha az ek bellek kullandığınızı göstererek bir bağı kırabilirsiniz. Bununla birlikte, ilk bağlantı kopma koşulu her zaman önceliklidir.
  • Bazı girişler için her karakterin değişmesi gerekir, bu nedenle 1'den az puan almak mümkün değildir.
  • Skor 2 ile basit bir algoritma düşünebilirsiniz (ama ben girmiyorum).

Suprema ve bağlarla ilgili notlar

  • Bir sayı kümesinin bir özeti, herhangi birinden daha küçük olmayan en küçük sayıdır. Bu, bir kümenin maksimumu gibidir, ancak {2/3, 3/4, 4/5, 5/6, ...} gibi bazı sonsuz kümelerin tek bir maksimum öğesi yoktur, ancak yine de bir supremum vardır, bu durumda 1.
  • Skor 2 algoritmam üzerinden sadece sabit sayıda ödevi "kaydetmeyi" başarırsanız, skorunuz yine de 2 olacaktır, çünkü girişinize keyfi olarak 2'ye yaklaşırsınız. Ancak, eğer bu gelirse tie-break kazanırsınız.

1
Bunların hepsi bellek kullanımıyla ilgili çığır açan skor-2 başvurularına gelirse biraz üzülürdüm. Çoğunlukla 2'den daha az gol
Ben Millwood

1
Resmi bir kanıtım yok, ama sezgilerim sabit alan kısıtlamasıyla 2'den daha iyi yapmanın imkansız olduğunu söylüyor. Sadece alan için değişken bildirimleri sayıyorsanız, hile yapabilir ve özyinelemeli bir işlev yapabilirim. ancak bu yalnızca little-omega(1)alanı yığına koyarak gizler .
wrongu

2
@bacchusbeale evet, ama sürekli ekstra bellek.
Martin Ender

4
Kuralları kesinlikle uygularsanız, böyle bir program yazmak imkansızdır. Dize isteğe bağlı uzunlukta olabilir ve en azından bir dizini depolayan bir değişken türüne ihtiyacınız olacaktır. Bu yüzden sadece dizginizi değişkeninizin sınırlarını aşacak kadar uzun yapmam gerekiyor. En az bir dizini başarıyla saklamak için gerekli belleğiniz dize uzunluğu ile büyüyecektir.
IchBinKeinBaum

3
@IchBinKeinBaum haklı, bu görevi O(1)ek alanla gerçekleştirmek imkansız . O(log n)Bir dizin konumu saklamak için alana ihtiyacınız vardır , çünkü bir k-bit tamsayı yalnızca uzunluktaki dizeler için saklayabilir 2^k. Dizelerin uzunluğunu sınırlamak zorluğu anlamsız hale getirir, çünkü her algoritma O(1)bu şekilde yer gerektirir .
Dennis

Yanıtlar:


4

Python, Puan: 2 1.5 1.25

Bu primo cevabı ve cevabım arasındaki doğrudan kombinasyon. Yani ona da kredi!

Kanıt hala devam ediyor, ama oynamak için kod burada! Eğer 1,25'ten daha büyük bir karşı örnek bulabilirseniz (veya bir hata varsa) bana bildirin!

Şu anda en kötü durum:

aa ... aa dcb ... cbd

burada "a", "b", "c" ve "" (boşluk) harflerinin tam olarak n'si ve tam olarak iki "d" harfi bulunur. Dizenin uzunluğu 4n + 2 ve atama sayısı 5n + 2'dir ve 5/4 = 1,25 puan verir .

Algoritma iki adımda çalışır:

  1. kÖyle bulun string[k]ve string[n-1-k]kelime sınırları
  2. Herhangi bir kelime tersine çevirme algoritmasını string[:k]+string[n-1-k:](yani, ilk kve son kkarakterlerin birleştirilmesi ) küçük değişikliklerle çalıştırın.

ndizenin uzunluğu nerede .

Bu algoritmanın sağladığı iyileştirme, 2. Adımdaki "küçük değişiklik" ten gelir. Temel olarak, birleştirilmiş dizede, konumdaki karakterlerin kve k+1kelime sınırları oldukları (yani bir sözcükte boşluk veya ilk / son karakter oldukları), ve bu nedenle doğrudan pozisyonda karakterleri yerini alabilir kve k+1birkaç atamaları harcamadan son dizesinde karşılık gelen karakteri ile. Bu, en kötü durumu ana makine tersine çevirme algoritmasından kaldırır

Gerçekten bulamadığımız durumlar vardır k, bu durumda, tüm dizede "herhangi bir kelime tersine çevirme algoritması" nı çalıştırırız.

Kod, "bitiştirilmiş" dizede tersine çevirme algoritmasını çalıştırırken bu dört durumu işlemek için uzun:

  1. Ne zaman kbulunamadı ( f_long = -2)
  2. Ne zaman string[k] != ' ' and string[n-1-k] != ' '( f_long = 0)
  3. Ne zaman string[k] != ' ' and string[n-1-k] == ' '( f_long = 1)
  4. Ne zaman string[k] == ' ' and string[n-1-k] != ' '( f_long = -1)

Eminim kod kısaltılabilir. Şu anda uzun çünkü başlangıçta tüm algoritmanın net bir resmine sahip değildim. Eminim bir kısa bir kodda temsil etmek için tasarlayabilirsiniz =)

Örnek çalışma (birincisi benim, ikincisi primo):

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

Üçüncü örnekte ana kelime tersine çevirme algoritmasının en kötü durumu dışında, yaklaşımımın 1,25'ten daha düşük bir puan verdiği, puanın neredeyse aynı olduğunu görebilirsiniz.

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, Puan: 1.5

Tam ödev sayısı aşağıdaki formülle tahmin edilebilir:

n <= 1.5 * uzunluk (dize)

en kötü durum:

abcdefghi jklmnopqrstuvwxyzzz

uzunluğu 37 olan dize üzerinde 55 atama ile.

Fikir öncekine benzer, sadece bu versiyonda en fazla uzunluk farkı olan kelime sınırlarında önek ve son ek bulmaya çalıştım. Sonra önceki algoritmamı bu önek ve sonek üzerinde çalıştırıyorum (birleştirilmiş olarak hayal edin) . Sonra işlenmemiş parçaya devam edin.

Örneğin, önceki en kötü durum için:

ab | ab | c

önce "ab" ve "c" (4 ödev) üzerinde kelime tersini yapacağız:

c | ab | ab

Sınırda eskiden boşluk olduğunu biliyoruz (ele alınması gereken birçok durum var, ancak bunu yapabilirsiniz), bu yüzden alanı sınırdaki kodlamak zorunda değiliz, bu önceki algoritmadan gelen ana gelişme .

Sonra nihayet orta dört karakter üzerinde koşuyoruz:

cba ab

toplam 8 ödevde, bu karakter için optimal, çünkü 8 karakterin hepsi değişti.

Önceki algoritmadaki en kötü durum ortadan kaldırıldığı için önceki algoritmadaki en kötü durum ortadan kaldırılır.

Bazı örnek çalışmalara bakın (ve @ primo'nun cevabı ile karşılaştırma - onun ikinci satırıdır):

Dize girin: her şeyi yapabilirim
"her şeyi yapabilirim": 20, 17
"her şeyi yapabilirim": 17, 17
Dize girin: abcdef ghijklmnopqrs
"ghijklmnopqrs fedcb a": 37, 25
"ghijklmnopqrs fedcb a": 31, 25
Dize girin: abcdef ghijklmnopqrst
"ghijklmnopqrst fedcb a": 38, 26
"ghijklmnopqrst fedcb a": 32, 26
Dize girin: abcdefghi jklmnozzzzzzzzzzzzzzzzz
"jklmnozzzzzzzzzzzzzzzzz ihgfedcb a": 59, 41
"jklmnozzzzzzzzzzzzzzzzz ihgfedcb a": 45, 41
Dize girin: abcdefghi jklmnopqrstuvwxyzzz
"jklmnopqrstuvwxyzzz ihgfedcb a": 55, 37
"jklmnopqrstuvwxyzzz ihgfedcb a": 45, 37
Dize girin: ab ababababababac
"cababababababa ab": 30, 30
"cababababababa ab": 31, 30
Dize girin: ab abababababababc
"cbababababababa ab": 32, 32
"cbababababababa ab": 33, 32
Dize girin: abc d abc
"abc d abc": 0, 9
"abc d abc": 0, 9
Dize girin: abc dca
"acd abc": 6, 9
"acd abc": 4, 9
Dize girin: abc ababababababc
"cbabababababa abc": 7, 29
"cbabababababa abc": 5, 29

primo'nun cevabı genellikle daha iyidir, ancak bazı durumlarda 1 puan avantajım olabilir =)

Ayrıca kodu benimkinden çok daha kısa, 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, Skor: asimptotik olarak 2, normal durumda çok daha az

alan kısıtlaması nedeniyle eski kod kaldırıldı

Fikir her endeks yinelemenize ve her dizin için i, biz, karakterini almak yeni konumu hesaplamak j, bulunduğu yerdeki karakteri ezberlemek jen karakterini atamak iiçin jdizindeki karakteri ile ve tekrar j. Yeni konumu hesaplamak için boşluk bilgisine ihtiyacımız olduğundan, eski alanı yeni harfin büyük harfli versiyonu ve yeni alanı '@' olarak kodlarım.


En kötü durumdaki kelime sayısını dizenin uzunluğu açısından azaltabiliyorsanız (örneğin, length(string)/3en kötü durumdaki her bir kelimeyi en azından bir şekilde en az 2 uzunlukta olmaya zorlayarak), o zaman puan daha az olacaktır. 2 (yukarıdaki örnekte 1.67 olacaktır)
14'te

1
Benim için bir takas sayacı ekledim; seninki aslında en kötü durum için benimkini dövüyor (ama genel durum için değil). Bunu düzeltmek için bir yol bulmalıyız;)
primo

Satır 127: if any(process_loop(...) for j in range(...))Bu işlem döngülerinden ödevlerin sayılması gerekmez mi?
primo

Bu herhangi bir görev yapmaz. Görüyorsanız, dry_runparametre sıfırdan farklı olarak ayarlanır (değer i). İçinde process_loop, eğerdry_run sıfırdan , herhangi bir atama yapmaz.
justhalf

1
Sanırım şimdi daha iyi bir resmim var. Esasında, her ikisi için de en kötü durumu ortadan kaldırmak için farklı en kötü durum davranışına sahip iki farklı yöntem birleştirilmiştir. Bunu sevdim. Genel olarak, bu, en kötü durumu daha da azaltmak için iki (veya daha fazla) tamamen farklı yöntem birleştirilebilecek gibi görünse de, bu en iyi yaklaşım olabilir.
primo

14

Perl, skor 1.3̅

Her boşluk olmayan karakter için bir atama yapılır ve her boşluk karakteri için iki atama yapılır. Boşluk karakterleri toplam karakter sayısının yarısından fazlasını oluşturamayacağından, en kötü durum puanı 1,5'tir .

Algoritma değişmedi, ancak bir alt üst sınırı kanıtlayabilirim. İki gözlem yapalım:

  1. Doğrudan boşlukların karşısındaki boşlukların değiştirilmesine gerek yoktur.
  2. Doğrudan boşlukların karşısındaki tek karakterli kelimeler ana aşamada değil, yalnızca bir kez sonunda değiştirilir.

Daha sonra asimptotik 1/2 boşluklu teorik 'en kötü durumun' en kötü durum olmadığı görülebilir: 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

Aslında, bu oldukça iyi bir durum.

Yukarıdaki bir ve iki gözlemi önlemek için, her bir karakterlik kelimenin üç veya daha fazla karakter uzunluğunda bir kelimenin ortasına yerleştirilmesi gerekir. Bu, asimptotik 1/3 boşluk içeren en kötü bir durumu önerecektir: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

Veya eşdeğer olarak, sadece iki karakterli kelimeler: 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

En kötü durum asimptotik 1/3 boşluk içerdiğinden, en kötü durum puanı 1,3̅ olur .

#!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;

Düzenle: Bir takas sayacı ve oran eklendi.

Giriş stdin'den alınır. Örnek kullanım:

$ 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

Yöntem

Başlamak için, dizenin ilk karakteri son hedefine taşınır. Daha sonra değiştirilen karakter, hedefine vb. Taşınır. Bu, iki koşuldan biri karşılanıncaya kadar devam eder:

  1. Karakter boşlukla değiştirilmelidir.
    Bu olduğunda, karakter değil boşlukla takas değil, uzayın ayna pozisyonuna. Algoritma bu konumdan devam eder.
  2. Bir döngüye ulaşıldı.
    Hedef, geçerli döngünün ilk başlangıç ​​konumuna döndüğünde, bir sonraki kaydırılmamış karakterin (veya daha doğrusu, değiştirilmemiş herhangi bir karakterin yapacağı) bulunması gerekir. Bunu sabit bellek kısıtlamaları altında yapmak için, bu noktaya kadar yapılan tüm takaslar geriye doğru izlenir.

Bu aşamadan sonra, boşluk olmayan her karakter en fazla bir kez taşınmıştır. Tamamlamak için, tüm boşluk karakterleri ayna konumlarındaki karakterlerle değiştirilir ve boşluk başına iki atama işlemi gerekir.


Vay canına, bu harika. Karakteri alanın ayna konumuna neden koymanın doğru cevabı verdiğini açıklayabilir misiniz?
justhalf

1
@Niklas, bunun mümkün olduğunu düşünmüyorum. Çünkü geri izlemeyi yapmak için boşluk konumu bilgisine ihtiyacınız vardır. Bu bilgileri geçersiz kılarsanız geri izleme yapamazsınız.
justhalf

1
Buradaki cevabımda
justhalf

1
@justhalf Son dizede, tüm boşluklar yansıtılmış konumlarında olacaktır. Bu nedenle, alanı değiştiren karakteri saklamak ve sonunda değiştirmek için bu konumu güvenle kullanabiliriz.
primo

1
Aferin. Benzer bir fikrim vardı ama boşlukları yerinde bırakıp sonra aynalamayı düşünmedim.
IchBinKeinBaum

7

Ruby, skor 2

Başlangıç ​​olarak çok temel bir algoritma. Önce tüm dizgiyi tersine çevirir, sonra dizgideki her sözcüğü yeniden ters çevirir. En kötü durumda (bir kelime, çift karakter sayısı) skor 2 olur.

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

Kullanımı:

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

Bir dizeyi ters çevirmek için neden Ruby yerleşik işlevini kullanmadınız? Skoru değiştirir mi?
AL

kullanım, s [a], s [b] = s [b], s [a]
Thaha kp

5

C ++: Puan 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
Test ettim. İyi çalışıyor!
bacchusbeale

2

Rebol'un

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
]

Bunun için puanlama konusunda net değilim. Bu kodda doğrudan dize ataması yoktur. Her şey, reverse/parttüm telin içinde ve başlangıçta yerinde tersine dönen bir şey tarafından ele alınır .

Kod hakkında bazı ayrıntılar:

  • Dize üzerinden döngü (series )tail?

  • Döngüde ilk kez dizenin tam tersini yapın - reverse/part series tail series (reverse series )

  • Sonra daha sonraki yinelemelerde bulunan her kelimeyi tersine çevirin - reverse/part series find series space

  • Bir kez bitkin kelime bulur sonra geri tail series böylece dizedeki son sözcüğü tersine çevirir -reverse/part series tail series

Rebol, bir dize ile dahili bir işaretçi üzerinden geçiş yapılmasına izin verir . Bunu şurada görebilirsiniz series: next f(sonraki kelimenin başlangıcında işaretçiyi boşluktan sonraya taşı) veseries: head series (işaretçiyi başa geri getirir) 'de görebilirsiniz.

Görmek fazla bilgi için Seri'ye .

Rebol konsolundaki kullanım örneği:

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

İlk geçişte her karakter bir kez yeniden konumlandırılır ve ikinci geçişte her boşluk olmayan karakter yeniden konumlandırılır. Göreceli olarak az alana sahip keyfi olarak büyük bir girdi için puan 2'ye yaklaşır.
primo

2

C: Puan 2

Bu, tüm dizeyi bir kez döndürür ve ardından her sözcüğü tersine çevirir.

#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
Bu sadece kodlu bir cevaptır. Kodunuzda neler olup bittiğine ilişkin bir açıklama ekleyin.
Justin

1

Python: skor 2

Howard'ın algoritması ile hemen hemen aynı, ancak tersine doğru adımları tersine çevirir (önce kelimeleri çevirir, sonra tüm dizeyi çevirir). Ek bellek kullanılan 3 bayt boyutunda değişkenlerin: i, jve t. Teknik olarak findve lenbazı dahili değişkenler kullanıyorlar, ancak kolayca yeniden kullanılabilirler iveya jherhangi bir işlev kaybı olmadan.

hızlı düzenleme: dize atamalarını yalnızca karakterler farklı olduğunda değiştirerek kaydetme, böylece not 2'den bazı ekstra puanlar alabilirim.

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

yığın

Puanlamayı tam olarak anlamadığımı itiraf edeceğim (sanırım iki) .. ama söyleyeceğim - bir şey yapıyor .

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

Giriş ilk standart giriş değeri olarak alınır ve bu nedenle tırnak işaretleri içine alınmalıdır -
çağrı:script.bat "hello there everyone"
Çıkış: everyone there hello.

Belki başka biri beni puanlayabilir (başka bir şekilde kendimi diskalifiye etmediğim varsayılırsa).


-2

JavaScript

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

Kullanımı:

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

Bir şeyi özlediğim garip duyguyu anlıyorum ...


3
Evet, bu yerinde değil, çünkü giriş dizesini değiştirmiyorsunuz. JavaScript'te bu mümkün olmadığından, dizeleri bir karakter dizisiyle (yani kod noktası tamsayıları veya tek karakter dizeleri) taklit etmeniz gerekir.
Martin Ender

Daha da önemlisi, çok fazla ek alan kullanır.
Ben Millwood
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.