Generators function - python

MAX_STUDENTS = 50
def get_student_ids():
student_id = 1
while student_id <= MAX_STUDENTS:
# Write your code below
n = yield student_id
if n != None:
student_id = n
continue
student_id += 1
student_id_generator = get_student_ids()
for i in student_id_generator:
# Write your code below
if i == 1:
i = student_id_generator.send(25)
print(i)
Im quite confused, when i run the code below, i understand the the send function gives 25 as the yield value and assigns it to n, however when entering the if statement, checking if n is not None, wouldnt this create an infinite loop, since n is not none, would take us to the continue statement, which takes us back to the next iteration of the while loop, completely skipping the incrementing of student id

The first thing one can notice is that n will always be of type none except when you send 25 into the generator.
Let's look at the flow of the program
this is the driver code at the start of the program
student_id_generator = get_student_ids() #creates a generator class
for i in student_id_generator: #interates through the generator class
if i == 1: # note that the first i == 1, so the first time the code hits
#this condition, it will be true
i = student_id_generator.send(25) #sets n to be 25
print(i) #for the first iteration, will return 1, as the first yield is 1
now, since n=25 (you just sent 25 into the generator code), let's look at the generator
n = yield student_id # n=25
if n != None: #since n==25, n is not none
student_id = n #student_id = 25
continue #go back to the while loop
student_id += 1 # will not be run
Now that student_id = 25, for the next iteration in the driver code, it will yield 25, so 25 will be printed. But n will be None as nothing is sent to the generator, so student_id += 1 will be run. From there, the while loop kicks in and it will continue until 'student_id == 50', where the loop breaks.
Will there be an infinite loop? No. Because the condition 'n != None' only occurs once.
I hope this helps. If you are still confused, my suggestion is to take out a pen and paper and work out what happens to the code step by step. I did that to help myself understand it.

Related

python for loop only updates 2 of 3 variables?

I want to use 3 variables in one for loops.
This is what I tried:
def loop_player_listbox():
global bol_loop, count
bol_loop = True
while True:
time.sleep(1)
str_libo_p = listbox.get(0,tk.END)
str_libo_r = listboxr.get(0,tk.END)
str_libo_price = listboxprice.get(0,tk.END)
for i,r,p in itertools.product(str_libo_p, str_libo_r, str_libo_price):
text_playername = wait.until(EC.element_to_be_clickable((By.XPATH,('/html/body/main/section/section/div[2]/div/div[2]/div/div[1]/div[1]/div[1]/div/input'))))
text_playername.click()
time.sleep(1)
text_playername.clear()
time.sleep(1)
text_playername.click()
text_playername.send_keys(i)
user_input_max_price = p
try:
choose_player = wait.until(EC.element_to_be_clickable((By.XPATH,("//span[#class='btn-text' and contains(text(),'"+i+"')]")))) and wait.until(EC.element_to_be_clickable((By.XPATH,("//span[#class='btn-subtext' and contains(text(),'"+r+"')]"))))
choose_player.click()
count = 0
while count < 5:
while True:
# some unimportant code
# set speed
# some unimportant code
# set max BIN price
while True:
#user_input_max_price = input('Enter max buy now price (>250):')
user_input_max_price = p
if user_input_max_price.isdigit():
int_user_input_max_price = int(user_input_max_price)
if int_user_input_max_price > 250:
break
else:
print('Max buy now price must be >250')
continue
else:
continue
# set max price
# some unimportant code
# Buy until 100 players were bought
while count < 5:
# some unimportant code
count +=1
The problem is, variable r and p do update after count = 5.
But i is always only showing the first item from my listbox and does not update. Every time my loop is coming back to the line for i,r,p ... I see in debug that only the variables r and p updated.
I have no idea why. While searching for how to combine more than one forloops I found itertoolsand so I used it.
Maybe anyone sees whast wrong?
Changed from itertools to zip fixed the issue.
for i,r,p in zip(str_libo_p, str_libo_r, str_libo_price):

If, else return else value even when the condition is true, inside a for loop

