cant break out of Python While loop - python-3.x

I'm quite new to Python so sorry in advance, I'm trying to break out of a While loop if a random number is not within a List of numbers.
Despite testing the output of the functions and confirming that an integer is in the List and it is in fact an integer and other methods return both True and False the While Statement ignores the value. See demo code.
import random,time
list=[i for i in range(10)]
print(list)
print(list[6]*10) # this returns an integer
if list[6]==12/2:
print('this evaluates as a int')
this=99 # sentry to run the while loop **** but cant index by
if 10/2 in list:
print('this also evaluates as an integer')
print(type(list))
print(type(this))
while this not in list:
this = random.randrange(9)
print(this,this in list)
time.sleep(.200)
list[this] = '*'
>>>
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
60
this evaluates as a int
this also evaluates as an integer
<class 'list'>
<class 'int'>
6 True
3 True
4 True
5 True
5 False

Its obvious there's a flaw in the While function where it is unable see True or False correctly, thank you to those who proposed suggestions but i come up with my own.
I created a sentry boolean to pass the value to the While function
sentry=True
while sentry:
this = random.randrange(9)
print(this,this in list)
if this not in list:
sentry=False
time.sleep(.200)
list[this] = '*'

The while loop condition should evaluate to False when you want to break out of the loop. In your case, since you want to exit when this is not in list, then set your condition to this in list.
Here is your code with the fixed condition, keep in mind that since the list is directly modified from inside the loop, the loop will terminate after one iteration.
import random, time
list=[i for i in range(10)]
print(list)
this = random.randrange(9)
while this in list:
this = random.randrange(9)
print(this, this in list)
time.sleep(.200)
list[this] = '*'
print(list)
If you did not want the loop to terminate after one iteration, you may want to consider relocating the expression list[this] = '*'. (Though this solution may also end after one iteration based on probability)
import random, time
list=[i for i in range(10)]
print(list)
this = random.randrange(9)
while this in list:
list[this] = '*' # moved here
print(list)
this = random.randrange(9)
print(this, this in list)
time.sleep(.200)
EDIT based on comment
Since the goal is to replace integers with *'s until no integers remain in the original list, here are some modifications.
The while loop exits when the number of elements replaced is equal to the number of items in the original array. The indices that have not been replaced yet are stored in the array called available.
import random, time
lst = [i for i in range(10)]
print(lst)
num_replaced = 0
# available = list.copy()
available = list(range(len(lst)))
while num_replaced < len(lst):
this = random.choice(available)
available.remove(this) # the index just chosen is no longer available
lst[this] = '*'
num_replaced += 1

Related

The best way of iterating through an array whose length changes in Python

I am implementing an algorithm which might affect the size of some array, and I need to iterate through the entire array. Basically a 'for x in arrayname' would not work because it does not update if the contents of arrayname are changed in the loop. I came up with an ugly solution which is shown in the following example:
test = np.array([1,2,3])
N = len(test)
ii=0
while ii < N:
N = len(test)
print(test[ii])
if test[ii] ==2:
test = np.append(test,4)
ii+=1
I am wondering whether a cleaner solution exists.
Thanks in advance!
Assuming all the elements are going to be added at the end and no elements are being deleted you could store the new elements in a separate list:
master_list = [1,2,3]
curr_elems = master_list
while len(curr_elems) > 0: # keep looping over new elements added
new_elems = []
for item in curr_elems: # loop over the current list of elements, initially the list but then all the added elements on second run etc
if should_add_element(item):
new_elems.append(generate_new_element(item))
master_list.extend(new_elems) # add all the new elements to our master list
curr_elems = new_elems # and prep to iterate over the new elements for next iteration of the while loop
The while loop seems the best solution. As the condition is re-evaluated at each iteration, you don’t need to reset the length of the list in the loop, you can do it inside the condition:
import random
l = [1, 2, 3, 4, 5]
i = 0
while i < len(l):
if random.choice([True, False]):
del l[i]
else:
i += 1
print(f'{l=}')
This example gives a blueprint for a more complex algorithm. Of course, in this simple case, it could be coded more simply with a filter, or like this:
l = [1, 2, 3, 4, 5]
[x for x in l if random.choice([True, False])]
You might want to check this related post for more creative solutions: How to remove items from a list while iterating?

