문자열 내에서 단어의 역순


17

작업

  • 일치하는 변경 가능한 문자열이 제공 [a-z]+( [a-z]+)*됩니다.
  • "hello there everyone"이 "모든 사용자 hello"가되도록 동일한 단어를 포함하는 문자열로 변환해야하지만 역순으로해야합니다.
  • 일정량 이상의 추가 메모리를 사용할 수 없습니다 (따라서 전체 문자열 또는 전체 단어를 방금 할당 한 버퍼에 복사하지 마십시오).
  • 시간 제약이 없습니다. 절망적으로 비효율적 인 것이 점수에 해를 끼치 지 않습니다.
  • 선택한 언어로 문자열의 변이를 허용하지 않는 경우 문자 배열을 사용할 수 있습니다.

너의 점수

  • 점수는 순전히 문자열 요소에 대한 어설 션 수에 따라 계산됩니다 (작은 점수가 가장 좋음). 문자열에 쓰는 라이브러리 함수를 사용하면 쓰기도 계산됩니다.
  • 당신이 입력에 필요한 과제의 수를 가정 입니다 N (들) . 그런 다음 점수는 n (s) / length (s) 모든 입력 ( 위에 지정된 정규 표현식과 일치 )에 대한 최대 (자기 적으로, 최고 ) 입니다. 이를 정확하게 계산할 수 없으면 증명할 수있는 가장 낮은 상한을 사용할 수 있습니다.
  • 알고리즘이 무의식적으로 적은 수의 할당을 사용한다는 것을 증명할 수 있다면 동점을 깰 수 있습니다 (이는 동일한 점수를 가진 경우에도 발생할 수 있습니다 (아래 참조)). 이 작업을 수행 할 수없는 경우 추가 메모리 사용을 줄임으로써 연결을 끊을 수 있습니다. 그러나 첫 번째 타이 브레이크 조건이 항상 우선합니다.
  • 일부 입력의 경우 모든 문자를 변경해야하므로 1보다 작은 점수를 매길 수 없습니다.
  • 점수 2의 간단한 알고리즘을 생각할 수 있습니다 (그러나 입력하지는 않습니다).

슈프림과 관계에 대한 메모

  • 일련의 숫자 중 가장 작은 숫자는 그보다 작은 숫자입니다. {2/3, 3/4, 4/5, 5/6, ...}와 같은 일부 무한 세트에는 단일 최대 요소가 없지만 여전히 최상위가 있지만, 이 경우 1.
  • 내 점수 2의 알고리즘 (예 :)에 대해 일정한 수의 할당 만 "저장"하는 경우 입력이 커질수록 임의로 2에 가까워 지므로 점수는 여전히 2입니다. 그러나 타이 브레이크에서는 승리합니다.

1
이 모든 것이 그들의 메모리 사용량에 대해 2 점수를 넘겨 주면 조금 슬프다. 나는 대부분 2 명 미만의 점수를 낼 수 있을지 궁금해하는이 질문을 게시했습니다.
Ben Millwood

1
공식적인 증거는 없지만 상수 공간 제한으로 2보다 더 나은 것은 불가능하다는 직감이 있습니다. 공간에 대한 변수 선언 만 계산하면 속임수와 재귀 함수를 만들 수 있습니다. 그러나 little-omega(1)그것은 스택에 놓아서 공간을 가장합니다 .
wrongu

2
@bacchusbeale 네,하지만 지속적인 추가 메모리입니다.
마틴 엔더

4
규칙을 엄격히 적용하면 그러한 프로그램을 작성할 수 없습니다. 문자열은 임의의 길이 일 수 있으며 적어도 색인을 저장하는 변수가 필요합니다. 따라서 변수의 범위를 초과 할 정도로 문자열을 길게 만들어야합니다. 최소한 하나의 인덱스를 성공적으로 저장하려면 문자열 길이에 따라 필요한 메모리가 커집니다.
IchBinKeinBaum

