How can I manually progress an iterator variable in Python 3? - python

I would like to know how to manually progress an iterator variable in Python3. In C, for example, if during a for loop such as:
for( int i = 0, k = 10; i < k; ++i), I could skip loop iterations when i == {2,3,4} by simply setting i from within the loop body like this: if(i == 1) i = 5;
However, when I do something like this from within a for in loop in Python3, the for in loop actually enforces that I cannot do this - it mutates i and sets it to the next sequential number regardless of what I did to the iterator variable in the loop body.
For example in the Python3 code below, I am trying to progress i to the position of j such that I can skip i to the end of the group of spaces once the algorithm has detected a group of spaces:
testString = "This is a testzstring"
# find length and end of each space group
spacePositionsAndLengths = {}
j = 0
length = len(testString)
for i in range(length):
if testString[i] == " ":
j = i # mark beginning of spaces
while j < length and testString[j] == " ":
j += 1
# j-1 is now the last space. Alternatively, j is at first non-space
lengthOfSpaces = j-i
print(f"i: {i}\t lengthOfSpaces: {lengthOfSpaces}")
spacePositionsAndLengths[i] = lengthOfSpaces
i = j # remember, at this point j is at first non-space
print(testString)
That algorithm prints this output when run with:
i: 4 lengthOfSpaces: 1
i: 7 lengthOfSpaces: 1
i: 9 lengthOfSpaces: 3
i: 10 lengthOfSpaces: 2
i: 11 lengthOfSpaces: 1
This is a testzstring
This is decent, but what I want is it to print this:
i: 4 lengthOfSpaces: 1
i: 7 lengthOfSpaces: 1
i: 9 lengthOfSpaces: 3
This is a testzstring
I do not want the redundant 3..2..1 "countdown" style space counting.
Note: I am not building a production application; I am working on algorithms and the instructions require me to NOT use many of the builtin string methods. I note this because I expect comments saying "Why don't you just use X, Y, or Z builtin function and be done with this whole thing in 1 line??"

To manually progress the iterator, you need access to it. You can explicitly create the iterator yourself and then both give it to the for statement and treat it yourself as you wish. Example:
irange = iter(range(10))
for i in irange:
print(i)
next(irange)
Output:
0
2
4
6
8
Though in your example, a while loop might be better.

What you need is a while loop so that you can have your own iterator that you can mutate. It may look something like.
j = 0
i = 0
length = len(testString)
while i < length:
if testString[i] == " ":
j = i # mark beginning of spaces
while j < length and testString[j] == " ":
j += 1
# j-1 is now the last space. Alternatively, j is at first non-space
lengthOfSpaces = j-i
print(f"i: {i}\t lengthOfSpaces: {lengthOfSpaces}")
spacePositionsAndLengths[i] = lengthOfSpaces
i = j # remember, at this point j is at first non-space
i += 1

Related

traversing through a list using recursion