If condition working differently for same value in python

I am trying to write a function which will return True or False if the given number is not greater than 2.
So simple, but the if condition is returning different outputs for same value '2'. The code I used is:
The code I used is:
ele_list = [1,2,3,2]
for i in ele_list:
if not i>2:
print(i,False)
ele_list.remove(i)
print(ele_list)
The ouput I am receiving is:
1 False
[2, 3, 2]
2 False
[3, 2]
I am confused to see that the first 2 in the list is passing through the if condition but the second 2 in the list is not passing through the condition. Please help me figure out this..
Removing elements from the list you're looping over is generally a bad idea.
What's happening here is that when you're removing an element, you're changing the length of the array, and therefor changing what elements are located at what indexes as well as changing the "goal" of the forloop.
Lets have a look at the following example:
ele_list = [4,3,2,1]
for elem in ele_list:
print(elem)
ele_list.remove(elem)
In the first iteration of the loop elem is the value 4 which is located at index 0. Then you're removing from the array the first value equal to elem. In other words the value 4 at index 0 is now removed. This shifts which element is stored at what index. Before the removal ele_list[0] would be equal to 4, however after the removal ele_list[0] will equal 3, since 3 is the value that prior to the removal was stored at index 1.
Now when the loop continues to the second iteration the index that the loop "looks at" is incremented by 1. So the variable elem will now be the value of ele_list[1] which in the updated list (after the removal of the value 4 in the previous iteration) is equal to 2. Then you're (same as before) removing the value at index 1 from the list, so now the length of the list just 2 elements.
When the loops is about to start the third iteration it checks to see if the new index (in this case 2) is smaller than the length of the list. Which its not, since 2 is not smaller than 2. So the loop ends.
The simplest solutions is to create a new copy of the array and loop over the copy instead. This can easily be done using the slice syntax: ele_list[:]
ele_list = [1,2,3,2]
for elem in ele_list[:]:
if not elem > 2:
print(elem, False)
ele_list.remove(elem)
print(ele_list)
the problem is that you're modifying your list as you're iterating over it, as mentioned in #Olian04's answer.
it sounds like what you really want to do, however, is only keep values that are > 2. this is really easy using a list comprehension:
filtereds_vals = [v for v in ele_list if v > 2]
if you merely want a function that gives you True for numbers greater than 2 and False for others, you can do something like this:
def gt_2(lst):
return [v > 2 for v in lst]
or, finally, if you want to find out if any of the values is > 2 just do:
def any_gt_2(lst):
return any(v > 2 for v in lst)
I think the problem here is how the remove function interacts with the for function.
See the documentation, read the "note" part:
https://docs.python.org/3.7/reference/compound_stmts.html?highlight=while#grammar-token-for-stmt
This can lead to nasty bugs that can be avoided by making a temporary copy using a slice of the whole sequence
A possible solution, as suggested into the documentation:
ele_list = [1,2,3,2]
for i in ele_list[:]:
if not i>2:
print(i,False)
ele_list.remove(i)
print(ele_list)
"""
1 False
[2, 3, 2]
2 False
[3, 2]
2 False
[3]
"""

Is there a way to return true for an empty list in a sort invariant?

