インプレースの文字列内の単語の逆順


17

タスク

  • に一致する可変文字列が与えられ[a-z]+( [a-z]+)*ます。
  • 「hello there everyone」が「everyone there hello」になるように、同じ単語を含む文字列に逆順で変更する必要があります。
  • 一定量以上の追加メモリを使用することは許可されていません(したがって、割り当てたばかりのバッファに文字列全体または単語全体をコピーすることはできません)。
  • 時間の制約はありません。絶望的に非効率であることはあなたのスコアを傷つけません。
  • 選択した言語で文字列の変更が許可されていない場合は、文字の配列を使用できます。

あなたのスコア

  • スコアは、文字列要素に対して行った割り当ての数に基づいて純粋にカウントされます(小さいスコアが最適です)。文字列に書き込むライブラリ関数を使用すると、その書き込みもカウントされます。
  • 入力sに必要な割り当ての数がn(s)であるとします。次に、スコアは、n(s)/ length(s)のすべての入力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
規則を厳密に施行すると、そのようなプログラムを書くことはできません。文字列は任意の長さにすることができ、少なくともインデックスを保存する何らかの種類の変数が必要になります。そのため、変数の境界を超えるのに十分な長さの文字列を作成する必要があります。少なくとも1つのインデックスを正常に保存するには、必要なメモリが文字列の長さとともに増加します。
IchBinKeinBaum

3
@IchBinKeinBaumは正しいO(1)です。追加のスペースでこのタスクを実行することは不可能です。O(log n)インデックス位置を格納するためのスペースが必要です。なぜなら、kビット整数は最大長の文字列に対してのみ格納できるから2^kです。文字列の長さを制限すると、すべてのアルゴリズムがO(1)このようにスペースを必要とするため、チャレンジはかなり無意味になります。
デニス

回答:


4

Python、スコア:2 1.5 1.25

これは直接 primoの答えと私の答えの組み合わせです。彼にも感謝します!

証拠はまだ進行中ですが、ここで遊ぶコードがあります!1.25を超えるスコアの反例を見つけることができる場合(またはバグがある場合)、お知らせください!

現在、最悪のケースは次のとおりです。

aa ... aa dcb ... cbd

ここで、文字「a」、「b」、「c」、および「」(スペース)の正確にn個、および正確に2つの「d」があります。ストリングの長さは4n + 2で、割り当ての数は5n + 2であり5/4 = 1.25のスコアを与えます。

アルゴリズムは2つのステップで機能します。

  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にそのようなものを見つけることができない場合があります。その場合、文字列全体に対して「任意の単語反転アルゴリズム」を実行します。

このコードは、「連結された」文字列で単語反転アルゴリズムを実行する際に、次の4つのケースを処理するのに長いです。

  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

コードを短縮できると確信しています。最初はアルゴリズム全体の明確なイメージがなかったため、現在は長いです。短いコードで表現されるように設計できると確信しています=)

サンプルの実行(1つ目は私のもの、2つ目はprimoのもの):

文字列を入力: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
文字列を入力してください:
"dcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbd aaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaa A":502、402、1.249
"dcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbd aaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaa A":502、402、1.249

3番目の例のホストワード反転アルゴリズムの最悪の場合を除き、スコアはほぼ同じであることがわかります。このアプローチでは、アプローチが1.25未満のスコアを生成します。

DEBUG = False