So I am new to recursion and I am trying to make a program where you can enter a list and python tests each integer (lets say 9 for example) and sees if the integer following it is doubled. So if I entered a list of 2 4 8 16 32, would return 4, and -5 -10 0 6 12 9 36, would return 2 because -5 followed by -10 is one and 6 followed by 12 is the second. This is the code I have so far. I feel like I am very close. but just a few thing stand in my way. Any help would be great!
L = []
def countDouble(L):
x = input(f'Enter a list of numbers separated by a space: ')
y = (x.split(' '))
print(y[1])
print(y[0])
count = 0
y[0] += y[0]
# unsure of how to multiple y[0] by 2
if y[0]*2 == y[1]:
count += 1
else:
count += 0
#how would I traverse through the rest of the entered list using recursion?
print(count)
countDouble(L)
If you want/need to solve it using recursion, the following will do the trick:
def count_sequential_doubles(li, count=0):
return count_sequential_doubles(li[1:], count + int(li[0] * 2 == li[1])) if len(li) > 1 else count
I would suggest this recursive way:
def countDouble(L):
count = 0
if len(L) == 1:
return count
else:
if int(L[0])*2 == int(L[1]):
count += 1
return count + countDouble(L[1:])
x = input(f'Enter a list of numbers separated by a space: ')
y = (x.split(' '))
count = countDouble(y)
print(count)
I urge you to read the entire answer, but in case you are not interested in tips, notes and the process of finding the solution, here are two solutions:
solution using recursion (not recommended):
x = input()
y = x.split(' ')
count = 0
def countDouble(i):
if(i+1 == len(y)):
return 'recursion ends here when'
if(int(y[i])*2==int(y[i+1])):
count += 1
countDouble(i+1)
countDouble(0)
print(count)
this solution just imitates a while loop:
solution using a while loop (recommended):
x = input()
y = x.split(' ')
count = 0
i = 0
while(i < len(y) - 1):
if(int(y[i]) * 2 == int(y[i+1])):
count += 1
i += 1
print(count)
Before I continue, here are a few tips and notes: (some of them will only make sense after)
I assume the 14 in your example is a typo
I didn't put the code in a function because it's not needed, but you can change it easily.
In your code, you are passing L as a parameter to the countDouble() function, but you don't use it. if you don't need a parameter don't pass it.
when splitting the input, the values of the list are still strings. so you have to invert them to integers (for instance, you can do that with the int() 'function') before comparing their values - otherwise multiplying by 2 will just repeat the string. for example: '13'*2 is the string '1313'
I don't know why you why you added y[0] to itself in line 9, but based on the code that comes after this would yield incorrect results, you don't need to change the elements in order to get their value multiplied by 2.
notice that in the else block, nothing has changed. adding 0 to the count doesn't change it. so you can remove the else block entirely
While it's possible to solve the problem in recursion, there's something else designed for these kind of problems: loops.
The problem is essentially repeating a simple check for every element of a list.
This is how I would arrive to a solution
so we want to run the following 'code':
if(y[0]*2 == y[1]):
count += 1
if(y[1]*2 == y[2]):
count += 1
if(y[2]*2 == y[3]):
count += 1
...
of course the computer doesn't understand what "..." means, but it gives us an idea to the pattern in the code. now we can do the following:
divide the extended 'code' into similar sections.
identify the variables in the pattern - the values that change between sections
find the starting values of all variables
find a pattern in the changes of each variable
find a breaking point, a condition on one of the variables that tells us we have reached the last repeating section.
here are the steps in this specific problem:
the sections are the if statements
the variables are the indexes of the elements in y we compare
the first index starts at 0 and the second at 1
both indexes increase by one after each if-statement
when the second index is bigger then the last index of y then we already checked all the elements and we can stop
so all is left is to set the needed variables, have a while loop with the breaking condition we found, and in the while loop have the general case of the repeating sections and then the changing of the variables.
so:
x = input(f'Enter a list of numbers separated by a space: ')
y = (x.split(' '))
count = 0
# setting the starting values of the variables
index1 = 0
index2 = 1
# creating a loop with the breaking condition
while(index2 < len(y)):
# the general case of the repeated code:
if(int(y[index1]) * 2 == int(y[index2])):
count += 1
# changing the variables for the next loop
index1 += 1
index2 += 1
print(count)
We see that the index2 is just index1 + 1 at all time. so we can replace it like that:
x = input(f'Enter a list of numbers separated by a space: ')
y = (x.split(' '))
count = 0
index1 = 0
while(index1 + 1 < len(y)):
if(int(y[index1]) * 2 == int(y[index1 + 1])):
count += 1
index1 += 1
print(count)
Note: You can use a for loop similarly to the while loop
So in summary, you can use recursion to solve the problem, but the recursion would just be imitating the process of a loop:
in each call, the breaking condition will be checked, the repeated code would run and the variables/parameters would change.
Hope you find this answer useful :)
Final edit: OP edited his example so my other code didnt apply
Some good questions people are asking, but in the spirit of helping, here's a recursive function that returns the count of all doubles.
def get_doubles_count_with_recursion(a_list, count, previous=None):
while a_list:
try:
first = previous if previous else a_list.pop(0)
next_item = a_list.pop(0)
except IndexError:
return count
if next_item / 2 == first:
count += 1
return get_doubles_count_with_recursion(a_list, count, next_item)
return count
a_list = [1, 3, 5, 10, 11, 14, 28, 56, 88, 116, 232, 464, 500]
doubles = get_doubles_count_with_recursion(a_list, 0)
print(doubles == 5)
Probably could clean it up a bit, but it's a lot easier to read than the other guy's ;)
If I'm reading your question right, you want a count of all pairs where the 2nd item is double the first. (and the 14 in the first list is a typo). In which case a simple function like this should do the job:
#a = [2,4,8,16,32]
a = [-5, -10, 0, 16, 32]
count = 0
for i, x in enumerate(a):
# Stop before the list overflows
if i < len(a) - 1:
# If the next element is double the current one, increment the counter
if a[i+1] == x * 2:
count = count + 1
else:
break
print(count)

Palindrome Partitions

