How do I increment a counter inside a while test of Python - python

How would you translate the following Java idiom to Python?
Comparable[] a, int lo, int hi;
int i = lo, j = hi+1;
Comparable v = a[lo];
while (a[++i] < v) if (i == hi) break;
My problem is that in the while test I cannot have a ++i or i += 1.

The problem you can't do that way in Python is a restriction of Python syntax. Let's get what does while look like from documentation:
while_stmt ::= "while" expression ":" suite
["else" ":" suite]
As You can see you must put expression before ":", while x += 1 is a statement (and statements doesn't return any value so cannot be used as a condition).
Here's what does this code look like in Python:
i += 1
while a[i] < v:
if i == hi:
break
i += 1
Though it works, it's most likely not a Python way to solve your problem. When you have a collection and you want to use indices you have to look forward to redesigning your code using for loop and enumerate built-in function.
P.S.
Anyway, direct code porting between languages with absolutely different philosophies is not a good way to go.

The Java code sets i to the index of either the first element >= a[lo], or to hi, whichever appears first. So:
v = a[lo]
for i in range(lo+1, hi+1):
if a[i] >= v:
break

If you want to iterate over all the objects in a list or any "iterable" thing, use "for item in list". If you need a counter as well, use enumerate. If you want a range of numbers use range or xrange.
But sometimes you really do want a loop with a counter that just goes up which you're going to break out of with break or return, just like in the original poster's example.
For these occasions I define a simple generator just to make sure I can't forget to increment the counter.
def forever(start=0):
count = start
while True:
yield count
count += 1
Then you can just write things like:
for count in forever():
if do_something() == some_value:
break
return count

The list class has a built-in method that does this kind of searching, but for whatever reason it only compares for equality. Of course, we can hack that:
class hax:
def __init__(self, value): self.value = value
def __eq__(self, other): return other >= self.value
a.index(hax(a[lo]), lo + 1, hi + 1)
... but please don't :)
Anyway, not only should you not be trying to port the code directly, as #Rostyslav suggests - you shouldn't really be trying to port the problem directly. There's something very strange about a Python program that uses lists in a way that would allow a problem like this to come up.

Related

python build a list recursively

Let's say I have a string
S = "qwertyu"
And I want to build a list using recursion so the list looks like
L = [u, y, t, r, e, w, q]
I tried to write code like this:
def rec (S):
if len(S) > 0:
return [S[-1]].append(rec(S[0:-1]))
Ideally I want to append the last element of a shrinking string until it reaches 0
but all I got as an output is None
I know I'm not doing it right, and I have absolutely no idea what to return when the length of S reaches 0, please show me how I can make this work
(sorry the answer has to use recursion, otherwise it won't bother me)
Thank you very much!!!
There are many simpler ways than using recursion, but here's one recursive way to do it:
def rec (S):
if not S:
return []
else:
temp = list(S[-1])
temp.extend(rec(S[:-1]))
return temp
EDIT:
Notice that the base case ensures that function also works with an empty string. I had to use temp, because you cannot return list(S[-1]).extend(rec(S[:-1])) due to it being a NoneType (it's a method call rather than an object). For the same reason you cannot assign to a variable (hence the two separate lines with temp). A workaround would be to use + to concatenate the two lists, like suggested in Aryerez's answer (however, I'd suggest against his advice to try to impress people with convoluted one liners):
def rec (S):
if not S:
return []
else:
return list(S[-1]) + rec(S[:-1])
In fact using + could be more efficient (although the improvement would most likely be negligible), see answers to this SO question for more details.
This is the simplest solution:
def rec(S):
if len(S) == 1:
return S
return S[-1] + rec(S[:-1])
Or in one-line, if you really want to impress someone :)
def rec(S):
return S if len(S) == 1 else S[-1] + rec(S[:-1])
Since append mutates the list, this is a bit difficult to express recursively. One way you could do this is by using a separate inner function that passes on the current L to the next recursive call.
def rec(S):
def go(S, L):
if len(S) > 0:
L.append(S[-1])
return go(S[0:-1], L)
else:
return L
return go(S, [])
L = [i for i in S[::-1]]
It should work.

for loops in Python - how to modify i inside the loop

This code was written in Python 3.6 in Jupyter Notebooks. In other languages, I am pretty sure I built loops that looked like this:
endRw=5
lenDF=100 # 1160
for i in range(0, lenDF):
print("i: ", i)
endIndx = i + endRw
if endIndx > lenDF:
endIndx = lenDF
print("Range to use: ", i, ":", endIndx)
# this line is a mockup for an index that is built and used
# in the real code to do something to a pandas DF
i = endIndx
print("i at end of loop", i)
In testing though, i does not get reset to endIndx and so the loop does not build the intended index values.
I was able to solve this problem and get what I was looking for by building a while loop like this:
endRw=5
lenDF=97 # 1160
i = 0
while i < lenDF:
print("i: ", i)
endIndx = i + endRw
if endIndx > lenDF:
endIndx = lenDF
print("Range to use: ", i, ":", endIndx)
# this line is a mockup for an index that is built and used
# in the real code to do something to a pandas DF
i = endIndx
print("i at end of loop: ", i)
Question: is there a way to modify the i from inside the for loop in python? Is there a way to do what I did with the while loop using a for loop in Python?
Solved the problem with while but just curious about this.
You can modify the loop variable in a for loop, the problem is that for loops in Python are not like "old-style" for loops in e.g. Java, but more like "new-style" for-each loops.
In Python, for i in range(0, 10): does not behave like for (int i = 0; i < 10; i++) {, but like for (int i : new int[] {0, 1, ..., 10}}.
That is, in each iteration of the loop, the loop head will not modify the loop variable (e.g. increment it), but assign a new value to it, i.e. the next value from the given iterable (a range in your case). Thus, any modification that you did in the previous iteration are overwritten.
If you want to loop a known number of iterations or for every item in an iterable, use a for loop, but if you want to loop until a certain condition (no longer) holds, as in your case, use while.
for loops operate on iterables. In for i in range(0, lenDF), i is assigned the next value in the range on each round of the loop regardless of how it is used in the loop. The question then, is whether there is a clean way to write an iterable that does what you want. In this case, all you want is to advance by a fixed step and adjust the final step length to account for the end of data.
endRw=5
lenDF=97 # 1160
for i in range(0, lenDF, endRw):
endIndx = min(i+endRw, lenDF)
print("Range to use: ", i, ":", endIndx)
This answer is unlikely to be useful, but since you were just curious:
The closest I think to what you want to do would be using a generator and its send method:
>>> def jumpable_range(start, stop):
... i = start
... while i <= stop:
... j = yield i
... i = i + 1 if j is None else j
...
>>> R = jumpable_range(2, 10)
>>>
>>> for i in R:
... if i==5:
... i = R.send(8)
... print(i)
...
2
3
4
8
9
10
>>>
Taking the original question literally:
#Tobias_k provides a good explanation of when to use while versus for loops, and the use case of this question fits while better (at least for Python). In short: you cannot directly modify the i in for i in because of how this code works under the covers in Python. So while should be used for a use case where you need to change your counter inside a loop (in Python).
#tdelaney provides a good answer in terms of refactoring the code using a Python for loop given the way Python behaves (the accepted answer to this question).
#PaulPanzer provides concepts that, while over-complicated, are useful to students to explore new concepts; but the answer solves the for loop problem by using a while loop inside an iterator and calling that into the for loop.
Even so, the concepts explored that play to the use of yield and iterators are worth exploring. If we take these concepts and attempt to re-write the original code to exploit them, this is what that code would look like:
def jumpable_range(start, stop):
i = start
while i <= stop:
j = yield i
i = i + 1 if j is None else j
endRw=5
lenDF=97 # 1160
Q = jumpable_range(0,lenDF)
for i in Q:
print("i: ", i)
endIndx = i + endRw
if endIndx > lenDF:
endIndx = lenDF
if i == endIndx: break
print("Range to use: ", i, ":", endIndx)
# this line is a mockup for an index that is built and used
# in the real code to do something to a pandas DF
i = Q.send(endIndx-1)
print("i at end of loop", i)
You always can set the value of i in a for loop. The problem is your setting value statement is before the implicit for loop setting value statement and covered by latter. You cannot change the rule to make latter statement useless. You shouldn't do this even you can. Just change to use proper conditional variable.

Python loop doesn't update variable?

I'm trying to make a loop that turn a function (like f(x)=(2x+3)(2x-3)) into a better format for editing, simply by adding a '+' before numbers (it would become f(x)=(+2x+3)(+2x-3)). The problem is that in the loop, after I insert a new char in the middle of the string, the string doesn't update, so when the loop goes on and I try to access a certain index of the function string, the char isn't correct.
def rewriteFunction(function):
for i, c in enumerate(function):
newFunction += c
if(str(c).isdigit()):
if not(i == 0):
if not(Sign.isSign(function[i - 1])):
function = function[:i] + "+" + function[i:]
If possible, could you answer me by sending the exact (corrected) code, without modifying it too much, of course if that's the right method to do that. Thanks in advance!!
In one line you store your updated data in the variable newFunction, but in another you store your updates back into function. For consistency, let's never change function and apply all of our updates to newFunction.
You never initialize newFunction.
You never explicitly return anything from rewriteFunction().
Try this:
def rewriteFunction(function):
newFunction = ''
for i, c in enumerate(function):
if(str(c).isdigit()):
if not(i == 0):
if not(function[i - 1] in '+-'):
newFunction += '+'
newFunction += c
return newFunction
assert rewriteFunction('f(x)=(2x+3)(2x-3)') == 'f(x)=(+2x+3)(+2x-3)'
If your solutions isn't bound to using loops, you may give a try to regular expressions to simplify things:
>>> import re
>>> s = 'f(x)=(2x+3)(2x-3))'
>>> re.sub(r'\b(?<![+-])(\d+)', r'+\1', s)
'f(x)=(+2x+3)(+2x-3))'
Feel free to ask any questions about the solution.

How do I double my step variable for each for-loop iteration in Python 3?

I'm new to Python, and I'm playing around with recursive functions just for practice.
I made the following algorithm which takes a number as x and halves it until it is equal to 1. n is the number of times x has been halved.
def binary(x, n = 0):
print(n,":",x)
x = x // 2
n += 1
if x > 0:
binary(x, n)
return x
return x
I'm trying to make a loop that will call binary() with multiple values for x. I want my step to be doubled each iteration. I have it working with a while loop like the one below.
i = 1
while i < 1000000000:
print("when x is", i, ":")
binary(i)
i += i
For some reason though, I can't seem to achieve the same thing with a For loop. Here's what I have now.
for i in range(1,1000):
print("when x is", i, ":")
binary(i)
i += i
In the code above, i += i does not seem to effect the i in my header. I know that range() takes a third parameter called step, but I've tried this:
for i in range(1,1000, i += i):
# statements
This gives me a name error, and says "i is not defined".
Most of my experience with programming is in JavaScript and C#. In both of those languages I wouldn't of had any trouble doing this.
How would I get this to work in a For loop using Python 3?
The third parameter of range is indeed step. But you should use it with a computed value like:
for i in range(1,1000,2):
#statements
The reason why your i += i didn't worked is because, under the hood, the for-loop is executing something similar to i = next(...) at the end of an iteration, overiding your last increment.
Edit
You can achieve the desired effect using a generator, but it kinda kills the "trying to avoid while-loops" thing:
def myrange(start, stop):
i = start
while i < stop:
yield i
i += i
for i in myrange(1, 1000):
print(i)
Anyway, while-loops are perfectly valid constructs and I’d personnally go with one in this case. Do not forget that the for-loop has a rather different semantic in python than in both languages you’re used to. So trying to use a for-loop because you are able to do so with javascript seems like a bad idea if all what you need is a while-loop.
range can step by a fixed amount, but not a variable amount. Use a while-loop to increment i by i:
i += i
You could replace the while-loop with an iterator, such as:
import itertools as IT
for i in (2**i for i in IT.count()):
if i >= 1000000000: break
print("when x is", i, ":")
binary(i)
but I don't think this has any advantage over a simple while-loop.
If all you're doing is doubling i, then why not just raise it to the power?
for p in range(int(1000000000**0.5)):
print(binary(2**p)

Index Error in python

I am new to programming, and I am trying to write a Vigenère Encryption Cipher using python. The idea is very simple and so is my function, however in this line:
( if((BinKey[i] == 'b')or(BinKey[i+1] == 'b')): )
It seems that I have an index problem, and I can't figure out how to fix it.
The error message is:
IndexError: string index out of range
I tried to replace the i+1 index by another variable equal to i+1, since I thought that maybe python is re-incrementing the i, but it still won't work.
So my questions are:
How to fix the problem, and what have I done wrong?
Looking at my code, what can I learn to improve my programming skills?
I want to build a simple interface to my program (which will contain all the encryption ciphers), and all I came up with from Google is pyqt, but it just seems too much work for a very simple interface, so is there a simpler way to build an interface? (I am working with Eclipse Indigo and pydev with Python3.x)
The Vigenère Encryption function (which contains the line that causes the problem) is:
def Viegner_Encyption_Cipher(Key,String):
EncryptedMessage = ""
i = 0
j = 0
BinKey = Bin_It(Key)
BinString = Bin_It(String)
BinKeyLengh = len(BinKey)
BinStringLengh = len(BinString)
while ((BinKeyLengh > i) and (BinStringLengh > j)):
if((BinKey[i] == 'b')or(BinKey[i+1] == 'b')):
EncryptedMessage = EncryptedMessage + BinKey[i]
else:
EncryptedMessage = EncryptedMessage + Xor(BinKey[i],BinString[j])
i = i + 1
j = j + 1
if (i == BinKeyLengh):
i = i+j
return EncryptedMessage
This is the Bin_It function:
def Bin_It(String):
TheBin = ""
for Charactere in String:
TheBin = TheBin + bin(ord(Charactere))
return TheBin
And finally this is the Xor function:
def Xor(a,b):
xor = (int(a) and not int(b)) or (not int(a) and int(b))
if xor:
return chr(1)
else:
return chr(0)
In your while condition, you are ensuring that i < len(BinKey). This means that BinKey[i] will be valid, but BinKey[i+1] will not be valid at the last iteration of your loop, as you will then be accessing BinKey[len(BinKey)], which is one past the end of your string. Strings in python start at 0 and end at len-1 inclusive.
To avoid this, you can update your loop criterion to be
while BinKeyLength > i+1 and ...:
You can either change
while ((BinKeyLengh > i) and (BinStringLengh > j)):
to
while ((BinKeyLengh > i-1) and (BinStringLengh > j)):
or change
if((BinKey[i] == 'b')or(BinKey[i+1] == 'b')):
to
if((BinKey[i] == 'b') or (BinKeyLengh > i-1 and BinKey[i+1] == 'b')):
This will avoid trying to go into BinKey[BinKeyLength], which is out of scope.
Looking at my code, what can I learn to improve my programming skills?
Looping over an index is not idiomatic Python. It's better to loop over the elements of an iterator where possible. After all, that's usually what you're interested in: for i in... is often followed by my_list[i].
In this example, you should use the built-in function zip (or itertools.izip if your code is lazy, though this isn't necessary in Python 3), which gives you pairs of values from two or more iterators, and stops when the shortest one is exhausted.
for key_char, string_char in zip(BinKey, BinString): # takes values sequentially from
# BinKey and BinString
# and binds them to the names
# key_char and string_char
# do processing on key_char and string_char
If you really must do a while loop on an index, then put the test the other way around so it's clearer what you're doing. Compare
while len(BinKey) > i and len(BinString) > j: # this looks like len(BinKey) and
# len(BinString) are varying and you're
# comparing them to static variables i and j
with
while i < len(BinKey) and j < len(BinString): # this looks like you're varying i and j
# and comparing them to len(BinKey) and len(BinString)
Which better communicates the purpose of the loop?
Finally, the clause
if (i == BinKeyLengh):
i = i+j
doesn't seem to do anything. If i == BinKeyLength then the while loop will stop in a moment anyway.
I think your error, as Python interpreter says, is that you accessing invalid array positions.
In order to solve this, unlike it's being said, you should change your code to
while (BinKeyLength > i+2 and ...):
This is because in the last step, BinKeyLength = i+2, then i+1 is BinKeyLength-1, which is the last position of your array.
Concerning your programming skills I recommend you two things:
Be the code. Sounds mystic but the most important thing that is missing here is figuring out which indices numbers are used.
As it has been said by rubik, follow some style guides like PEP8 style guide.

Categories

Resources