3
@IchBinKeinBaum이 맞습니다 O(1). 이 공간 을 추가 공간 으로 수행하는 것은 불가능 합니다. O(log n)k 비트 정수는 최대 길이의 문자열에 대해서만 저장할 수 있기 때문에 인덱스 위치를 저장할 공간 이 필요 합니다 2^k. 모든 알고리즘은 O(1)이런 방식으로 공간 을 필요로하기 때문에 문자열의 길이를 제한하면 도전이 무의미 해진다.
Dennis

답변:


4

파이썬, 점수 : 2 1.5 1.25

이것은 primo의 답변과 나의 답변 사이 의 직접적인 조합입니다. 그래서 그에게도 크레딧!

증거는 아직 진행 중이지만 여기에 사용할 코드가 있습니다! 1.25보다 큰 점수의 카운터 예제를 찾거나 버그가있는 경우 알려주십시오.

현재 최악의 경우는 다음과 같습니다.

aa ... aa dcb ... cbd

여기서 "a", "b", "c"및 ""(공백) 문자 각각 정확히 n 개와 정확히 두 개의 "d"가 있습니다. 문자열의 길이는 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 단계의 "작은 수정"에서 비롯된 것입니다. 연결된 문자열에서 위치에있는 문자 kk+1단어 경계 (단어 에서 공백 또는 첫 번째 / 마지막 문자임을 의미) 는 지식입니다 . 그래서 우리는 직접 위치에 문자를 대체 할 수 kk+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)

코드를 줄일 수 있다고 확신합니다. 처음에는 전체 알고리즘에 대한 명확한 그림이 없었기 때문에 오래되었습니다. 더 짧은 코드로 표현되도록 디자인 할 수 있다고 확신합니다 :)

샘플 실행 (첫 번째는 내 것이고 두 번째는 프리모입니다) :

문자열 입력 : 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 dcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbc
"dcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbc
"dcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbc

세 번째 예에서 호스트 단어 역전 알고리즘의 최악의 경우를 제외하고 점수가 거의 동일하다는 것을 알 수 있습니다. 제 접근법은 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

길이가 37 인 문자열에 55 개의 할당이 있습니다.

아이디어는 이전의 아이디어와 비슷합니다.이 버전에서는 길이가 최대 1 인 단어 경계에서 접두사와 접미사를 찾으려고 시도했습니다. 그런 다음 접두사와 접미사에서 이전 알고리즘을 실행합니다 (연결 된 것으로 상상하십시오) . 그런 다음 처리되지 않은 부분을 계속하십시오.

예를 들어, 이전 최악의 경우 :

ab | ab | 씨

먼저 "ab"와 "c"(4 개의 과제)에 대해 단어 반전을 수행합니다.

c | ab | ab

우리는 경계에서 공간이었던 것으로 알고 있습니다 (처리 해야하는 많은 경우가 있지만 그렇게 할 수 있습니다). 경계에서 공간을 인코딩 할 필요가 없습니다. 이것은 이전 알고리즘의 주요 개선 사항입니다. .

그런 다음 마지막으로 중간 4자를 실행하여 다음을 얻습니다.

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 jklmnozzzzzzzzzzzzzzzzzzzz
"jklmnozzzzzzzzzzzzzzzzz ihgfedcb a": 59, 41
"jklmnozzzzzzzzzzzzzzzzz ihgfedcb a": 45, 41
문자열 입력 : abcdefghi jklmnopqrstuvwxyzzz
"jklmnopqrstuvwxyzzz ihgfedcb a": 55, 37
"jklmnopqrstuvwxyzzz ihgfedcb a": 45, 37
문자열 입력 : ab ababababababac
"cababababababa ab": 30, 30
"cababababababa ab": 31, 30
문자열 입력 : ab abababababababc
"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 ababababababc
"cbabababababa abc": 7, 29
"cbabababababa abc": 5, 29

primo의 대답은 일반적으로 더 낫습니다.

