Have a good day everyone, pardon my lack of understanding, but I can't seem to figure out why python built in function does not work when being called with another function variable and it just doesn't do what I want at all. Here is the code
def ignoreten(h):
ignoring = False
for i in range (1,len(h)-2):
if ignoring == True and h[i]==10:
h.remove(10)
if ignoring == False and h[i] ==10:
ignoring = True
The basic idea of this is just to decided the first 10 in a list, keep it, continue iterating until you faced another 10, then just remove that 10 to avoid replication, I had searched around but can't seem to find any solution and that's why I have to bring it up here. Thank you
The code you listed
def ignoreten(h):
ignoring = False
for i in range (1,len(h)-2):
if ignoring == True and h[i]==10:
h.remove(10)
if ignoring == False and h[i] ==10:
ignoring = True
Will actually do almost the exact opposite of what you want. It'll iterate over h (sort of, see [1]), and if it finds 10 twice, it'll remove the first occurrence from the list. (And, if it finds 10 three times, it'll remove the first two occurrences from the list.)
Note that list.remove will:
Remove the first item from the list whose value is equal to x. It
raises a ValueError if there is no such item.
Also note that you're mutating the list you're iterating over, so there's some additional weirdness here which may be confusing you, depending on your input.
From your follow-up comment to my question, it looks like you want to remove only the second occurrence of 10, not the first and not any subsequent occurrences.
Here are a few ways:
Iterate, store index, use del
def ignoreten(h):
index = None
found_first = False
for i,v in enumerate(h):
if v == 10:
if not found_first:
found_first = True
else:
index = i
break
if index is not None:
del h[index]
A little more verbose than necessary, but explicit, safe, and modifiable without much fear.
Alternatively, you could delete inside the loop but you want to make sure you immediately break:
def ignoreten(h):
found_first = False
for i,v in enumerate(h):
if v == 10:
if not found_first:
found_first = True
else:
del h[i]
break
Collect indices of 10s, remove second
def ignoreten(h):
indices = [i for (i,v) in enumerate(h) if v == 10]
if len(indices) > 1:
del h[indices[1]] # The second index of 10 is at indices[1]
Clean, but will unnecessarily iterate past the second 10 and collect as many indices of 10s are there are. Not likely a huge issue, but worth pointing out.
Collect indices of 10s, remove second (v2, from comments)
def ignoreten(h):
indices = [i for (i,v) in enumerate(h) if v == 10]
for i in reversed(indices[1:]):
del h[i]
From your comment asking about removing all non-initial occurrences of 10, if you're looking for in-place modification of h, then you almost definitely want something like this.
The first line collects all the indices of 10 into a list.
The second line is a bit tricky, but working inside-out it:
[1:] "throws out" the first element of that list (since you want to keep the first occurrence of 10)
reversed iterates over that list backwards
del h[i] removes the values at those indices.
The reason we iterate backwards is because doing so won't invalidate the rest of our indices that we've yet to delete.
In other words, if the list h was [1, 10, 2, 10, 3, 10], our indices list would be [1, 3, 5].
In both cases we skip 1, fine.
But if we iterate forwards, once we delete 3, and our list shrinks to 5 elements, when we go to delete 5 we get an IndexError.
Even if we didn't go out of bounds to cause an IndexError, our elements would shift and we'd be deleting incorrect values.
So instead, we iterate backwards over our indices, delete 5, the list shrinks to 5 elements, and index 3 is still valid (and still 10).
With list.index
def ignoreten(h):
try:
second_ten = h.index(10, h.index(10)+1)
del h[second_ten]
except ValueError:
pass
The inner .index call finds the first occurrence, the second uses the optional begin parameter to start searching after that. Wrapped in try/except in case there are less than two occurrences.
⇒ Personally, I'd prefer these in the opposite order of how they're listed.
[1] You're iterating over a weird subset of the list with your arguments to range. You're skipping (not applying your "is 10" logic to) the first and last two elements this way.
Bonus: Walrus abuse
(don't do this)
def ignoreten(h):
x = 0
return [v for v in h if v != 10 or (x := x + 1) != 1]
(unlike the previous versions that operated on h in-place, this creates a new list without the second occurrence of 10)
But the walrus operator is contentious enough already, please don't let this code out in the wild. Really.
Related
(This is Python 2.7.10)
I am traversing through a list of sublists that like this:
for x in words:
for subs in bigLsts:
if x in subs:
print() # Here, I want to print a different word in subs that is not the same word
bigLsts is a list of word lists formatted like this:
bigLsts = [
["herro", "hewwo", "holas"],
["woah", "woahwa", "whatda"]
]
If x is in the subs, how can I print another word in the sublist that is not the same word as x? So, if x == "hewwo" how can I print either "herro" or "holas" but not "hewwo"
I have some solutions like generating a random number that does not include the index of that element, but solutions like that feel a bit clunky to me. Is there any cleaner solution?
Generating a random number that is not the index of the element does not have to be clunky. One simple way to do it is to generate a number that is in the range [0, len(subs) - 2] and add one if the number is greater than or equal to the index you want to avoid. You can use the fact that python booleans are a subtype of integers to make the computation very simple:
ind = random.randrange(0, len(subs) - 1)
ind += ind >= subs.index(x)
print(subs[ind])
That being said, you can use an even simpler formulation, courtesy of this unrelated answer:
min((i for i in subs if i != x), key=lambda x: random.random())
The idea is to take the element with the minimum uniformly randomly generated key. The generator automatically handles skipping the element you want to skip without doing an index lookup or ever mentioning indices at all.
In your inner loop, you can consider using the following try statement (to replace the if x in subs):
from random import randrange
try:
idx = subs.index(x)
while True:
a = randrange(0, len(subs))
if a != idx:
break
print(subs[a])
except ValueError:
pass
The try suite tries to find the index in subs with corresponding element equal to the word x.
In the event that x is not found, a ValueError is raised and we quietly ignore it.
In the event that x is found, idx is assigned the corresponding index. Then, we assign a random integer in [0, len(subs) - 1] (i.e. the set of valid indices for subs) to a until a is not equal to idx. Once a is not equal to idx, we break out of the loop and print subs[a].
Drawing inspiration from #Mad Physicist, it would be better if the try suite were replaced with
a = randrange(0, len(subs) - 1)
a += a >= subs.index(x)
which assigns a to a random integer in [0, len(subs) - 1) and then increments a by 1 if a >= idx is true; otherwise (i.e. if a < idx is true), a is unchanged. If this change is made, the try statement becomes
try:
a = randrange(0, len(subs) - 1)
a += a >= subs.index(x)
print(subs[a])
except ValueError:
pass
A slight optimization would be to simply catch all exceptions with except: (replacing except ValueError:). See, for example, this answer. The gist of it is that if an except clause has an expression (here, ValueError), it needs to be evaluated and tested against the raised exception from the try suite. An expression-less except clause (as in except:) does not have to do this additional step. I would not recommend making catching all exceptions a habit, however -- it is context-specific.
Example Session
Suppose that
words = ["green", "eggs", "and", "ham", "hewwo", "woahwa"]
bigLsts = [['herro', 'hewwo', 'holas'], ['woah', 'woahwa', 'whatda']]
A session might yield output
herro
whatda
So I wrote this algorithm where given a set of integers it will remove all integers except 0 and 7 and then it will check if the remaining integers are in a certain order and then will return a boolean. Code below:
def spy_game(nums):
for i in nums:
if i != 0:
if i == 7:
continue
else:
nums.remove(i)
else:
continue
stringlist = [str(o) for o in nums]
mystring = ''.join(stringlist)
return '007' in mystring
spy_game([1,0,2,4,0,7,5])
Now the problem is that if I run
(for example) spy_game([1,0,2,4,0,7,5]) it will not return True regardless of the fact that the sequence of interest is present. After I decided to return the list per se after the filtration process, I found that all numbers except the ones in the middle got filtered out. So in this example, if I return nums it will return [0, 4, 0, 7] although the 4 should've been removed. I am aware that there are more optimal alternatives to this algorithm but I just want to understand why it doesn't work. Thank you.
Instead of modifying the list, use another list to keep track of the wanted numbers.
You should not modify the list while iterating on it.
Here's a cleaned up version
def spy_game(nums):
ans = []
for i in nums:
if i == 0 or i == 7:
ans.append(i)
stringlist = [str(o) for o in ans]
mystring = ''.join(stringlist)
return '007' in mystring
zenwraight's comment says what the problem is: in Python, you can't modify a list while iterating over it.
As for why, the Python documentation discusses this in a note on the for statement's section:
An internal counter is used to keep track of which item is used next, and this is incremented on each iteration. … This means that if the [loop body] deletes the current … item from the sequence, the next item will be skipped (since it gets the index of the current item which has already been treated).
The documentation also describes what happens when you insert an element during a loop, and suggests one possible solution (using a slice to copy the list: for i in nums[:]: ...). In your use case, that solution is likely to work fine, but it is considerably less efficient than options that don't copy the entire list.
A better solution might be to use another list comprehension:
nums = [i for i in nums if i == 0 or i == 7]
write a function that takes, as an argument, a list called aList. It returns a Boolean True if the list contains each of the integers between 1 and 6 exactly once, and False otherwise.
This is homework and I thought I had it right, but it is now telling me that it isn't right. Here is my code.
def isItAStraight(aList):
count = 0
for i in set(aList):
count += 1
return aList.count(i) == 1
for some reason even if a number appears more than once it still gives true and I can't figure out why it won't give me false unless the first or last number are changed.
Each number has to occur only one time otherwise it is false.
So like take [1,2,3,4,5,6]
would be true.
But [1,2,2,3,4,5]
would be false.
Also, I can't import things like Counter or collections (though it would be so much easier to do it isn't apart of the assignment.)
The list is randomly generated from 1 to 6.
With a return inside the loop, you are only checking one value. You need to check each value. Also, instead of looping through the items of the list, you should loop through the items you're actually looking for. It would also help to make sure there are the correct number of items in the list.
def isItAStraight(aList):
if len(aList) != 6:
return False
for i in range(1, 7):
if aList.count(i) != 1:
return False
return True
But the easiest way to do this is to simply sort the list and check if it's what you're looking for:
def isItAStraight(aList):
return sorted(aList) == list(range(1, 7))
You need to be careful about what's inside the list. What you've written is a basically the same as the pseudo-code below:
let count = 0
for every unique element in aList:
Add 1 to count
if count is now 1, return true.
This will always return true if there is at least one element in aList, since you're adding 1 to count and then returning immediately.
A couple approaches to consider:
Create a 6 element list of all zeros called flags. Iterate over aList and set the corresponding element in flags to 1. If flags is all ones, then you return true.
Sort the list, then check if the first six numbers are 1, 2, 3, 4, 5, 6.
I am learning the recursive functions. I completed an exercise, but in a different way than proposed.
"Write a recursive function which takes a list argument and returns the sum of its integers."
L = [0, 1, 2, 3, 4] # The sum of elements will be 10
My solution is:
def list_sum(aList):
count = len(aList)
if count == 0:
return 0
count -= 1
return aList[0] + list_sum(aList[1:])
The proposed solution is:
def proposed_sum(aList):
if not aList:
return 0
return aList[0] + proposed_sum(aList[1:])
My solution is very clear in how it works.
The proposed solution is shorter, but it is not clear for me why does the function work. How does if not aList even happen? I mean, how would the rest of the code fulfill a not aList, if not aList means it checks for True/False, but how is it True/False here?
I understand that return 0 causes the recursion to stop.
As a side note, executing without if not aList throws IndexError: list index out of range.
Also, timeit-1million says my function is slower. It takes 3.32 seconds while the proposed takes 2.26. Which means I gotta understand the proposed solution.
On the call of the function, aList will have no elements. Or in other words, the only element it has is null. A list is like a string or array. When you create a variable you reserve some space in the memory for it. Lists and such have a null on the very last position which marks the end so nothing can be stored after that point. You keep cutting the first element in the list, so the only thing left is the null. When you reach it you know you're done.
If you don't use that condition the function will try to take a number that doesn't exist, so it throws that error.
You are counting the items in the list, and the proposed one check if it's empty with if not aList this is equals to len(aList) == 0, so both of you use the same logic.
But, you're doing count -= 1, this has no sense since when you use recursion, you pass the list quiting one element, so here you lose some time.
According to PEP 8, this is the proper way:
• For sequences, (strings, lists, tuples), use the fact that empty
sequences are false.
Yes: if not seq:
if seq:
No: if len(seq)
if not len(seq)
Here is my amateur thougts about why:
This implicit check will be faster than calling len, since len is a function to get the length of a collection, it works by calling an object's __len__ method. This will find up there is no item to check __len__.
So both will find up there is no item there, but one does it directly.
not aList
return True if there is no elements in aList. That if statement in the solution covers edge case and checks if input parameter is not empty list.
For understand this function, let's run it step by step :
step 0 :
L=[0,1,2,3,4]
proposed_sum([0,1,2,3,4])
L != []
return l[0] + proposed_sum([1,2,3,4])
step 1 calcul proposed_sum([1,2,3,4]):
proposed_sum([1,2,3,4])
L != []
return l[0] + sum([2,3,4])
step 2 calcul proposed_sum([2,3,4]):
proposed_sum([2,3,4])
L != []
return l[0] + sum([3,4])
step 3 calcul proposed_sum([3,4]):
proposed_sum([3,4])
L != []
return l[0] + sum([4])
step 4 calcul proposed_sum([4]):
proposed_sum([4])
L != []
return l[0] + sum([])
step 5 calcul proposed_sum([]):
proposed_sum([])
L == []
return 0
step 6 replace:
proposed_sum([0,1,2,3,4])
By
proposed_sum([]) + proposed_sum([4]) + proposed_sum([3,4]) + proposed_sum([2,3,4]) + proposed_sum([1,2,3,4])+ proposed_sum([0,1,2,3,4])
=
(0) + 4 + 3 + 2 + 1 + 0
Python considers as False multiple values:
False (of course)
0
None
empty collections (dictionaries, lists, tuples)
empty strings ('', "", '''''', """""", r'', u"", etc...)
any other object whose __nonzero__ method returns False
in your case, the list is evaluated as a boolean. If it is empty, it is considered as False, else it is considered as True. This is just a shorter way to write if len(aList) == 0:
in addition, concerning your new question in the comments, consider the last line of your function:
return aList[0] + proposed_sum(aList[1:])
This line call a new "instance" of the function but with a subset of the original list (the original list minus the first element). At each recursion, the list passed in argument looses an element and after a certain amount of recursions, the passed list is empty.
This is my homework.
The problem is to find a way to check whether the items in a list are consecutive or not.
The following is code I've written:
def consecutive(var):
for x in range(2, len(var)):
forward = var[x] - var[x-1]
backward = var[x-1] - var[x-2]
if forward == backward:
return True
else:
return False
var = []
print 'Enter your number:'
while True:
num = raw_input()
if num == '':
break
var += [int(num)]
print consecutive(var)
If I input numbers like 1, 2, 3, 4, 5 then I will get True
If I input numbers like 2, 6, 3, 9, 7, 1, 4 then I'll get False
Here, I succeeded returning True or False values respectively.
But there're two questions that make me upset because if I use my code to solve the questions, I don't get the value that I want (it gives me an error)
First question: Is an empty list considered a consecutive list or not?
Second: Is a list that involves a single value considered a consecutive list or not?
Would you like to help me?
By mathematical convention as discussed in the comments, it would be standard to consider both an empty list and a list with a single element as consecutive, should it not be specified in your homework more precisely.
By the logic of vacuous truth, were a list to not have consecutive order it would require enough elements (2) to break this condition of ordering. To handle this in your existing code, you could simply perform a check before your main check to ensure the base cases of an empty list and a list with one element return True.