def find_new_idx(string, pos, char, f_start, f_end, b_start, b_end, f_long):
    if DEBUG: print 'Finding new idx for s[%d] (%s)' % (pos, char)
    if f_long == 0:
        f_limit = f_end-1
        b_limit = b_start
    elif f_long == 1:
        f_limit = f_end-1
        b_limit = b_start+1
    elif f_long == -1:
        f_limit = f_end-2
        b_limit = b_start
    elif f_long == -2:
        f_limit = f_end
        b_limit = b_start

    if (f_start <= pos < f_limit or b_limit < pos < b_end) and char == ' ':
        word_start = pos
        word_end = pos+1
    else:
        if pos < f_limit+1:
            word_start = f_start
            if DEBUG: print 'Assigned word_start from f_start (%d)' % f_start
        elif pos == f_limit+1:
            word_start = f_limit+1
            if DEBUG: print 'Assigned word_start from f_limit+1 (%d)' % (f_limit+1)
        elif b_limit <= pos:
            word_start = b_limit
            if DEBUG: print 'Assigned word_start from b_limit (%d)' % b_limit
        elif b_limit-1 == pos:
            word_start = b_limit-1
            if DEBUG: print 'Assigned word_start from b_limit-1 (%d)' % (b_limit-1)
        i = pos
        while f_start <= i <= f_limit or 0 < b_limit <= i < b_end:
            if i==f_limit or i==b_limit:
                cur_char = 'a'
            elif i!=pos:
                cur_char = string[i]
            else:
                cur_char = char
            if cur_char == ' ':
                word_start = i+1
                if DEBUG: print 'Assigned word_start from loop'
                break
            i -= 1

        if b_limit <= pos:
            word_end = b_end
            if DEBUG: print 'Assigned word_end from b_end (%d)' % b_end
        elif b_limit-1 == pos:
            word_end = b_limit
            if DEBUG: print 'Assigned word_end from b_limit (%d)' % (b_limit)
        elif pos < f_limit+1:
            word_end = f_limit+1
            if DEBUG: print 'Assigned word_end from f_limit+1 (%d)' % (f_limit+1)
        elif pos == f_limit+1:
            word_end = f_limit+2
            if DEBUG: print 'Assigned word_end from f_limit+2 (%d)' % (f_limit+2)
        i = pos
        while f_start <= i <= f_limit or 0 < b_limit <= i < b_end:
            if i==f_limit or i==b_limit:
                cur_char = 'a'
            elif i!=pos:
                cur_char = string[i]
            else:
                cur_char = char
            if cur_char == ' ':
                word_end = i
                if DEBUG: print 'Assigned word_end from loop'
                break
            i += 1
    if DEBUG: print 'start, end: %d, %d' % (word_start, word_end)
    word_len = word_end - word_start
    offset = word_start-f_start
    result = (b_end-offset-(word_end-pos)) % b_end
    if string[result] == ' ' and (b_start == -1 or result not in {f_end-1, b_start}):
        return len(string)-1-result
    else:
        return result

def process_loop(string, start_idx, f_start, f_end, b_start, b_end=-1, f_long=-2, dry_run=False):
    assignments = 0
    pos = start_idx
    tmp = string[pos]
    processed_something = False
    count = 0
    while pos != start_idx or not processed_something:
        count += 1
        if DEBUG and count > 20:
            print '>>>>>Break!<<<<<'
            break
        new_pos = find_new_idx(string, pos, tmp, f_start, f_end, b_start, b_end, f_long)
        if DEBUG:
            if dry_run:
                print 'Test:',
            else:
                print '\t',
            print 'New idx for s[%d] (%s): %d (%s)' % (pos, tmp, new_pos, string[new_pos])
        if dry_run:
            tmp = string[new_pos]
            if new_pos == dry_run:
                return True
        elif pos == new_pos:
            break
        elif tmp == string[new_pos]:
            pass
        else:
            tmp, string[new_pos] = string[new_pos], tmp
            assignments += 1
        pos = new_pos
        processed_something = True
    if dry_run:
        return False
    return assignments

