Manually compute the length of a string - python

I have a homework assignment asking for string length calculation without using built-in functions.
What I had in mind is to use a counter:
s = 0
while name[s] != "":
s += 1
but I'm stuck with how to solve the string index out of range error...or is there actually another way?

you have two simple options :
Either add a try/except clause:
s = 0
try:
while(name[s]):
s += 1
except IndexError:
pass
print(s)
Or use an iterator:
s = 0
for _ in name:
s += 1
print(s)

Try this,
counter = 0
st = 'ABCDEF'
for i in st:
counter += 1
print('Length of String is : ', str(counter))

So, a string is basically a sequence of characters. For example:
'hello' = ['h', 'e', 'l', 'l', 'o']
So if you just loop through this array and add 1 to your length variable every loop, you will get the length:
string = "hello"
length = 0
for character in string:
length = length + 1
print(length)
This way, you won't even need to worry about handling exceptions :)
Try it online
https://repl.it/IptA/0
Further Reading
Strings
Lists

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

A string has an attribute __len__, a function that returns the length of the string. Thus, the following solution does not use built-ins, but the calculation is trivial (without calculating operations, thus, it might be not the intention of the homework):
def get_string_length(s):
return s.__len__()
Test:
print(get_string_length('hello'))
Result:
5

Related

How to find the max number of times a sequence of characters repeats consecutively in a string? [duplicate]