So I am supposed to write a function that returns true if the said sequence is arranged from smallest to largest. I am able to understand strings and num lists but for an empty list, I am unable to understand why it won't return as true.
This is for understanding and possibly help me understand how to manipulate my loops better.
def is_sorted(seq):
for i in range(0, len(seq)):
if seq[i]<seq[i+1]:
return True
else:
return False
print(is_sorted([])) # expected to be true but returns none
The obvious problem is that with an empty list you are returning None which evaluates to false. The other problem is that you are using return inside of a loop, which means that you aren't evaluating every sequence in the iterable.
#hiro protagonist's answer is one solution to this problem. I offer my alternative using all and a generator expression.
def is_sorted(seq):
return all(seq[i] < seq[i + 1] for i in range(len(seq) - 1))
# All evaluate True
print(is_sorted(['a', 'b', 'c']))
print(is_sorted([1, 2, 3]))
print(is_sorted([]))
# Both evaluate False
print(is_sorted(['a', 'c', 'b']))
print(is_sorted([0, 1, -1]))
Edit with explanation
As best I understand it, all works by stepping through an iterable and returns False if any value in it evaluates to False, otherwise returning True.
As the comments may show you, I don't have a good understanding of Python generators. A generator is an iterable object that calculates the next value and yields it back each time it is referenced.
The generator defined above, each time that all references it, calculates seq[i] < seq[i + 1] and gives that value back. If this is False at any time then all will return False.
I hope this helps. I'm sure one of the good people in the comments will correct any flawed understanding that I have.
Your implementation is wrong. It will return True for [1, 3, 2] since it only compares the first 2 elements (return returns after the first iteration).
It can be fixed by checking for the opposite condition, then return True after the loop.
You should also iterate until len(seq) - 1 otherwise the last iteration will cause an IndexError.
def is_sorted(seq):
for i in range(0, len(seq) - 1):
if seq[i] > seq[i + 1]:
return False
return True
print(is_sorted([1, 2, 3]))
# True
print(is_sorted([1, 3, 2]))
# False
print(is_sorted([]))
# True
And of course there is the trivial, naive solution,
def is_sorted(seq):
return seq == sorted(seq)
this is a variant that also works for empty lists and lists of length 1:
from itertools import islice
def is_sorted(seq):
return all(i <= j for i, j in zip(seq, islice(seq, 1, None)))
it iterates over seq[k] and seq[k+1] using zip and islice. and only if all elements satisfy the requirement True will be returned.
Its because for an empty list the code inside the for is not reached. So neither return statement isnt reached. Also you should take into account that a list with only one element should also return True. Solution:
def is_sorted(seq):
for i in range(0, len(seq)-1):
if seq[i]>=seq[i+1]:
return False
return True

Python- How to fix failed testcase on a has repeat function?

I've built a function that checks for repeats of a specific number in a list named xs. V is the number to check for repeats of. It needs to return True if there are more than one occurrences of the number and if there are none, it needs to return False.
I'm failing one test case which is input xs=[1,2,1] v=1, this function needs to return True, but my code is making it False. Can you see where I went wrong?
Here is my current code:
def has_repeat(xs, v):
count=0
for num in range(len(xs)):
if num == v:
count+=1
if count>1:
return True
else:
return False
You're actually iterating over the range of the length of the list, not the items in the list.
The range function returns a list of numbers from 0 (by default) to the number you provide, in this case 3 (not inclusive). See Python documentation.
As an example if you try:
l = [1, 2, 3]
print(range(len(l)))
It will print out [0, 1, 2]
What you should do is instead of
for num in range(len(xs))
do
for num in xs:
You can try it out on PyFiddle here
As an added tasty bonus, you could change this to use the .count method on your list of items to check how many occurrences of that number are in the list, removing the need to iterate the list at all, like so:
count = xs.count(v)

Insert Sort Index Error?

I'm attempting to create an insert Sort which takes the smallest number from a list and appends it to another list.
The problem is, everything I attempt to pop() the number out of the list, I get an index error.
Here's my code:
alist = [2,9,8,6,1]
blist =[]
def insertsort(list, slist) :
for item in list:
smallest = list[0]
if item < smallest:
smallest = list[item]
list.pop(smallest)
slist.append(smallest)
insertsort(alist, blist)
print(blist)
And the error is:
IndexError: pop index out of range
Thanks in advance for any help.
When a function call fails, reads the docs, or use, in this case, >>> help(list.pop). Much faster that waiting hours for someone to answer a trivial question.
The argument to pop is an index of a value in the list, not a value itself. Your code has several other problems once this is fixed.
alist = [2,9,8,6,1]
blist =[]
def insertsort(inlist, outlist):
while inlist:
it = iter(inlist)
sdex = 0
small = next(it)
for dex, item in enumerate(it, 1):
if item < small:
sdex = dex
small = item
inlist.pop(sdex)
outlist.append(small)
insertsort(alist, blist)
print(blist)
prints [1, 2, 6, 8, 9]
The following version of the function uses the builtin min function and gives the same output.
def insertsort(inlist, outlist):
while inlist:
small = min(inlist)
inlist.pop(inlist.index(small))
outlist.append(small)

Resources