def reverse(string, f_start, f_end, b_start, b_end=-1, f_long=-2):
    if DEBUG: print 'reverse: %d %d %d %d %d' % (f_start, f_end, b_start, b_end, f_long)
    if DEBUG: print
    if DEBUG: print ''.join(string)
    assignments = 0
    n = len(string)
    if b_start == -1:
        for i in range(f_start, f_end):
            if string[i] == ' ':
                continue
            if DEBUG: print 'Starting from i=%d' % i
            if any(process_loop(string, j, f_start, f_end, -1, f_end, dry_run=i) for j in range(f_start, i) if string[j] != ' '):
                continue
            if DEBUG:
                print
                print 'Finished test'
            assignments += process_loop(string, i, f_start, f_end, -1, f_end)
            if DEBUG: print
            if DEBUG: print ''.join(string)
        for i in range(f_start, (f_start+f_end-1)/2):
            if (string[i] == ' ' and string[n-1-i] != ' ') or (string[i] != ' ' and string[n-1-i] == ' '):
                string[i], string[n-1-i] = string[n-1-i], string[i]
                assignments += 2
    else:
        for i in range(f_start, f_end)+range(b_start, b_end):
            if string[i] == ' ' and i not in {f_end-1, b_start}:
                continue
            if DEBUG: print 'Starting from i=%d' % i
            if any(process_loop(string, j, f_start, f_end, b_start, b_end, f_long, i) for j in range(f_start, f_end)+range(b_start, b_end) if j<i and (string[j] != ' ' or j in {f_end-1, b_start})):
                continue
            assignments += process_loop(string, i, f_start, f_end, b_start, b_end, f_long)
            if DEBUG: print
            if DEBUG: print ''.join(string)
        for i in range(f_start, f_end-1):
            if (string[i] == ' ' and string[n-1-i] != ' ') or (string[i] != ' ' and string[n-1-i] == ' '):
                string[i], string[n-1-i] = string[n-1-i], string[i]
                assignments += 2
    return assignments

class SuperList(list):
    def index(self, value, start_idx=0):
        try:
            return self[:].index(value, start_idx)
        except ValueError:
            return -1

    def rindex(self, value, end_idx=-1):
        end_idx = end_idx % (len(self)+1)
        try:
            result = end_idx - self[end_idx-1::-1].index(value) - 1
        except ValueError:
            return -1
        return result

def min_reverse(string):
    assignments = 0
    lower = 0
    upper = len(string)
    while lower < upper:
        front = string.index(' ', lower) % (upper+1)
        back = string.rindex(' ', upper)
        while abs(front-lower - (upper-1-back)) > 1 and front < back:
            if front-lower < (upper-1-back):
                front = string.index(' ', front+1) % (upper+1)
            else:
                back = string.rindex(' ', back)
            if DEBUG: print lower, front, back, upper
        if front > back:
            break
        if DEBUG: print lower, front, back, upper
        if abs(front-lower - (upper-1-back)) > 1:
            assignments += reverse(string, lower, upper, -1)
            lower = upper
        elif front-lower < (upper-1-back):
            assignments += reverse(string, lower, front+1, back+1, upper, -1)
            lower = front+1
            upper = back+1
        elif front-lower > (upper-1-back):
            assignments += reverse(string, lower, front, back, upper, 1)
            lower = front
            upper = back
        else:
            assignments += reverse(string, lower, front, back+1, upper, 0)
            lower = front+1
            upper = back
    return assignments

def minier_find_new_idx(string, pos, char):
    n = len(string)
    try:
        word_start = pos - next(i for i, char in enumerate(string[pos::-1]) if char == ' ') + 1
    except:
        word_start = 0
    try:
        word_end = pos + next(i for i, char in enumerate(string[pos:]) if char == ' ')
    except:
        word_end = n
    word_len = word_end - word_start
    offset = word_start
    result = (n-offset-(word_end-pos))%n
    if string[result] == ' ':
        return n-result-1
    else:
        return result

def minier_process_loop(string, start_idx, dry_run=False):
    assignments = 0
    pos = start_idx
    tmp = string[pos]
    processed_something = False
    while pos != start_idx or not processed_something:
        new_pos = minier_find_new_idx(string, pos, tmp)
        #print 'New idx for s[%d] (%s): %d (%s)' % (pos, tmp, new_pos, string[new_pos])
        if pos == new_pos:
            break
        elif dry_run:
            tmp = string[new_pos]
            if new_pos == dry_run:
                return True
        elif tmp == string[new_pos]:
            pass
        else:
            tmp, string[new_pos] = string[new_pos], tmp
            assignments += 1
        pos = new_pos
        processed_something = True
    if dry_run:
        return False
    return assignments