This question already has answers here:
How to count consecutive repetitions of a substring in a string?
(4 answers)
Closed 1 year ago.
I'm working on a cs50/pset6/dna project. I'm struggling with finding a way to analyze a sequence of strings, and gather the maximum number of times a certain sequence of characters repeats consecutively. Here is an example:
String: JOKHCNHBVDBVDBVDJHGSBVDBVD
Sequence of characters I should look for: BVD
Result: My function should be able to return 3, because in one point the characters BVD repeat three times consecutively, and even though it repeats again two times, I should look for the time that it repeats the most number of times.
It's a bit lame, but one "brute-force"ish way would be to just check for the presence of the longest substring possible. As soon as a substring is found, break out of the loop:
EDIT - Using a function might be more straight forward:
def get_longest_repeating_pattern(string, pattern):
if not pattern:
return ""
for i in range(len(string)//len(pattern), 0, -1):
current_pattern = pattern * i
if current_pattern in string:
return current_pattern
return ""
string = "JOKHCNHBVDBVDBVDJHGSBVDBVD"
pattern = "BVD"
longest_repeating_pattern = get_longest_repeating_pattern(string, pattern)
print(len(longest_repeating_pattern))
EDIT - explanation:
First, just a simple for-loop that starts at a larger number and goes down to a smaller number. For example, we start at 5 and go down to 0 (but not including 0), with a step size of -1:
>>> for i in range(5, 0, -1):
print(i)
5
4
3
2
1
>>>
if string = "JOKHCNHBVDBVDBVDJHGSBVDBVD", then len(string) would be 26, if pattern = "BVD", then len(pattern) is 3.
Back to my original code:
for i in range(len(string)//len(pattern), 0, -1):
Plugging in the numbers:
for i in range(26//3, 0, -1):
26//3 is an integer division which yields 8, so this becomes:
for i in range(8, 0, -1):
So, it's a for-loop that goes from 8 to 1 (remember, it doesn't go down to 0). i takes on the new value for each iteration, first 8 , then 7, etc.
In Python, you can "multiply" strings, like so:
>>> pattern = "BVD"
>>> pattern * 1
'BVD'
>>> pattern * 2
'BVDBVD'
>>> pattern * 3
'BVDBVDBVD'
>>>
A slightly less bruteforcey solution:
string = 'JOKHCNHBVDBVDBVDJHGSBVDBVD'
key = 'BVD'
len_k = len(key)
max_l = 0
passes = 0
curr_len=0
for i in range(len(string) - len_k + 1): # split the string into substrings of same len as key
if passes > 0: # If key was found in previous sequences, pass ()this way, if key is 'BVD', we will ignore 'VD.' and 'D..'
passes-=1
continue
s = string[i:i+len_k]
if s == key:
curr_len+=1
if curr_len > max_l:
max_l=curr_len
passes = len(key)-1
if prev_s == key:
if curr_len > max_l:
max_l=curr_len
else:
curr_len=0
prev_s = s
print(max_l)
You can do that very easily, elegantly and efficiently using a regex.
We look for all sequences of at least one repetition of your search string. Then, we just need to take the maximum length of these sequences, and divide by the length of the search string.
The regex we use is '(:?<your_sequence>)+': at least one repetition (the +) of the group (<your_sequence>). The :? is just here to make the group non capturing, so that findall returns the whole match, and not just the group.
In case there is no match, we use the default parameter of the max function to return 0.
The code is very short, then:
import re
def max_consecutive_repetitions(search, data):
search_re = re.compile('(?:' + search + ')+')
return max((len(seq) for seq in search_re.findall(data)), default=0) // len(search)
Sample run:
print(max_consecutive_repetitions("BVD", "JOKHCNHBVDBVDBVDJHGSBVDBVD"))
# 3
This is my contribution, I'm not a professional but it worked for me (sorry for bad English)
results = {}
# Loops through all the STRs
for i in range(1, len(reader.fieldnames)):
STR = reader.fieldnames[i]
j = 0
s=0
pre_s = 0
# Loops through all the characters in sequence.txt
while j < (len(sequence) - len(STR)):
# checks if the character we are currently looping is the same than the first STR character
if STR[0] == sequence[j]:
# while the sub-string since j to j - STR lenght is the same than STR, I called this a streak
while sequence[j:(j + len(STR))] == STR:
# j skips to the end of sub-string
j += len(STR)
# streaks counter
s += 1
# if s > 0 means that that the whole STR and sequence coincided at least once
if s > 0:
# save the largest streak as pre_s
if s > pre_s:
pre_s = s
# restarts the streak counter to continue exploring the sequence
s=0
j += 1
# assigns pre_s value to a dictionary with the current STR as key
results[STR] = pre_s
print(results)

Next lexicographically bigger string permutation and solution efficiency

I'm trying to solve Hackerrank question: Find next lexicographically bigger string permutation for a given string input.
Here my solution:
def biggerIsGreater(w):
if len(w)<=1: return w
# pair letters in w string with int representing positional index in alphabet
letter_mapping = dict(zip(string.ascii_lowercase, range(1, len(string.ascii_lowercase)+1)))
char_ints = [letter_mapping[letter] for letter in w.lower() if letter in letter_mapping]
# reverse it
reversed_char_ints = char_ints[::-1]
# get char set to reorder, including pivot.
scanned_char_ints = []
index = 0
zipped = list(zip(reversed_char_ints, reversed_char_ints[1:]))
while index < len(zipped):
char_tuple = zipped[index]
scanned_char_ints.append(char_tuple[0])
if char_tuple[0] <= char_tuple[1]:
if index == len(zipped) - 1:
return "no answer"
else:
scanned_char_ints.append(char_tuple[1])
break
index += 1
# get smallest among bigger values of pivot
char_to_switch = None
char_to_switch_index = None
for item in scanned_char_ints[:-1]:
if item > scanned_char_ints[-1]:
if char_to_switch == None or item <= char_to_switch:
char_to_switch = item
char_to_switch_index = scanned_char_ints.index(item)
# switch pivot and smallest of bigger chars in scanned chars
pivot_index = len(scanned_char_ints) - 1
scanned_char_ints[pivot_index], scanned_char_ints[char_to_switch_index] = scanned_char_ints[char_to_switch_index], scanned_char_ints[pivot_index]
# order from second to end the other chars, so to find closest bigger number of starting number
ord_scanned_char_ints = scanned_char_ints[:-1]
ord_scanned_char_ints.sort(reverse=True)
ord_scanned_char_ints.append(scanned_char_ints[-1])
# reverse scanned chars
ord_scanned_char_ints.reverse()
# rebuild char int list
result_ints = char_ints[:len(char_ints) - len(ord_scanned_char_ints)]
result_ints.extend(ord_scanned_char_ints)
result_ = ""
for char_intx in result_ints:
for char, int_charz in letter_mapping.items():
if int_charz == char_intx:
result_ += char
return result_
(I know that there are solution on internet with more concise way of implement the problem, but I obviously trying to succeed by myself).
Now, it seems to run for 1, 2, 100 input of strings with at most 100 characters.
But when hackerrank test procedure tests it against 100000 strings of at most 100 letters, an error results, with no further information about it. Running a test with a similar input size, in my machine, does not throw any error.
What is wrong with this solution?
Thanks in advance

Compressing multiple nested `for` loops

Similar to this and many other questions, I have many nested loops (up to 16) of the same structure.
Problem: I have 4-letter alphabet and want to get all possible words of length 16. I need to filter those words. These are DNA sequences (hence 4 letter: ATGC), filtering rules are quite simple:
no XXXX substrings (i.e. can't have same letter in a row more than 3 times, ATGCATGGGGCTA is "bad")
specific GC content, that is number of Gs + number of Cs should be in specific range (40-50%). ATATATATATATA and GCGCGCGCGCGC are bad words
itertools.product will work for that, but data structure here gonna be giant (4^16 = 4*10^9 words)
More importantly, if I do use product, then I still have to go through each element to filter it out. Thus I will have 4 billion steps times 2
My current solution is nested for loops
alphabet = ['a','t','g','c']
for p1 in alphabet:
for p2 in alphabet:
for p3 in alphabet:
...skip...
for p16 in alphabet:
word = p1+p2+p3+...+p16
if word_is_good(word):
good_words.append(word)
counter+=1
Is there good pattern to program that without 16 nested loops? Is there a way to parallelize it efficiently (on multi-core or multiple EC2 nodes)
Also with that pattern i can plug word_is_good? check inside middle of the loops: word that starts badly is bad
...skip...
for p3 in alphabet:
word_3 = p1+p2+p3
if not word_is_good(word_3):
break
for p4 in alphabet:
...skip...
from itertools import product, islice
from time import time
length = 16
def generate(start, alphabet):
"""
A recursive generator function which works like itertools.product
but restricts the alphabet as it goes based on the letters accumulated so far.
"""
if len(start) == length:
yield start
return
gcs = start.count('g') + start.count('c')
if gcs >= length * 0.5:
alphabet = 'at'
# consider the maximum number of Gs and Cs we can have in the end
# if we add one more A/T now
elif length - len(start) - 1 + gcs < length * 0.4:
alphabet = 'gc'
for c in alphabet:
if start.endswith(c * 3):
continue
for string in generate(start + c, alphabet):
yield string
def brute_force():
""" Straightforward method for comparison """
lower = length * 0.4
upper = length * 0.5
for s in product('atgc', repeat=length):
if lower <= s.count('g') + s.count('c') <= upper:
s = ''.join(s)
if not ('aaaa' in s or
'tttt' in s or
'cccc' in s or
'gggg' in s):
yield s
def main():
funcs = [
lambda: generate('', 'atgc'),
brute_force
]
# Testing performance
for func in funcs:
# This needs to be big to get an accurate measure,
# otherwise `brute_force` seems slower than it really is.
# This is probably because of how `itertools.product`
# is implemented.
count = 100000000
start = time()
for _ in islice(func(), count):
pass
print(time() - start)
# Testing correctness
global length
length = 12
for x, y in zip(*[func() for func in funcs]):
assert x == y, (x, y)
main()
On my machine, generate was just a bit faster than brute_force, at about 390 seconds vs 425. This was pretty much as fast as I could make them. I think the full thing would take about 2 hours. Of course, actually processing them will take much longer. The problem is that your constraints don't reduce the full set much.
Here's an example of how to use this in parallel across 16 processes:
from multiprocessing.pool import Pool
alpha = 'atgc'
def generate_worker(start):
start = ''.join(start)
for s in generate(start, alpha):
print(s)
Pool(16).map(generate_worker, product(alpha, repeat=2))
Since you happen to have an alphabet of length 4 (or any "power of 2 integer"), the idea of using and integer ID and bit-wise operations comes to mind instead of checking for consecutive characters in strings. We can assign an integer value to each of the characters in alphabet, for simplicity lets use the index corresponding to each letter.
Example:
6546354310 = 33212321033134 = 'aaaddcbcdcbaddbd'
The following function converts from a base 10 integer to a word using alphabet.
def id_to_word(word_id, word_len):
word = ''
while word_id:
rem = word_id & 0x3 # 2 bits pet letter
word = ALPHABET[rem] + word
word_id >>= 2 # Bit shift to the next letter
return '{2:{0}>{1}}'.format(ALPHABET[0], word_len, word)
Now for a function to check whether a word is "good" based on its integer ID. The following method is of a similar format to id_to_word, except a counter is used to keep track of consecutive characters. The function will return False if the maximum number of identical consecutive characters is exceeded, otherwise it returns True.
def check_word(word_id, max_consecutive):
consecutive = 0
previous = None
while word_id:
rem = word_id & 0x3
if rem != previous:
consecutive = 0
consecutive += 1
if consecutive == max_consecutive + 1:
return False
word_id >>= 2
previous = rem
return True
We're effectively thinking of each word as an integer with base 4. If the Alphabet length was not a "power of 2" value, then modulo % alpha_len and integer division // alpha_len could be used in place of & log2(alpha_len) and >> log2(alpha_len) respectively, although it would take much longer.
Finally, finding all the good words for a given word_len. The advantage of using a range of integer values is that you can reduce the number of for-loops in your code from word_len to 2, albeit the outer loop is very large. This may allow for more friendly multiprocessing of your good word finding task. I have also added in a quick calculation to determine the smallest and largest IDs corresponding to good words, which helps significantly narrow down the search for good words
ALPHABET = ('a', 'b', 'c', 'd')
def find_good_words(word_len):
max_consecutive = 3
alpha_len = len(ALPHABET)
# Determine the words corresponding to the smallest and largest ids
smallest_word = '' # aaabaaabaaabaaab
largest_word = '' # dddcdddcdddcdddc
for i in range(word_len):
if (i + 1) % (max_consecutive + 1):
smallest_word = ALPHABET[0] + smallest_word
largest_word = ALPHABET[-1] + largest_word
else:
smallest_word = ALPHABET[1] + smallest_word
largest_word = ALPHABET[-2] + largest_word
# Determine the integer ids of said words
trans_table = str.maketrans({c: str(i) for i, c in enumerate(ALPHABET)})
smallest_id = int(smallest_word.translate(trans_table), alpha_len) # 1077952576
largest_id = int(largest_word.translate(trans_table), alpha_len) # 3217014720
# Find and store the id's of "good" words
counter = 0
goodies = []
for i in range(smallest_id, largest_id + 1):
if check_word(i, max_consecutive):
goodies.append(i)
counter += 1
In this loop I have specifically stored the word's ID as opposed to the actual word itself incase you are going to use the words for further processing. However, if you are just after the words then change the second to last line to read goodies.append(id_to_word(i, word_len)).
NOTE: I receive a MemoryError when attempting to store all good IDs for word_len >= 14. I suggest writing these IDs/words to a file of some sort!

Finding the length of longest repeating?

I have tried plenty of different methods to achieve this, and I don't know what I'm doing wrong.
reps=[]
len_charac=0
def longest_charac(strng)
for i in range(len(strng)):
if strng[i] == strng[i+1]:
if strng[i] in reps:
reps.append(strng[i])
len_charac=len(reps)
return len_charac
Remember in Python counting loops and indexing strings aren't usually needed. There is also a builtin max function:
def longest(s):
maximum = count = 0
current = ''
for c in s:
if c == current:
count += 1
else:
count = 1
current = c
maximum = max(count,maximum)
return maximum
Output:
>>> longest('')
0
>>> longest('aab')
2
>>> longest('a')
1
>>> longest('abb')
2
>>> longest('aabccdddeffh')
3
>>> longest('aaabcaaddddefgh')
4
Simple solution:
def longest_substring(strng):
len_substring=0
longest=0
for i in range(len(strng)):
if i > 0:
if strng[i] != strng[i-1]:
len_substring = 0
len_substring += 1
if len_substring > longest:
longest = len_substring
return longest
Iterates through the characters in the string and checks against the previous one. If they are different then the count of repeating characters is reset to zero, then the count is incremented. If the current count beats the current record (stored in longest) then it becomes the new longest.
Compare two things and there is one relation between them:
'a' == 'a'
True
Compare three things, and there are two relations:
'a' == 'a' == 'b'
True False
Combine these ideas - repeatedly compare things with the things next to them, and the chain gets shorter each time:
'a' == 'a' == 'b'
True == False
False
It takes one reduction for the 'b' comparison to be False, because there was one 'b'; two reductions for the 'a' comparison to be False because there were two 'a'. Keep repeating until the relations are all all False, and that is how many consecutive equal characters there were.
def f(s):
repetitions = 0
while any(s):
repetitions += 1
s = [ s[i] and s[i] == s[i+1] for i in range(len(s)-1) ]
return repetitions
>>> f('aaabcaaddddefgh')
4
NB. matching characters at the start become True, only care about comparing the Trues with anything, and stop when all the Trues are gone and the list is all Falses.
It can also be squished into a recursive version, passing the depth in as an optional parameter:
def f(s, depth=1):
s = [ s[i] and s[i]==s[i+1] for i in range(len(s)-1) ]
return f(s, depth+1) if any(s) else depth
>>> f('aaabcaaddddefgh')
4
I stumbled on this while trying for something else, but it's quite pleasing.
You can use itertools.groupby to solve this pretty quickly, it will group characters together, and then you can sort the resulting list by length and get the last entry in the list as follows:
from itertools import groupby
print(sorted([list(g) for k, g in groupby('aaabcaaddddefgh')],key=len)[-1])
This should give you:
['d', 'd', 'd', 'd']
This works:
def longestRun(s):
if len(s) == 0: return 0
runs = ''.join('*' if x == y else ' ' for x,y in zip(s,s[1:]))
starStrings = runs.split()
if len(starStrings) == 0: return 1
return 1 + max(len(stars) for stars in starStrings)
Output:
>>> longestRun("aaabcaaddddefgh")
4
First off, Python is not my primary language, but I can still try to help.
1) you look like you are exceeding the bounds of the array. On the last iteration, you check the last character against the character beyond the last character. This normally leads to undefined behavior.
2) you start off with an empty reps[] array and compare every character to see if it's in it. Clearly, that check will fail every time and your append is within that if statement.
def longest_charac(string):
longest = 0
if string:
flag = string[0]
tmp_len = 0
for item in string:
if item == flag:
tmp_len += 1
else:
flag = item
tmp_len = 1
if tmp_len > longest:
longest = tmp_len
return longest
This is my solution. Maybe it will help you.
Just for context, here is a recursive approach that avoids dealing with loops:
def max_rep(prev, text, reps, rep=1):
"""Recursively consume all characters in text and find longest repetition.
Args
prev: string of previous character
text: string of remaining text
reps: list of ints of all reptitions observed
rep: int of current repetition observed
"""
if text == '': return max(reps)
if prev == text[0]:
rep += 1
else:
rep = 1
return max_rep(text[0], text[1:], reps + [rep], rep)
Tests:
>>> max_rep('', 'aaabcaaddddefgh', [])
4
>>> max_rep('', 'aaaaaabcaadddddefggghhhhhhh', [])
7

Python iterator behaviour

Given an arbitrary input string I'm meant to find the sum of all numbers in that string.
This obviously requires that i know the NEXT element in string while iterating through it...and make the decision whether its an integer. if the previous element was an integer also, the two elements form a new integer, all other characters are ignored and so on.
For instance an input string
ab123r.t5689yhu8
should result in the sum of 123 + 5689 + 8 = 5820.
All this is to be done without using regular expressions.
I have implemented an iterator in python, whose (next()) method i think returns the next element, but passing the input string
acdre2345ty
I'm getting the following output
a
c
d
r
e
2
4
t
y
Some numbers 3 and 5 are missing...why is this? I need that the next() to work for me to be able to sift through an input string and do the calculations correctly
Better still, how should i implement the next method so that it yields the element to the immediate right during a given iteration?
Here is my code
class Inputiterator(object):
'''
a simple iterator to yield all elements from a given
string successively from a given input string
'''
def __init__(self, data):
self.data = data
self.index = 0
def __iter__(self):
return self
def next(self):
"""
check whether we've reached the end of the input
string, if not continue returning the current value
"""
if self.index == len(self.data)-1:
raise StopIteration
self.index = self.index + 1
return self.data[self.index]
# Create a method to get the input from the user
# simply return a string
def get_input_as_string():
input=raw_input("Please enter an arbitrary string of numbers")
return input
def sort_by_type():
maininput= Inputiterator(get_input_as_string())
list=[]
s=""
for char in maininput:
if str(char).isalpha():
print ""+ str(char)
elif str(char).isdigit() and str(maininput.next()).isdigit():
print ""+ str(char)
sort_by_type()
Python strings are already iterable, no need to create you own iterator.
What you want is thus simply achieved without iterators:
s = "acdre2345ty2390"
total = 0
num = 0
for c in s:
if c.isdigit():
num = num * 10 + int(c)
else:
total += num
num = 0
total += num
Which results in:
>>> print total
4735
This can be done with itertools.groupby:
from itertools import groupby
s = 'ab123r#t5689yhu8'
tot = 0
for k, g in groupby(s, str.isdigit):
if k:
tot += int(''.join(g))
Or in one line (as suggested in the comments down below):
tot = sum((int(''.join(g)) for k, g in groupby(s, str.isdigit) if k)
Edit: I first deleted this answer as there are much better solutions for your problem in this thread, but as you are directly asking how to use the next method to get your code working I have recovered it, in case you find it useful.
Try this (I mocked the iterator for convenience):
def sort_by_type():
maininput = iter("acdre2345ty")
for char in maininput:
if char.isalpha():
print char
elif char.isdigit():
number = char
while True:
# try/except could take care of the StopIteration exception
# when a digit is last in the string
#
# try:
# char = maininput.next()
# except StopIteration:
# char = ""
#
# however using next(iterator, default) is much better:
#
char = next(maininput, "")
if char.isdigit():
number += char
else:
break
print number
print char
if produces:
a
c
d
r
e
2345
t
y
For entertainment purposes only (I couldn't resist, 49 chars):
eval(''.join([['+0+',x][x.isdigit()]for x in s]))

Categories

Resources