Зворотний порядок слів у рядку


17

Завдання

  • Вам надається змінний рядок, який відповідає [a-z]+( [a-z]+)*.
  • Ви повинні вимкнути його в рядок, який містить ті самі слова, але у зворотному порядку, щоб "привіт там всі" став "всім привіт".
  • Вам заборонено використовувати більше постійної кількості додаткової пам'яті (тому не копіюйте весь рядок або будь-яке ціле слово в буфер, який ви тільки що виділили).
  • Часових обмежень немає. Бути безнадійно неефективним не зашкодить вашому балу.
  • Якщо ваша обрана мова не дозволяє мутувати рядки, масиви символів є прийнятною заміною.

Ваш рахунок

  • Ваша оцінка зараховується виключно на кількість присвоєних вами елементів рядків (найкращі невеликі бали). Якщо ви використовуєте функцію бібліотеки, яка записує в рядок, її записи також враховуються.
  • Припустимо, кількість завдань, необхідних для введення s, - це n (s) . Тоді ваш бал - максимальний (педантично, супремум) для всіх входів s (відповідний регексу, зазначеному вище) n (s) / length (s) . Якщо ви не можете точно обчислити це, ви можете використовувати нижню верхню межу, яку ви можете довести.
  • Ви можете розірвати краватку, якщо зможете довести, що ваш алгоритм використовує асимптотично менше завдань (це може статися, навіть якщо у вас однаковий бал, див. Нижче). Якщо ви не можете цього зробити, ви можете розірвати краватку, показавши, що використовуєте менше додаткової пам'яті. Однак перша умова перерви завжди має перевагу.
  • Для деяких входів потрібно змінити кожного символу, тому неможливо набрати менше 1.
  • Я можу придумати простий алгоритм з оцінкою 2 (але я його не ввожу).

Примітки про верховенство та зв'язки

  • Супріум з набору чисел - найменше число, яке не менше, ніж будь-яке з них. Це дуже схоже на максимум набору, за винятком того, що деякі нескінченні множини, такі як {2/3, 3/4, 4/5, 5/6, ...}, не мають жодного максимального елемента, але все одно матимуть надсумок, у цьому випадку 1.
  • Якщо вам вдасться "зберегти" лише постійну кількість завдань за моїм алгоритмом оцінки 2 (скажімо), ваш рахунок все одно буде 2, тому що ви довільно наблизитеся до 2, чим більший ваш внесок. Однак ви виграєте на тай-брейку, якщо справа дойде до цього.

1
Мені було б трохи сумно, якби все це зводилося до кращого результату 2-х подань щодо їхньої пам’яті. Я в основному розмістив це питання, цікавлячись, чи вдасться кому набрати менше 2
Бен Міллвуд

1
У мене немає офіційного підтвердження, але моя інтуїція підказує мені, що з обмеженням постійного простору зробити це краще, ніж 2. Якщо ви будете рахувати лише мінливі декларації для простору, я можу обдурити і зробити рекурсивну функцію. але це лише замаскує little-omega(1)простір, поставивши його на стек.
неправильно

2
@bacchusbeale так, але це постійна додаткова пам'ять.
Мартін Ендер

4
Якщо ви суворо виконуєте правила, то писати таку програму неможливо. Рядок може мати довільну довжину, і вам знадобиться хоча б якась змінна, яка зберігає індекс. Тому я просто повинен зробити рядок досить довгим, щоб перевищити межі вашої змінної. Для успішного зберігання хоча б одного індексу потрібна пам'ять зростатиме з довжиною рядка.
IchBinKeinBaum

3
@IchBinKeinBaum має рацію, неможливо виконати це завдання з O(1)додатковим простором. Вам потрібен O(log n)простір для зберігання позиції індексу, оскільки k-бітове ціле число може зберігати в них лише рядки довжиною до 2^k. Обмеження довжини рядків робить виклик досить безглуздим, оскільки кожен алгоритм потребує O(1)простору таким чином.
Денніс

Відповіді:


4

Пітон, Оцінка: 2 1,5 1,25

Це пряме поєднання між відповіддю примо і моєю відповіддю. Тож кредити і йому!

Доказ ще триває, але ось код, з яким потрібно грати! Якщо ви можете знайти приклад зустрічного балу більше 1,25 (або якщо помилка), дайте мені знати!

Нині найгірший випадок:

aa ... aa dcb ... cbd

де з кожної літери є рівно n літер "a", "b", "c" і "" (пробіл), і рівно дві "d" s. Довжина струни - 4n + 2, а кількість присвоєнь - 5n + 2 , що дає оцінку 5/4 = 1,25 .