def minier_reverse(string):
    assignments = 0
    for i in range(len(string)):
        if string[i] == ' ':
            continue
        if any(minier_process_loop(string, j, dry_run=i) for j in range(i) if string[j] != ' '):
            continue
        assignments += minier_process_loop(string, i)
    n = len(string)
    for i in range(n/2):
        if string[i] == ' ' and string[n-i-1] != ' ':
            string[i], string[n-i-1] = string[n-i-1], string[i]
            assignments += 2
        elif string[n-i-1] == ' ' and string[i] != ' ':
            string[i], string[n-i-1] = string[n-i-1], string[i]
            assignments += 2
    return assignments

def main():
    while True:
        str_input = raw_input('Enter string: ')
        string = SuperList(str_input)
        result = min_reverse(string)
        n = len(string)
        print '"%s": %d, %d, %.3f' % (''.join(string), result, n, 1.0*result/n)
        string = SuperList(str_input)
        result2 = minier_reverse(string)
        print '"%s": %d, %d, %.3f' % (''.join(string), result2, n, 1.0*result2/n)

if __name__ == '__main__':
    main()

Python、スコア:1.5

割り当ての正確な数は、次の式で概算できます。

n <= 1.5 * length(string)

最悪の場合:

abcdefghi jklmnopqrstuvwxyzzz

長さ37の文字列に55の割り当てがあります。

アイデアは以前のものと似ています。このバージョンでは、長さの差が最大1の単語境界で接頭辞と接尾辞を見つけようとしました。その後、その接頭辞と接尾辞で以前のアルゴリズムを実行します。その後、未処理の部品で続行します。

たとえば、以前の最悪の場合:

ab | ab | c

最初に「ab」と「c」(4つの割り当て)で単語の反転を行い、次のようにします。

c | ab | ab

境界ではスペースであったことがわかっているため(多くの場合は処理できますが、それを行うことができます)、境界でスペースをエンコードする必要はありません。これは前のアルゴリズムの主な改善点です。 。

そして最後に、真ん中の4文字で実行して以下を取得します。

cba ab

8文字すべてが変更されたため、合計8つの割り当てで、この場合に最適です。

これにより、以前のアルゴリズムの最悪のケースが排除されるため、以前のアルゴリズムの最悪のケースが排除されます。

サンプルの実行(および@primoの回答との比較-彼の2行目)を参照してください。

文字列を入力:私は何でもできます
「何でもできる」: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 jklmnozzzzzzzzzzzzzzzzz
「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の答えは一般に優れていますが、場合によっては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の文字を記憶し、to jの文字を割り当て、indexの文字で繰り返します。新しい位置を計算するにはスペース情報が必要なので、古いスペースを大文字の新しい文字としてエンコードし、新しいスペースを「@」としてエンコードします。ijj


最悪の場合の単語数を文字列の長さの観点から減らすことができる場合(たとえば、length(string)/3最悪の場合の各単語を少なくとも長さ2に強制することにより)、スコアは以下になります。 2(上記の例では1.67)
14年

1
スワップカウンターを追加しました。あなたのものは実際には最悪の場合(しかし一般的な場合ではなく)私のものを打ち負かします。それを修正する方法を見つける必要があります;)
primo

127行目:if any(process_loop(...) for j in range(...))これらのプロセスループからの割り当てをカウントする必要はありませんか?
プリモ

それは割り当てを行いません。表示されている場合、dry_runパラメーターはゼロ以外(値はi)に設定されています。内でprocess_loopdry_runゼロ以外の場合、割り当ては行われません。
justhalf

1
私は今より良い写真を持っていると思う。本質的に、両方の最悪の場合を排除するために、異なる最悪の場合の動作を伴う2つの異なる方法が組み合わされます。私はそれが好きです。最悪のケースをさらに減らすために、2つ(またはそれ以上)の完全に異なる方法を組み合わせることができると思われますが、一般的には、これが取るべき最良のアプローチかもしれません。
プリモ

