There is an alternative to the "stupid" counting by adding one for each character:
- An exponential search finds a range for the string length.
- A binary search pins down the string length starting with the range, found in the previous step.
The code with test section:
def is_valid_index(s, i):
'''Returns True, if i is a valid index of string s, and False otherwise.'''
try:
s[i]
return True
except IndexError:
return False
def string_length(s):
'''Returns the length of string s without built-ins.'''
# Test for empty string (needed for the invariant
# of the following exponential search.)
if not is_valid_index(s, 0):
return 0
# Exponential search with low as inclusive lower bound
# and high as exclusive upper bound.
# Invariant for the loop: low is a valid index.
low = 0
high = 1
while True:
if is_valid_index(s, high):
low = high
high *= 2
continue
break
# Binary search inside the found range
while True:
if low + 1 == high:
return high
middle = (low + high) // 2
if is_valid_index(s, middle):
low = middle
else:
high = middle
# Test section
print(string_length('hello'))
# Test the first thousand string lengths
for i in range(1000):
s = 'x' * i
if len(s) != string_length(s):
print('Error for {}: {}'.format(i, string_length(s)))
# Test quite a large string
s = 'x' * 1234567890
print(string_length(s))
Result:
5
1234567890