Алгоритм працює в два етапи:

  1. Знайдіть kтаке, що string[k]і string[n-1-k]є межею слів
  2. Запустіть будь-який алгоритм реверсування слів string[:k]+string[n-1-k:] (тобто об'єднання першої kта останньої kсимволів) з невеликою модифікацією.

де nдовжина струни.

Удосконалення цього алгоритму відбувається завдяки "невеликій модифікації" на кроці 2. Це, в основному, знання про те, що в об'єднаному рядку символи розташовані kі k+1є межами слова (це означає, що це пробіли або перший / останній символ у слові), і тому ми можемо безпосередньо замінити символи в положенні kтаk+1 відповідним символом у заключній рядку, заощадивши кілька завдань. Це видаляє найгірший випадок з алгоритму зворотного перегляду слів-хостів

Бувають випадки, коли насправді такого знайти неможливо k , у такому випадку ми просто запускаємо «алгоритм зміни будь-якого слова» у цілому рядку.

Код довгий, щоб обробляти ці чотири випадки, коли запускається алгоритм зворотного перетворення слів у рядку "з'єднаний":

  1. Коли kне знайдено ( f_long = -2)
  2. Коли string[k] != ' ' and string[n-1-k] != ' '( f_long = 0)
  3. Коли string[k] != ' ' and string[n-1-k] == ' '( f_long = 1)
  4. Коли string[k] == ' ' and string[n-1-k] != ' '( f_long = -1)

Я впевнений, що код можна скоротити. В даний час це довго, тому що я не мав чіткої картини всього алгоритму на початку. Я впевнений, що можна зробити так, щоб він був представлений у коротшому коді =)

Вибірка зразка (перший - мій, другий - примо)

Введіть рядок: a bc def ghij
"ghij def bc a": 9, 13, 0,692
"ghij def bc a": 9, 13, 0,692
Введіть рядок: ab cdefghijklmnopqrstuvw xyz
"zyxwvutsrqponmlkjihgf edc ab": 50, 50, 1.000
"zyxwvutsrqponmlkjihgf edc ab": 51, 50, 1.020
Введіть рядок: abcdefg hijklmnopqrstuvwx
"hijklmnopqrstuvwx gfedcb a": 38, 31, 1.226
"hijklmnopqrstuvwx gfedcb a": 38, 31, 1.226
Введіть рядок: 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
Введіть рядок: 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

Ви можете бачити, що оцінка майже однакова, за винятком найгіршого випадку алгоритму перевертання хост-слів у третьому прикладі, для якого мій підхід дає оцінку менше 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()

Пітон, Оцінка: 1.5

Точна кількість завдань може бути приблизна за формулою:

n <= 1,5 * довжина (рядок)

при цьому найгірший випадок:

abcdefghi jklmnopqrstuvwxyzzz

з 55 завданнями на рядку довжиною 37.

Ідея схожа на попередню, якраз у цій версії я намагався знайти префікс і суфікс у межі слів із різницею довжини не більше 1. Потім я запускаю свій попередній алгоритм на цьому префіксі та суфіксі (уявіть їх як об'єднані) . Потім продовжуйте на необроблену частину.

Наприклад, для попереднього найгіршого випадку:

ab | ab | c

ми спочатку зробимо сторнування слів на "ab" та "c" (4 завдання):

c | ab | ab

Ми знаємо, що на кордоні це був простір (є багато випадків, з якими можна обробляти, але ви можете це зробити), тому нам не потрібно кодувати простір на кордоні, це головне вдосконалення від попереднього алгоритму .

Потім нарешті ми біжимо на середину чотирьох символів, щоб отримати:

cba ab

загалом 8 завдань, що є оптимальним для даного випадку, оскільки всі 8 символів змінилися.

Це виключає найгірший випадок у попередньому алгоритмі, оскільки найгірший випадок у попередньому алгоритмі виключається.

Дивіться деякий зразок запуску (і порівняння з відповіддю @ primo - його другий рядок):

Введіть рядок: я можу робити все, що завгодно
"все, що я можу": 20, 17
"все, що я можу": 17, 17
Введіть рядок: abcdef ghijklmnopqrs
"ghijklmnopqrs fedcb a": 37, 25
"ghijklmnopqrs fedcb a": 31, 25
Введіть рядок: abcdef ghijklmnopqrst
"ghijklmnopqrst fedcb a": 38, 26
"ghijklmnopqrst fedcb a": 32, 26
Введіть рядок: abcdefghi jklmnozzzzzzzzzzzzzzz
"jklmnozzzzzzzzzzzzzzzzz ihgfedcb a": 59, 41
"jklmnozzzzzzzzzzzzzzzzz ihgfedcb a": 45, 41
Введіть рядок: abcdefghi jklmnopqrstuvwxyzzz
"jklmnopqrstuvwxyzzz ihgfedcb a": 55, 37
"jklmnopqrstuvwxyzzz ihgfedcb a": 45, 37
Введіть рядок: ab ababababababac
"кабабабабаба аб": 30, 30
"кабабабабаба аб": 31, 30
Введіть рядок: ab ababababababac
"cbababababababa ab": 32, 32
"cbababababababa ab": 33, 32
Введіть рядок: abc d abc
"abc d abc": 0, 9
"abc d abc": 0, 9
Введіть рядок: abc dca
"acd abc": 6, 9
"acd abc": 4, 9
Введіть рядок: abc abababababac
"cbabababababa abc": 7, 29
"cbabababababa abc": 5, 29

Відповідь примо, як правило, краща, хоча в деяких випадках я можу мати 1 бал переваги =)