14

Perl、スコア1.3̅

スペース以外の文字ごとに1つの割り当てが実行され、スペース文字ごとに2つの割り当てが実行されます。スペース文字は合計文字数の半分を超えることはできないため、最悪の場合のスコアは1.5です。

アルゴリズムは変更されていませんが、下限を証明できます。2つの観察を行います。

  1. スペースの真向かいにあるスペースを交換する必要はありません。
  2. スペースの真向かいにある単一文字の単語は、メインフェーズではスワップされず、最後に1回だけスワップされます。

漸近的に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

または同等に、2文字の単語のみ: 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;

編集:スワップカウンターと比率を追加しました。

入力は標準入力から取得されます。サンプル使用法:

$ 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

方法

まず、文字列の最初の文字が最終的な宛先に移動されます。置換されたばかりのキャラクターは、次に目的地に移動されます。これは、次の2つの条件のいずれかが満たされるまで続きます。

  1. キャラクターはスペースで交換する必要があります。
    これが発生すると、キャラクターはスペースとスワップされ、スペースのミラー位置にスワップされます。アルゴリズムはその位置から続行します。
  2. サイクルに達しました。
    ターゲットが現在のサイクルの最初の開始位置に戻ったとき、次のスワップされていない文字(または、スワップされていない文字があれば)を見つける必要があります。一定のメモリ制約の下でこれを行うには、この時点までに行われたすべてのスワップがバックトラックされます。

このフェーズの後、各非スペース文字は最大で1回移動されました。終了するには、すべてのスペース文字がミラー位置の文字と交換されるため、スペースごとに2つの割り当て操作が必要です。


かっこいい。キャラクターをスペースの鏡の位置に置くと正しい答えが得られる理由を説明できますか?
ちょうど

1
@Niklas、それは可能だとは思わない。バックトラックを行うには、スペースの位置情報が必要です。その情報を上書きすると、バックトラッキングを実行できません。
14年

1
:私はここに私の答えで私のアルゴリズムとの比較作るcodegolf.stackexchange.com/a/26952/16337
justhalf

1
@justhalf最後の文字列では、すべてのスペースがミラー化された位置になります。したがって、この位置を安全に使用して、スペースを置き換える文字を保存し、最後にそれらを切り替えることができます。
primo

1
よくやった。私は同様のアイデアを持っていましたが、スペースをそのまま残してミラーリングすることを考えていませんでした。
IchBinKeinBaum

7

ルビー、スコア2

手始めとして、非常に基本的なアルゴリズム。最初に文字列全体を逆にし、次に文字列内の各単語を再び逆にします。最悪の場合(1単語、偶数文字)、スコアは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

use、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
私はそれをテストしました。うまくいく!
バッカスビール

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"

最初のパスで各文字が1回再配置され、2回目のパスで各非スペース文字が再び再配置されます。比較的少ないスペースで任意の大きさの入力のために、スコアが2に近づく
プリモ

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

Python:スコア2

ハワードのアルゴリズムとほぼ同じですが、逆の手順を逆に実行します(最初に単語を反転し、次に文字列全体を反転します)。:使用する追加のメモリが3バイトサイズの変数であるijt。技術的に、findそしてlenいくつかの内部変数を使用しているが、彼らは同じように簡単に再使用することができますij機能を失うことなく。

クイック編集:文字が異なる場合にのみスワッピングすることで文字列の割り当てを保存するため、注#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

バッチ

私は(私はそれが2だと思う)完全に得点を理解していないために認めるよ..しかし、私は言うだろう- それは事をします

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

入力は、最初に標準入力値とし、そのため引用符で囲んされる必要がある-
コール:script.bat "hello there everyone"
アウト: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では不可能であるため、文字の配列(つまり、コードポイント整数または単一文字の文字列)で文字列をエミュレートする必要があります。
マーティンエンダー

要するに、それはたくさんの追加のスペースを使用します。
ベンミルウッド
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.