Given a string split it into as few strings as possible such that each string is a palindrome
k = "racecarannakayak"
palin = [] # to get palindrome strs
i, j = 0, 2
while j != len(k) + 1:
if k[i:j] == k[:j][::-1]:
palin.append(k[i:j])
i += 1
j += 1
print(palin)
the result is ["racecar"] but it should be ["racecar", "anna", "kayak"]
what is wrong with my code??
There are a few issues with your code.
First, your pointer i keeps iterating even though your point j is
smaller than it in most of the iterations. Meaning you are doing
stuff like k[10:2] which will be pointless.
Second, your pointer j works only once over the whole array. Meaning
you either need multiple passes OR you need to reset the i pointer to
the last position of j pointer IF you find a palindrome.
Third, there is no condition to make sure that single alphabet string
is not considered a palindrome by your inversion logic
Last, there seems to be a typo, as mentioned by #fas in the comments. k[i:j]==k[i:j][::-1] instead of what you have written.
If you consider the above 3 and make the necessary changes you should get the right results to your code as below -
k = "racecarannakayak"
palin = [] # to get palindrome strs
i, j = 0, 2
while j != len(k) + 1:
if k[i:j] == k[i:j][::-1] and len(k[i:j])>2: #Check length is >2
palin.append(k[i:j])
i=j #Reset i pointer to current j
j += 1
print(palin)
['racecar', 'anna', 'kayak']

While loop not giving desired outcome

New to python here, so not sure if I'm posting in the right place but I was wondering why the following loop does not give an answer. I am trying to add all of the numbers previous to i, (1 + 2 + 3 + ... + i), but the loop I came up with does not complete.
j = 0
i = 17
while i > 0:
j = j + i
i - 1
print(j)
I expect j = 153, but the loop doesn't put out anything.
The issue is with i--you're not properly reducing it by 1, so this is an infinite loop. Try this:
j = 0
i = 17
while i > 0:
j = j + i
i -= 1
print(j)
Notice that your line in the while statement,
i-1, does not assign (= operator) a value to i. i stays 17 forever and ever, and so you see no output because the program gets stuck in your while loop. Try to fix your i assignment, like so:
j = 0
i = 17
while i > 0:
j = j + i
i = i - 1 # <= i = i - 1
print(j)
One way to debug your loop in the future is to put a print statement in the middle, like in the code below. The extra print statements slow your program down a bit, but you'll be able to easily see that your loop is going way more than you want!
j = 0
i = 17
while i > 0:
j = j + i
i = i - 1
print(j)
print(i) # so that we can see that i is going down :P
print(j)
Good luck :)
As noted above, you're not re-assigning to your i within the loop. But I'd also note that it's generally a bad idea to modify an iterator within the iteration loop. You can achieve the expected results in a more pythonic way using a range expression, and wrap it in a function also :)
def foo(max_num):
j = 0
for i in range(max_num+1):
j += i
return j
But even better, without the loop:
def foo(max_num):
return sum(range(max_num+1))
There are a couple of ways to do what you want to do. The way I recommend you do it is as shown below.
total = 0
i = 18
for j in range(i):
total += j
print(total)
The range(i) takes a maximum number i, and allows you to iterate through every number starting from 0 to i-1 (e.g. i = 5 will add numbers 0,1,2,3,4), therefore always make i one more than you need it to be if you want to be if you want to include the max number.
As already stated i - 1 wasn't assigned back to i
Following is a pythonic way of implementing the loop with a list comprehension
Code:
j = 0
i = 17
j = sum([j + x for x in range(i, 0, -1)])
print(j)
>>> 153
As a Function:
def sum_of_steps(start: int, stop: int, step: int) -> int:
return sum([x for x in range(start, stop, step)])
j = sum_of_steps(17, 0, -1)
print(j)
>>> 153
The function, sum_of_steps uses type hints or function annotations.
Python Basics: List Comprehensions

How to jump some steps when use python's for statement