Також його код набагато коротший, ніж мій, ха-ха.

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, Оцінка: асимптотично 2, у звичайному випадку набагато менше

старий код видалено через обмеження місця

Ідея полягає в тому , щоб перебирати кожен індекс, і для кожного індексу i, ми беремо характер, обчислити нове положення j, запам'ятайте символ в позиції j, привласнити символ , iщоб j, і повторіть з символом з індексом j. Оскільки нам потрібна інформація про простір для обчислення нової позиції, я кодую старий простір як верхню версію нової літери, а новий простір як "@".


Якщо ви можете зменшити кількість слів у гіршому випадку, щоб вони були з точки зору довжини рядка (скажімо, щоб length(string)/3змусити кожне слово в гіршому випадку як мінімум бути довжиною 2), то оцінка буде менше 2 (у прикладі вище це буде 1,67)
якразполовина

1
Я додав мінний лічильник; ваш насправді бив мою за найгірший випадок (але не загальний випадок). Потрібно знайти спосіб це виправити;)
примі

Рядок 127: if any(process_loop(...) for j in range(...))Чи не потрібно вважати завдання з цих циклів обліку?
примо

Це не робить ніякого завдання. Якщо ви бачите, dry_runпараметр встановлюється не-нульовим (значення до i). Всередині process_loop, якщо dry_runвін не дорівнює нулю, він не виконає жодного завдання.
justhalf

1
Я думаю, я зараз маю кращу картину. По суті, два різних методи з різною поведінкою в гіршому випадку поєднуються, щоб усунути найгірший випадок для обох. Мені це подобається. Я думаю, що в цілому це може бути найкращий підхід, хоча, мабуть, два (або більше) абсолютно різних методів можуть бути поєднані, щоб ще більше зменшити гірший випадок.
примо

14

Perl, оцінка 1,3̅

Для кожного непробільного символу виконується одне завдання, а для кожного символу пробілу - два призначення. Оскільки символи пробілу не можуть становити більше половини від загальної кількості символів, найгірший бал становить 1,5 .

Алгоритм не змінився, але я можу довести нижню верхню межу. Зробимо два спостереження:

  1. Проміжки безпосередньо від пробілів не потрібно міняти місцями.
  2. Слова з одним символом безпосередньо через пробіли не змінюються під час головної фази, а лише в кінці кінця.

Тоді можна побачити, що теоретичний «найгірший випадок» з асимптотикою 1/2 проміжків зовсім не гірший: 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

Насправді це досить непоганий випадок.

Щоб запобігти спостереженню один і два вище, кожне слово з одним символом потрібно буде переставити в середину слова довжиною три або більше символів. Це може запропонувати найгірший випадок, що містить асимптотичні 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

Або рівнозначно, лише двозначні слова: 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

Оскільки найгірший випадок містить асимптотично 1/3 пробілів, найгірший бал стає 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;

Редагувати: Додано лічильник заміни та коефіцієнт.

Введення взято з stdin. Використання зразка:

$ 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

Метод

Для початку перший символ рядка переміщується до його кінцевого пункту призначення. Тоді щойно замінений персонаж переміщується до місця призначення тощо. Це триває, поки не буде виконано одну з двох умов:

  1. Персонаж повинен бути поміщений пробілом.
    Коли це відбувається, персонаж є не змінюється місцями, а скоріше у дзеркальному положенні простору. Алгоритм продовжується з цього положення.
  2. Досягнуто циклу.
    Коли ціль повертається у початкове початкове положення поточного циклу, необхідно знайти наступний незамінений символ (а точніше, будь-який незамінений символ). Для цього в умовах постійних обмежень пам'яті всі зміни, які були зроблені до цього моменту, відстежуються заднім числом.