Here is the function i defined:
def count_longest(field, data):
l = len(field)
count = 0
final = 0
n = len(data)
for i in range(n):
count = 0
if data[i:i + l] is field:
while data[i - l: i] == data[i:i + l]:
count = count + 1
i = i + 1
else:
print("OK")
if final == 0 or count >= final:
final = count
return final
a = input("Enter the field - ")
b = input("Enter the data - ")
print(count_longest(a, b))
It works in some cases and gives incorrect output in most cases. I checked by printing the strings being compared, and even after matching the requirement, the loop results in "OK" which is to be printed when the condition is not true! I don't get it! Taking the simplest example, if i enter 'as', when prompted for field, and 'asdf', when prompted for data, i should get count = 1, as the longest iteration of the substring 'as' is once in the string 'asdf'. But i still get final as 0 at the end of the program. I added the else statement just to check the if the condition was being satisfied, but the program printed 'OK', therefore informing that the if condition has not been satisfied. While in the beginning itself, data[0 : 0 + 2] is equal to 'as', 2 being length of the "field".
There are a few things I notice when looking at your code.
First, use == rather than is to test for equality. The is operator checks if the left and right are referring to the very same object, whereas you want to properly compare them.
The following code shows that even numerical results that are equal might not be one and the same Python object:
print(2 ** 31 is 2 ** 30 + 2 ** 30) # <- False
print(2 ** 31 == 2 ** 30 + 2 ** 30) # <- True
(note: the first expression could either be False or True—depending on your Python interpreter).
Second, the while-loop looks rather suspicious. If you know you have found your sequence "as" at position i, you are repeating the while-loop as long as it is the same as in position i-1—which is probably something else, though. So, a better way to do the while-loop might be like so:
while data[i: i + l] == field:
count = count + 1
i = i + l # <- increase by l (length of field) !
Finally, something that might be surprising: changing the variable i inside the while-loop has no effect on the for-loop. That is, in the following example, the output will still be 0, 1, 2, 3, ..., 9, although it looks like it should skip every other element.
for i in range(10):
print(i)
i += 1
It does not effect the outcome of the function, but when debugging you might observe that the function seems to go backward after having found a run and go through parts of it again, resulting in additional "OK"s printed out.
UPDATE: Here is the complete function according to my remarks above:
def count_longest(field, data):
l = len(field)
count = 0
final = 0
n = len(data)
for i in range(n):
count = 0
while data[i: i + l] == field:
count = count + 1
i = i + l
if count >= final:
final = count
return final
Note that I made two additional simplifications. With my changes, you end up with an if and while that share the same condition, i.e:
if data[i:i+1] == field:
while data[i:i+1] == field:
...
In that case, the if is superfluous since it is already included in the condition of while.
Secondly, the condition if final == 0 or count >= final: can be simplified to just if count >= final:.

How to reset Yield and stop iteration

I am facing some problems regarding a generator. I have a list of 1000 elements. I want to read single item one by one and do some operation. The operation is something like comparing with some specific value. If I able to find that value from the list I want to stop iteration and reset the yield again.
I looking for the funtionality how to reset __next__ pointer in generator. Also I have to make 100 object in runtime FN_SOVLS.
class FN_SOV1S:
def __init__(self,elementlist,idxNo):
self._elementlist = elementlist
self._idxNo =idxNo
setup()
process()
def setup(self):
try:
self.df = pd.read_excel(r'D:\OPCUA\Working_VF1.xls', sheet_name='Valve1S')
for tag,col in self.readcmd():
if col==4:
self.cmd = tag
if col == 5:
self.openFB = tag
if col == 6:
self.clsFB = tag
if col == 7:
self.delaytime = tag
except Exception as e:
log_exception(e)
def process(self):
for tagname,tagvalue in self.searchValueBytag():
if tagname == self.cmd:
if tagvalue == 1:
sleep(self.delaytime)
gen.writegeneral.writenodevalue(self.openFB,1)
gen.writegeneral.writenodevalue(self.clsFB,0)
else:
gen.writegeneral.writenodevalue(self.openFB, 0)
gen.writegeneral.writenodevalue(self.clsFB, 1)
def searchValueBytag(self):
n = 0
while n < len(self._elementlist):
tagname, tagvalue = self._elementlist[n]
yield tagname, tagvalue
n =+ 1
The condition is to reset Generator function is:
for tagname,tagvalue in self.searchValueBytag():
if tagname == self.cmd:
You cannot "reset" a running generator.
What you can do is break out of the for-loop that uses the generator.
Later you can then create the generator anew by calling searchValueBytag again.
I don't completely understand your question, but hopefully, this can help. This uses a flag that will continue restarting the generator until the self.cmd value is no longer found.
Obviously, this is not identical to your code and more is needed for it to work perfectly, but you can easily use this flag to reset the generator
def generator():
# arbitrary length
length = 100
n = 0
while n < length:
yield n
n += 1
# create a complete flag that is only true when the end of the iteration is reached
complete = False
# keep trying until complete is true
while not complete:
# restarts the generator by making a new one
g = generator()
# keeps going until 'break'
while True:
# try/catch because next returns error when the end of the generator is reached
# when the end is reached we know that to turn complete to true
try:
# get the next val in the iterator
value = next(g)
# if value is the reset flag, then break out of while loop and restart generator
if value == RESET_FLAG:
break
except:
# StopIteration exeption received, job finished
complete = True
break
def generator():
length = 100
n = 0
while n < length:
yield n
n += 1

How can I average multiple outputs independently in python