I have an interveiw yesterday, when I write python code to achieve some algorithm, I have a question. Some logic can be achieved like this using C:
void get_some_num(int a[], int length) {
int i;
for(i = 0; i < length - 1; ++i) {
if(something) i++; // jump a num in the array
a[i] = 1;
}
return some_num;
}
C language can jump some elements(such as n) when using i += n statement in for-loop to iterate an array, but I find it's difficult to achieve by using python's for statement gracefully.
How can I do it?
If you want to do this in a way similar to C, then just use a while loop (any for loop is really just a specialization of a while loop after all):
i = 0
end = 10
while i < end:
# NOTE: do something with i here
if i == 5:
i += 3
i += 1
Or you can explicitly create and move forward the iterator (which I find a lot less readable):
it = iter(range(0, 10))
for i in it:
if i == 5:
for j in range(0, 3):
i = next(it)
print(i)
Python also supports continue to "skip steps" and continue the loop.
Using it in a for loop:
for i in range(0, 10):
if i == 5:
continue
# will never print 5
print(i)
if you are looking to make it skip a few indexes in your iteration, then you can do something like this with a while loop:
x = range(0, 10)
i = 0
while i <= len(x):
if i == 5:
i += 3
continue
print(i)
i += 1
Output:
0
1
2
3
4
8
9
10
I've never really had much need to skip more than one step, but... You can use a skip variable that you decrement every time you skip.
skip = 0
for i, item in enumerate(my_list[:-1]):
if skip:
skip -= 1
continue
if item == my_list[i+1]:
skip = 1 # or whatever n value you want
continue
# Other code
If you want to do several jumps use the mod operator %
i.e
Jump every 2
for i in range(1, 10):
if i%2 == 0:
print(i)
2
4
6
8
jump every 3
for i in range(1, 10):
if i%3 == 0:
print(i)
3
6
9
An so on.
You have to use continue
for item in loop:
if item == 'wrong':
continue

Python symbol comparison

I have st = 'aaaabbсaa'. My task is if in the string characters repeat then I must write the character plus a number counting the repeats.
My code (but it doesn't work):
st = "aaaabbcaa"
cnt = 0
cnt2 = 0
cnt3 = 0
j = len(st)
i = 0
while i < j:
if st[i] == st[i - 1]:
cnt += 1
print("a" + str(cnt), end="")
elif st[i] == st[i - 1]:
cnt2 += 1
print("b" + str(cnt2), end="")
elif st[i] == st[i - 1]:
cnt3 += 1
print("c" + str(cnt3), end="")
i += 1
Sample Input 1: aaaabbcaa
Sample Output 1: a4b2c1a2
Sample Input 2: abc
Sample Output 2: a1b1c1
This looks like a task for itertools.groupby.
from itertools import groupby
data = 'aaaabbсaa'
compressed = ''.join('{}{}'.format(key, len(list(group))) for key, group in groupby(data))
print(compressed)
Result
a4b2с1a2
This might help to understand what's happening here.
data = 'aaaabbсaa'
for key, group in groupby(data):
print(key, len(list(group)))
Result
a 4
b 2
с 1
a 2
You've got three problems with your code.
First, as gnibbler points out, all of your if/elif conditions are the same. And you don't need a separate condition for each letter, you just need to print the variable (like st[i]) instead of a literal (like "a").
Second, you're trying to print out the current run length for each character in the run, instead of after the entire run. So, if you get this working, instead of a4b2c1a2 you're going to get a1a2a3a4b1b2c1a1a2. You need to keep track of the current run length for each character in the run, but then only print it out when you get to a different character.
Finally, you've got two off-by-one errors. First, when i starts at 0, st[i - 1] is st[-1], which is the last character; you don't want to compare with that. Second, when i finally gets to j-1 at the end, you've got a leftover run that you need to deal with.
So, the smallest change to your code is:
st = "aaaabbcaa"
cnt = 0
j = len(st)
i = 0
while i < j:
if i == 0 or st[i] == st[i - 1]:
cnt += 1
else:
print(st[i - 1] + str(cnt), end="")
cnt = 1
i += 1
print(st[i - 1] + str(cnt))
As a side note, one really easy way to improve this: range(len(st)) gives you all the numbers from 0 up to but not including len(st), so you can get rid of j and the manual i loop and just use for i in range(len(st)):.
But you can improve this even further by looping over an iterable of st[i], st[i-1] pairs; then you don't need the indexes at all. This is pretty easy with zip and slicing. And then you don't need the special handling for the edges either either:
st = "aaaabbcaa"
cnt = 1
for current, previous in zip(st[1:]+" ", st):
if current == previous:
cnt += 1
else:
print(previous + str(cnt), end="")
cnt = 1
I think Matthias's groupby solution is more pythonic, and simpler (there's still a lot of things you could get wrong with this, like starting with cnt = 0), but this should be mostly understandable to a novice out of the box. (If you don't understand the zip(st[1:]+" ", st), try printing out st[1:], list(zip(st[1:], st)), and list(zip(st[1:]+" ", st) and it should be clearer.)
This is kind of a silly way to go about it, but:
def encode(s):
_lastch = s[0]
out = []
count = 0
for ch in s:
if ch == _lastch:
count +=1
else:
out.append(_lastch + str(count))
_lastch = ch
count = 1
out.append(_lastch + str(count))
return ''.join(out)
Example
>>> st = "aaaabbcaa"
>>> encode(st)
'a4b2c1a2'

Categories