Python, Score: 2 1.5 1.25
This is the direct combination between primo's answer and my answer. So credits to him also!
The proof is still in progress, but here is the code to play with! If you can find a counter example of score greater than 1.25 (or if there is a bug), let me know!
Currently the worst case is:
a a ... a a dcb...cbd
where there are exactly n of each the letters "a", "b", "c", and " " (space), and exactly two "d"s. The length of the string is 4n+2 and the number of assignments is 5n+2, giving a score of 5/4 = 1.25.
The algorithm works in two steps:
- Find
k
such that string[k]
and string[n-1-k]
are word boundaries
- Run any word reversing algorithm on
string[:k]+string[n-1-k:]
(i.e., concatenation of first k
and last k
chars) with small modification.
where n
is the length of the string.
The improvement this algorithm gives comes from the "small modification" in Step 2. It's basically the knowledge that in the concatenated string, the characters at position k
and k+1
are word boundaries (which means they are spaces or first/last character in a word), and so we can directly replace the characters in position k
and k+1
with the corresponding character in the final string, saving a few assignments. This removes the worst case from the host word-reversal algorithm
There are cases where we can't actually find such k
, in that case, we just run the "any word reversal algorithm" on the whole string.
The code is long to handle these four cases on running the word reversal algorithm on the "concatenated" string:
- When
k
is not found (f_long = -2
)
- When
string[k] != ' ' and string[n-1-k] != ' '
(f_long = 0
)
- When
string[k] != ' ' and string[n-1-k] == ' '
(f_long = 1
)
- When
string[k] == ' ' and string[n-1-k] != ' '
(f_long = -1
)
I'm sure the code can be shortened. Currently it's long because I didn't have clear picture of the whole algorithm in the beginning. I'm sure one can design it to be represented in a shorter code =)
Sample run (first is mine, second is primo's):
Enter string: a bc def ghij
"ghij def bc a": 9, 13, 0.692
"ghij def bc a": 9, 13, 0.692
Enter string: 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
"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": 50, 50, 1.000
"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": 51, 50, 1.020
Enter string: a b c d e f g hijklmnopqrstuvwx
"hijklmnopqrstuvwx g f e d c b a": 38, 31, 1.226
"hijklmnopqrstuvwx g f e d c b a": 38, 31, 1.226
Enter string: 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
Enter string: a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a dcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbd
"dcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbd a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a": 502, 402, 1.249
"dcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbcbd a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a": 502, 402, 1.249
You can see that the score is almost the same, except for the worst case of the host word-reversal algorithm in the third example, for which my approach yields score of less than 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, Score: 1.5
The exact number of assignments can be approximated by the formula:
n <= 1.5*length(string)
with the worst case being:
a b c d e f g h i jklmnopqrstuvwxyzzz
with 55 assignments on string with length 37.
The idea is similar to my previous one, it's just that in this version I tried to find prefix and suffix at word boundaries with length difference at most 1. Then I run my previous algorithm on that prefix and suffix (imagine them as being concatenated). Then continue on unprocessed part.
For example, for the previous worst case:
ab| a b| c
we will first do word reversal on "ab" and " c" (4 assignments) to be:
c | a b|ab
We know that at the boundary it used to be space (there are many cases to be handled, but you can do that), so we don't need to encode the space at the boundary, this is the main improvement from the previous algorithm.
Then finally we run on the middle four characters to get:
c b a ab
in total 8 assignments, the optimal for this case, since all 8 characters changed.
This eliminates the worst case in previous algorithm since the worst case in previous algorithm is eliminated.
See some sample run (and comparison with @primo's answer - his is the second line):
Enter string: i can do anything
"anything do can i": 20, 17
"anything do can i": 17, 17
Enter string: a b c d e f ghijklmnopqrs
"ghijklmnopqrs f e d c b a": 37, 25
"ghijklmnopqrs f e d c b a": 31, 25
Enter string: a b c d e f ghijklmnopqrst
"ghijklmnopqrst f e d c b a": 38, 26
"ghijklmnopqrst f e d c b a": 32, 26
Enter string: a b c d e f g h i jklmnozzzzzzzzzzzzzzzzz
"jklmnozzzzzzzzzzzzzzzzz i h g f e d c b a": 59, 41
"jklmnozzzzzzzzzzzzzzzzz i h g f e d c b a": 45, 41
Enter string: a b c d e f g h i jklmnopqrstuvwxyzzz
"jklmnopqrstuvwxyzzz i h g f e d c b a": 55, 37
"jklmnopqrstuvwxyzzz i h g f e d c b a": 45, 37
Enter string: ab a b a b a b a b a b a b a c
"c a b a b a b a b a b a b a ab": 30, 30
"c a b a b a b a b a b a b a ab": 31, 30
Enter string: ab a b a b a b a b a b a b a b c
"c b a b a b a b a b a b a b a ab": 32, 32
"c b a b a b a b a b a b a b a ab": 33, 32
Enter string: abc d abc
"abc d abc": 0, 9
"abc d abc": 0, 9
Enter string: abc d c a
"a c d abc": 6, 9
"a c d abc": 4, 9
Enter string: abc a b a b a b a b a b a b c
"c b a b a b a b a b a b a abc": 7, 29
"c b a b a b a b a b a b a abc": 5, 29
primo's answer is generally better, although in some cases I can have 1 point advantage =)
Also his code is much shorter than mine, haha.
DEBUG = False
def find_new_idx(string, pos, char, f_start, f_end, b_start, b_end, f_long):
if DEBUG: print 'Finding new idx for s[%d] (%s)' % (pos, char)
if f_long == 0:
f_limit = f_end-1
b_limit = b_start
elif f_long == 1:
f_limit = f_end-1
b_limit = b_start+1
elif f_long == -1:
f_limit = f_end-2
b_limit = b_start
elif f_long == -2:
f_limit = f_end
b_limit = b_start
if (f_start <= pos < f_limit or b_limit < pos < b_end) and (char == ' ' or char.isupper()):
word_start = pos
word_end = pos+1
else:
if pos < f_limit+1:
word_start = f_start
if DEBUG: print 'Assigned word_start from f_start (%d)' % f_start
elif pos == f_limit+1:
word_start = f_limit+1
if DEBUG: print 'Assigned word_start from f_limit+1 (%d)' % (f_limit+1)
elif b_limit <= pos:
word_start = b_limit
if DEBUG: print 'Assigned word_start from b_limit (%d)' % b_limit
elif b_limit-1 == pos:
word_start = b_limit-1
if DEBUG: print 'Assigned word_start from b_limit-1 (%d)' % (b_limit-1)
i = pos
if not (i < f_limit and b_limit < i):
i -= 1
while f_start <= i < f_limit or 0 < b_limit < i < b_end:
if i!=pos:
cur_char = string[i]
else:
cur_char = char
if cur_char == ' ' or cur_char.isupper():
word_start = i+1
if DEBUG: print 'Assigned word_start from loop'
break
i -= 1
if b_limit <= pos:
word_end = b_end
if DEBUG: print 'Assigned word_end from b_end (%d)' % b_end
elif b_limit-1 == pos:
word_end = b_limit
if DEBUG: print 'Assigned word_end from b_limit (%d)' % (b_limit)
elif pos < f_limit+1:
word_end = f_limit+1
if DEBUG: print 'Assigned word_end from f_limit+1 (%d)' % (f_limit+1)
elif pos == f_limit+1:
word_end = f_limit+2
if DEBUG: print 'Assigned word_end from f_limit+2 (%d)' % (f_limit+2)
i = pos
if not (i < f_limit and b_limit < i):
i += 1
while f_start <= i < f_limit or 0 < b_limit < i < b_end:
if i!=pos:
cur_char = string[i]
else:
cur_char = char
if cur_char == ' ' or cur_char.isupper():
word_end = i
if DEBUG: print 'Assigned word_end from loop'
break
i += 1
if DEBUG: print 'start, end: %d, %d' % (word_start, word_end)
word_len = word_end - word_start
offset = word_start-f_start
return (b_end-offset-(word_end-pos)) % b_end
def process_loop(string, start_idx, f_start, f_end, b_start, b_end=-1, f_long=-2, dry_run=False):
assignments = 0
pos = start_idx
tmp = string[pos]
processed_something = False
count = 0
while pos != start_idx or not processed_something:
count += 1
if count > 20:
if DEBUG: print 'Break!'
break
new_pos = find_new_idx(string, pos, tmp, f_start, f_end, b_start, b_end, f_long)
#if dry_run:
# if DEBUG: print 'Test:',
if DEBUG: print 'New idx for s[%d] (%s): %d (%s)' % (pos, tmp, new_pos, string[new_pos])
if pos == new_pos:
break
elif dry_run:
tmp = string[new_pos]
if new_pos == dry_run:
return True
elif tmp == string[new_pos]:
pass
elif tmp == ' ':
if b_start!=-1 and new_pos in {f_end-1, b_start}:
tmp, string[new_pos] = string[new_pos], tmp
else:
tmp, string[new_pos] = string[new_pos], '@'
assignments += 1
elif string[new_pos] == ' ':
if b_start!=-1 and new_pos in {f_end-1, b_start}:
tmp, string[new_pos] = string[new_pos], tmp
else:
tmp, string[new_pos] = string[new_pos], tmp.upper()
assignments += 1
else:
tmp, string[new_pos] = string[new_pos], tmp
assignments += 1
pos = new_pos
processed_something = True
if dry_run:
return False
return assignments
def reverse(string, f_start, f_end, b_start, b_end=-1, f_long=-2):
if DEBUG: print 'reverse: %d %d %d %d %d' % (f_start, f_end, b_start, b_end, f_long)
if DEBUG: print
if DEBUG: print ''.join(string)
assignments = 0
if b_start == -1:
for i in range(f_start, (f_start+f_end)/2):
if DEBUG: print 'Starting from i=%d' % i
if any(process_loop(string, j, f_start, f_end, -1, f_end, dry_run=i) for j in range(f_start, i)):
continue
assignments += process_loop(string, i, f_start, f_end, -1, f_end)
if DEBUG: print
if DEBUG: print ''.join(string)
else:
for i in range(f_start, f_end):
if DEBUG: print 'Starting from i=%d' % i
if any(process_loop(string, j, f_start, f_end, b_start, b_end, f_long, i) for j in range(f_start, i)):
continue
assignments += process_loop(string, i, f_start, f_end, b_start, b_end, f_long)
if DEBUG: print
if DEBUG: print ''.join(string)
for i in range(len(string)):
if string[i] == '@':
string[i] = ' '
assignments += 1
if string[i].isupper():
string[i] = string[i].lower()
assignments += 1
return assignments
class SuperList(list):
def index(self, value, start_idx=0):
try:
return self[:].index(value, start_idx)
except ValueError:
return -1
def rindex(self, value, end_idx=-1):
end_idx = end_idx % (len(self)+1)
try:
result = end_idx - self[end_idx-1::-1].index(value) - 1
except ValueError:
return -1
return result
def min_reverse(string):
# My algorithm
assignments = 0
lower = 0
upper = len(string)
while lower < upper:
front = string.index(' ', lower) % (upper+1)
back = string.rindex(' ', upper)
while abs(front-lower - (upper-1-back)) > 1 and front < back:
if front-lower < (upper-1-back):
front = string.index(' ', front+1) % (upper+1)
else:
back = string.rindex(' ', back)
if DEBUG: print lower, front, back, upper
if front > back:
break
if DEBUG: print lower, front, back, upper
if abs(front-lower - (upper-1-back)) > 1:
assignments += reverse(string, lower, upper, -1)
lower = upper
elif front-lower < (upper-1-back):
assignments += reverse(string, lower, front+1, back+1, upper, -1)
lower = front+1
upper = back+1
elif front-lower > (upper-1-back):
assignments += reverse(string, lower, front, back, upper, 1)
lower = front
upper = back
else:
assignments += reverse(string, lower, front, back+1, upper, 0)
lower = front+1
upper = back
return assignments
def minier_find_new_idx(string, pos, char):
n = len(string)
try:
word_start = pos - next(i for i, char in enumerate(string[pos::-1]) if char == ' ') + 1
except:
word_start = 0
try:
word_end = pos + next(i for i, char in enumerate(string[pos:]) if char == ' ')
except:
word_end = n
word_len = word_end - word_start
offset = word_start
result = (n-offset-(word_end-pos))%n
if string[result] == ' ':
return n-result-1
else:
return result
def minier_process_loop(string, start_idx, dry_run=False):
assignments = 0
pos = start_idx
tmp = string[pos]
processed_something = False
while pos != start_idx or not processed_something:
new_pos = minier_find_new_idx(string, pos, tmp)
#print 'New idx for s[%d] (%s): %d (%s)' % (pos, tmp, new_pos, string[new_pos])
if pos == new_pos:
break
elif dry_run:
tmp = string[new_pos]
if new_pos == dry_run:
return True
elif tmp == string[new_pos]:
pass
else:
tmp, string[new_pos] = string[new_pos], tmp
assignments += 1
pos = new_pos
processed_something = True
if dry_run:
return False
return assignments
def minier_reverse(string):
# primo's answer for comparison
assignments = 0
for i in range(len(string)):
if string[i] == ' ':
continue
if any(minier_process_loop(string, j, dry_run=i) for j in range(i) if string[j] != ' '):
continue
assignments += minier_process_loop(string, i)
n = len(string)
for i in range(n/2):
if string[i] == ' ' and string[n-i-1] != ' ':
string[i], string[n-i-1] = string[n-i-1], string[i]
assignments += 2
elif string[n-i-1] == ' ' and string[i] != ' ':
string[i], string[n-i-1] = string[n-i-1], string[i]
assignments += 2
return assignments
def main():
while True:
str_input = raw_input('Enter string: ')
string = SuperList(str_input)
result = min_reverse(string)
print '"%s": %d, %d' % (''.join(string), result, len(string))
string = SuperList(str_input)
result2 = minier_reverse(string)
print '"%s": %d, %d' % (''.join(string), result2, len(string))
if __name__ == '__main__':
main()
Python, Score: asymptotically 2, in normal case much less
old code removed due to space constraint
The idea is to iterate through each index, and for each index i
, we take the character, calculate the new position j
, memorize the character at position j
, assign the character at i
to j
, and repeat with character at index j
. Since we need the space information to calculate the new position, I encode old space as the uppercase version of the new letter, and new space as '@'.
What do you mean by "number of assigments you make to string elements"? E.g. in C,
str[0]='A';
counts as one? Orstr[]="FOOBAR";
counts as one? – user12205 – 2014-05-11T14:15:01.440@ace I would think that
str[a:b] = str[c:d]
, for example, should count asb-a
. – primo – 2014-05-11T14:20:22.490str[0]='A'
counts as one assignment. The latter case is only possible in initialisation, right? And with a string literal on the RHS? In that case I'm not sure it's going to help you very much. I say make it seven assignments (including the implicit null byte) (so primo is correct) – Ben Millwood – 2014-05-11T14:22:50.297So we will end up with answers with the same score of 2 but different tie-breaks? – A.L – 2014-05-11T15:08:23.753
1I'd be a little bit sad if this all came down to tie-breaking score-2 submissions on their memory usage. I mostly posted this question wondering if anyone would manage to score less than 2. – Ben Millwood – 2014-05-11T15:39:47.010
1I don't have a formal proof, but my intuition tells me that with the constant-space restriction, it's impossible to do better than 2. If you only count variable declarations for space, I could cheat and make a recursive function. but that only disguises the
little-omega(1)
space by putting it on the stack. – wrongu – 2014-05-11T17:22:04.9471We would need at least one temporary variable to use for swapping characters. Does this count as extra memory? – bacchusbeale – 2014-05-11T19:55:08.303
2@bacchusbeale yes, but it's constant extra memory. – Martin Ender – 2014-05-11T20:22:35.777
4If you strictly enforce the rules then it's impossible to write such a program. The string can be of arbitrary length and you will need at least some kind of variable that stores an index. So I just have to make the string long enough to exceed the bounds of your variable. To successfully store at least one index your needed memory will grow with the string length. – IchBinKeinBaum – 2014-05-11T22:54:12.320
1@IchBinKeinBaum, the rule reads "You are not permitted to use more than a constant amount of additional memory." A single index takes constant memory, and is independent of the size of the string. Also nobody actually worries about strings exceeding RAM size or integer overflow with indices. – wrongu – 2014-05-12T00:49:27.017
1I found this question really hard to understand. Maybe because English is not my mother tongue. I have read the score calculation 3 times but still can't figure out how to apply these rules to the answers. Proving bounds or fewer assignments is neither simple nor code golfing IMHO. – A.L – 2014-05-12T01:58:34.687
1Probably the scoring can be better formulated if you can provide (or hide) some test strings for which the scores of all programs will be calculated. The lowest number of assignments wins. That will be very objective. To count the number of assignments, you can create an function for people should use to modify the string, then you can count the number of assignments by counting the number of calls to the function. – justhalf – 2014-05-12T06:53:13.473
1@IchBinKeinBaum In the RAM model, which is usually used in algorithm analysis, it is assumed that for the machine word width w, we have n << 2^w. This is also practical. For w = 64 it's highly unlikely that the input size will ever be larger than what a word can address – Niklas B. – 2014-05-12T11:38:39.697
Do you mean a mutable or immutable string? – The Guy with The Hat – 2014-05-12T11:43:56.667
1@TheGuywithTheHat Mutable. The description specifically says that if your language doesn't support mutable strings, character arrays are acceptable. – IchBinKeinBaum – 2014-05-12T11:58:26.740
@NiklasB. @rangu To make it concrete: If I use Java I have to use char arrays because
– IchBinKeinBaum – 2014-05-12T13:20:40.557Strings
are immutable - kind of. Am I allowed to assume that the string length is < 2^31 because otherwise I can't fit the string into a single char array?@IchBinKeinBaum I would assume so. That Java does not allow you to use the full word size to index an array is a rather arbitrary restriction of the language, not a theoretical problem (you can always use 64-bit indexing) – Niklas B. – 2014-05-12T13:24:22.200
Certainly I am missing something here. Which part of the rule prohibits Perl
– manatwork – 2014-05-13T09:05:16.007s///
? http://pastebin.com/A2z7PESK@manatwork I suppose one would be disinclined, due to having to wade through the perl source to show that a) additional memory usage is bounded by a constant for any arbitrary string (is it?), and b) that the number of inplace string operation is bounded by the length of the string (it probably is). – primo – 2014-05-13T11:11:45.130
3@IchBinKeinBaum is right, it is impossible to perform this task with
O(1)
additional space. You needO(log n)
space to store an index position, since an k-bit integer can only store in them for strings of a length up to2^k
. Limiting the length of the strings makes the challenge rather pointless, since every algorithm would requireO(1)
space this way. – Dennis – 2014-05-13T14:17:28.4401In all seriousness, an unsigned 256 bit integer is nearly large enough to enumerate every electron in the observable universe. For all practicality, the space required to store such an index can safely be considered to be constant. – primo – 2014-05-14T03:44:37.763
I'm curious. OP, is there any practical system where string assignment is the most costly part, hence you're willing to sacrifice time to save some assignments? It's cool that we can actually achieve a score of 1.25 =D – justhalf – 2014-05-14T09:58:53.497
Thanks to the people who pointed out my sloppiness wrt constant memory. I indeed intended for indices into the string to count as constant size. I pondered clarifying it to "constant number of unbounded integers" but them some smart-alec would encode the entire string into a single integer and operate on it there :) I suspect just weakening to "logarithmic space overhead" would be fine, but since everyone seems to be interpreting the challenge as I intended, I will make no changes. In response to justhalf, there is no intended practical application, "cool" was all I hoped for :) – Ben Millwood – 2014-05-15T20:58:05.817