또한 그의 코드는 내 코드보다 훨씬 짧습니다.

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()

파이썬, 점수 : 무증상 2, 보통의 경우 훨씬 적음

공간 제약으로 인해 오래된 코드가 제거되었습니다.

아이디어는 각 색인을 통해 반복하고, 각 인덱스에 대해 i, 우리는 문자를 가지고 새로운 위치를 계산 j, 위치에있는 문자를 기억 j에서 문자를 할당 i하는 j인덱스 문자로, 반복 j. 우리는 새로운 위치를 계산하기 위해 공간 정보가 필요하기 때문에 오래된 공간을 새로운 문자의 대문자 버전으로, 새로운 공간을 '@'로 인코딩합니다.


최악의 경우 단어 수를 문자열의 길이로 줄일 수 있다면 (예 length(string)/3를 들어 최악의 경우 각 단어를 적어도 길이 2 이상으로 강제하여) 점수가 다음보다 작습니다. 2 (위의 예에서는 1.67입니다)
justhalf

1
스왑 카운터를 추가했습니다. 당신은 실제로 최악의 경우 (그러나 일반적인 경우는 아님)에 대해 내 것을 이겼습니다. ;)
primo

127 행 : if any(process_loop(...) for j in range(...))이 프로세스 루프의 할당을 계산할 필요가 없습니까?
primo

그것은 할당을하지 않습니다. 표시되는 경우 dry_run매개 변수는 0이 아닌 값으로 설정됩니다 (값은 i). 내부에서 0이 아닌 process_loop경우 dry_run할당이 수행되지 않습니다.
Justhalf

1
나는 지금 더 나은 그림을 가지고 있다고 생각합니다. 본질적으로, 최악의 경우를 제거하기 위해 최악의 경우 동작이 다른 두 가지 방법이 결합됩니다. 나는 그것을 좋아한다. 나는 이것이 최악의 경우를 더 줄이기 위해 완전히 다른 두 가지 방법이 결합 될 수 있지만 일반적으로 이것이 최선의 접근 방법이라고 생각합니다.
primo

14

펄, 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과 2의 관찰을 방지하기 위해, 각각의 1- 문자 단어는 3 자 이상의 길이의 단어의 중간으로 재배치되어야합니다. 이것은 무조건 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 score가 됩니다 .

#!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. 사이클에 도달했습니다.
    대상이 현재 사이클의 초기 시작 위치로 돌아 오면 스왑되지 않은 다음 문자 (또는 스왑되지 않은 문자)가 있어야합니다. 일정한 메모리 제약 조건에서이 작업을 수행하려면이 시점까지 이루어진 모든 스왑이 역 추적됩니다.

이 단계 후에 공백이 아닌 각 문자가 최대 한 번 이동되었습니다. 완료하기 위해 모든 공백 문자가 미러 위치의 문자와 교체되므로 공간 당 두 개의 할당 작업이 필요합니다.


우아! 그거 멋진데. 캐릭터를 공간의 거울 위치에 놓는 것이 왜 정답인지 설명 할 수 있습니까?
justhalf

1
@Niklas, 가능하지 않다고 생각합니다. 역 추적을 수행하려면 공간 위치 정보가 필요합니다. 해당 정보를 재정의하면 역 추적을 수행 할 수 없습니다.
justhalf

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] 님의 [B] = S [B]는, s의 [A]
타하 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

레볼

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에 근접합니다.
primo

2

C : 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 점

Howard의 알고리즘과 거의 동일하지만 반전 단계를 반대로 수행합니다 (먼저 단어를 뒤집은 다음 전체 문자열을 뒤집습니다). 사용되는 추가 메모리는 3 바이트 크기 변수 ij, 및 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

자바 스크립트

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에서는 불가능하므로 문자 배열 (예 : 코드 포인트 정수 또는 단일 문자 문자열)로 문자열을 에뮬레이션해야합니다.
Martin Ender

요컨대, 많은 추가 공간을 사용합니다.
벤 밀우드
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.