PYTHON 3: Hi, so I have this piece of code
for money in range(0, 2501, 500):
print("{} Euro".format(money), end='')
throws = 0
d = trump.possession(board)
while False in d.values():
prevLoc = piece.location
piece.move(trump.throw())
throws += 1
if piece.location < prevLoc:
money += 200
if board.names[piece.location] in d and d[board.names[piece.location]] == False and money >= board.values[piece.location]:
money -= board.values[piece.location]
d[board.names[piece.location]] = True
return throws
and this code takes 0 money to start with, runs the code, looks for amount of throws required to buy the entire board, does the same with 500 starting money, 1000 and so forth
my question is, how can i take the average of the throws to buy the entire board for each starting value? the way my code is now it returns the amount of throws for all the starting values, but simulated once, so it may not be accurate.
I searched a lot, and tried some things but i had problems with this one because I want to like run it, say for example, 2000 times, and get the average for each starting value for the money.
anyone got any tips for this? been struggling on it for a while..
i tried making a for loop from 0 to 2000 and then inside of that another for loop that prints 0-2500 and then uses the code below in a function, appends the return value of throws into a list and sums it up and devides it by 2000, it did not turn out so good...
I'm going to assume this is in a function, due to the return statement. You need to collect outputs into a list and then average that at the end.
def calc_throws(simulations):
throw_list = []
average = lambda x: sum(x)/len(x)
for i, money in enumerate(range(0, 2501, 500)):
print("{} Euro".format(money), end='')
throw_list.append([money, []])
for _ in range(simulations):
throws = 0
d = trump.possession(board)
while False in d.values():
prevLoc = piece.location
piece.move(trump.throw())
throws += 1
if piece.location < prevLoc:
money += 200
if board.names[piece.location] in d and d[board.names[piece.location]] == False and money >= board.values[piece.location]:
money -= board.values[piece.location]
d[board.names[piece.location]] = True
throw_list[i][1].append(throws)
throw_list[i][1] = average(throw_list[i][1])
return throw_list
Rather than a single number, this returns a list of lists like #[[0,20],[500,15],...[2500,3]] (or whatever reasonable numbers are) which gives you the average for each amount of starting money.

Incrementing in a for loop

So my issue is with this not incrementing correctly... I tried to uses an int "step" to + 1 every time this loop is ran but it doesn't do anything. Why is that? Also when I print(step) it only adds up to 337. It does not go the full 1000 like I had thought I asked it too. How do I do this correctly?
lockers = []
step = 3
locker = 0
while len(lockers) <= 1000:
lockers.append(1)
for i in range(0, len(lockers)):
lockers[i] = 0
for i in range(0, len(lockers), 2):
lockers[i] = 1
for i in range(0, len(lockers), step):
if lockers[i] == 0:
lockers [i] = 1
else:
lockers[i] = 0
step += 1
print(lockers)
range gives you an iterable object:
>>> range(10,20 , 2)
range(10, 20, 2)
>>> list(range(10,20 , 2))
[10, 12, 14, 16, 18]
The values in it are fully decided as soon as the call returns, and aren't re-evaluated each time around the loop. Your step only goes up to 337 because you are incrementing it once for each element in the object range(0, 1000, 3), which has 334 items, not 1000:
>>> len(range(0,1000,3))
334
To get something that works like range but advances the step, you would need to write your own generator:
def advancing_range(start, stop, step):
''' Like range(start, stop, step) except that step is incremented
between each value
'''
while start < stop:
yield start
start += step
step += 1
You can then do for i in advancing_range(0, 1000, 3): and it will work as you intend.
But this is a very strange thing to want to do. Judging by your variable names, I would guess you're coding the locker problem, which says:
A new high school has just been completed. There are 1,000 lockers
in the school and they have been numbered from 1 through 1,000.
During recess (remember this is a fictional problem), the students
decide to try an experiment. When recess is over each student will
walk into the school one at a time. The first student will open all
of the locker doors. The second student will close all of the locker
doors with even numbers. The third student will change all of the
locker doors that are multiples of 3 (change means closing lockers
that are open, and opening lockers that are closed.) The fourth
student will change the position of all locker doors numbered with
multiples of four and so on. After 1,000 students have entered the
school, which locker doors will be open, and why?
But the advancing range logic says something more like "the first student opens the first locker, then the second opens the second locker after that, then the third student opens the third locker after that ...". You want to affect multiple lockers each time, but further spaced out. Essentially, you want to copy and paste your first two loops another 998 times with a one higher step each time. Of course, you can do better than copy and paste, and this seems like you want two nested loops, where the outer one advances the step that the inner one uses. That would look like this:
for step in range(1, len(lockers)):
for i in range(step, len(lockers), step):
Simplifying your other logic by using booleans instead of 1 and 0, the whole program looks like this:
lockers = [True] * 1000
for step in range(1, len(lockers)):
for i in range(step, len(lockers), step):
lockers[i] = not lockers[i]
print(sum(lockers))
It prints that the number of open lockers is 969.
If you want to adjust the step size while iterating, you can have an own range object:
class AdjustableRange(object):
def __init__(self, start, stop, step):
self.start = start
self.stop = stop
self.step = step
self.value = None
def __iter__(self):
if self.value is None:
self.value = start
while self.value < self.stop:
yield self.value
self.value += self.step
This (untested) one you can use for iterting like
rg = AdjustableRange(0, len(lockers), step):
for i in rg:
if lockers[i] == 0:
lockers [i] = 1
else:
lockers[i] = 0
rg.step += 1 # this influences the iteration
But, as was already said, there are better ways to solve your "real" problem.

Categories

Resources