Після цієї фази кожен непробільний символ переміщений не більше одного разу. Для завершення всі символи пробілу поміняються місцями з символами в їх дзеркальному положенні, вимагаючи двох операцій з призначення на пробіл.


Ого, це круто. Чи можете ви пояснити, чому розміщення персонажа в дзеркальному положенні простору дає правильну відповідь?
щойнополовина

1
@Niklas, я не думаю, що це можливо. Тому що для зворотного відстеження вам потрібна інформація про місце місця. Якщо ви перекриєте цю інформацію, ви не можете робити зворотний трек.
якраз половина

1
Я роблю порівняння зі своїм алгоритмом у своїй відповіді тут: codegolf.stackexchange.com/a/26952/16337
justhalf

1
@justhalf У заключному рядку всі пробіли будуть у своєму дзеркальному положенні. Тому ми можемо сміливо використовувати цю позицію для зберігання символу, який замінює пробіл, і перемикати їх в кінці.
примо

1
Молодці. Я мав подібну ідею, але не думав просто залишити пробіли на місці, а потім віддзеркалити їх.
IchBinKeinBaum

7

Рубі, оцінка 2

Як стартер дуже базовий алгоритм. Він спочатку перевертає весь рядок, а потім знову повертає кожне слово в рядку. У гіршому випадку (одне слово, парна кількість символів) оцінка стає 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

Використання:

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

Чому ви не використовували вбудовану функцію Ruby, щоб повернути рядок? Чи це змінить бал?
AL

використання, s [a], s [b] = s [b], s [a]
Thaha kp

5

C ++: оцінка 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
Я тестував це. Добре працює!
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
]

Мені незрозуміло підрахунок за це. У цьому коді немає прямого призначення рядків. Все обробляється тим, хто reverse/partробить реверс на місці всередині і спочатку на цілій нитці.

Деякі деталі коду:

  • Проведіть цикл через рядок ( series), поки він не досягнеtail?

  • Перший раз у циклі роблять повний зворотний рядок - reverse/part series tail series(що те саме, що reverse series)

  • Потім переверніть кожне слово, знайдене під час подальших ітерацій - reverse/part series find series space

  • Після того, як вичерпане слово знайде, тоді поверніться, tail seriesщоб воно перевернуло останнє слово в рядку -reverse/part series tail series

Rebol дозволяє проходити рядок через внутрішній покажчик . Ви можете побачити це на series: next f(переміщення вказівника на пробіл та початок наступного слова) таseries: head series (скидання вказівника назад на голову).

Докладніше див. У серії .

Приклад використання в консолі 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"

На першому проході кожен символ репозиціонується один раз, а на другому проході кожен непробільний символ знову репозиціонується. Для довільно великого вкладу з відносно невеликим пробілом, оцінка наближається до 2.
прим

2

С: Оцінка 2

Це просто перевертає весь рядок один раз, а потім перевертає кожне слово.

#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
Це відповідь лише для коду. Подумайте про додавання пояснення того, що відбувається у вашому коді.
Джастін

1

Пітон: оцінка 2

Практично ідентичний алгоритму Говарда, але робить кроки перевороту у зворотному порядку (спочатку перевертає слова, потім перевертає цілий рядок). Додаткові б пам'яті 3 змінні байти розміру: i, jі t. Технічно findі lenвикористовуються деякі внутрішні змінні, але вони так само легко можуть повторно використовуватись iабо jбез втрати функції.

швидке редагування: економлять на рядкових завданнях, лише змінюючи місцями, коли символи відрізняються, тому я можу отримати декілька додаткових очок із примітки №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

Партія

Я визнаю, що не повністю розумію оцінку (я думаю, це два) .. але я скажу - це все робить .

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

Введення приймається як перше стандартне вхідне значення, і тому його слід оточити лапками -
call: script.bat "hello there everyone"
out : everyone there hello.

Можливо, хтось інший може забити мене (якщо припустити, що я не дискваліфікував себе).


-2

Javascript

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

Використання:

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

У мене виникає дивне відчуття, що я щось пропустив ...


3
Так, це не на місці, тому що ви не змінюєте вхідний рядок. Оскільки це неможливо в JavaScript, вам потрібно емулювати рядки з масивом символів (тобто цілими кодовими точками або односимвольними рядками).
Мартін Ендер

Більш того, він використовує багато і багато додаткового простору.
Бен Мілвуд
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.