I need to perform a triple cut. My function takes as parameter list of int, and somewhere in that list there is 27 and 28. What I need to do is to check what comes first 27 or 28, everything before the 27 or 28(depending upon what comes first) goes to the bottom of the list and everything after 27 or 28 (depending upon what comes second will go the top of the list.
Here is an example:
>>> test_list = [1, 28, 3, 4, 27, 5, 6, 7, 8]
>>> triple_cut(test_list)
>>> test_list = [5, 6, 7, 8, 28, 3, 4, 27, 1]
Here is what I have so far
#find the index positions of the two jokers
find_joker1 = deck.index(27)
find_joker2 = deck.index(28)
print(find_joker1, find_joker2)
new_list = []
# use selection to check which joker occurs first
if(find_joker1 > find_joker2): # joker2(28) occurs first in the list
# loop throgh each element in the list that occurs before Joker1
# and move them to the end of the list
# move element that occur after Joker1(27) to the top of the list
for i in deck:
if(deck.index(i) > find_joker1): # elements that occur after second joker
new_list.append(i) # move those element to the top of the list
new_list.append(28) # add Joker2
for i in deck: # element between the two Jokers
if(deck.index(i) > find_joker2 and deck.index(i) < find_joker1):
new_list.append(i)
new_list.append(27)
for i in deck: # elements before the first joker
if(deck.index(i) < find_joker2):
new_list.append(i)
print(new_list)
Can be solved by slicing.
def triple_cut(lst):
a=lst.index(27)
b=lst.index(28)
if a>b:
return lst[a+1:]+ lst[b:a+1]+ lst[:b]
else:
return lst[b+1:]+ lst[a:b+1]+ lst[:a]
What actually happenning:
Slice everything after the bigger indexed one.
Slice from the lower indexed one to upper indexed one.
Slice everything before the lower indexed one.
Add all together.
N.B: During slicing, first index is inclusive and second index is exclusive.
Demo,
>>test_list = [1, 28, 3, 4, 27, 5, 6, 7, 8]
>>tripplecut(test_list)
[5, 6, 7, 8, 27, 3, 4, 28, 1]
Some explanation:
By slicing, you can get a part of a list. First see, how actually slicing works:
lst[start:end:increment]
For relevance to your question, skip the increment part. Default increment is 1, just what we need. So, we will slice a list like below:
lst[start:end]
Let's do some experiments with your given list.
>>> test_list = [1, 28, 3, 4, 27, 5, 6, 7, 8]
Say, we want a list from index 2(3) to index 5(27). Simply do:
>>> test_list[2:6]
[3,4,27]
Why I've used 6 in place of 5. That's because:
In case of slicing, the start index is inclusive, but the end index is exclusive.
What if we want a list from start to index 4(27). Do:
>> test_list[:5]
[1,28,3,4,27]
If we want index 3 to end? Simply do:
>>test_list[3:]
[4, 27, 5, 6, 7, 8]
Hope that will help you a bit.
Since you don't actually need to know which is which, you can just find the two indexes and slice.
def triple_cut(lst):
first, second = [i for i, e in enumerate(lst) if e in [27, 28]]
return lst[second+1:] + lst[first:second+1]+ lst[:first]
Related
I have a list of numbers. Assume it is a random selection of numbers in the range of 40.
I have a for loop that selects one index in each iteration. after finishing one round in the loop, I save the index I selected in a list and then delete it from the original list (so after each iteration one element is deleted from the original list).
perm = random.sample(range(1, 40), 30)
for i in range(7):
index = random.randrange(len(perm))
perm.pop(index)
In the end, I have a list of indices showing the number of the index I deleted at each iteration say something like l = [5, 15, 6, 7, 11, 3, 8]. Is there an easy way to get the original place of these indices? (for example I deleted 5 first, so when I selected 15 it was actually in the 16th place in the original array and I want to find the original place of all indices)
I tried something simple like this:
for i in range(len(l)):
for j in range(i):
if l[i] >= l[j]:
l[i] += 1
But it does not seem to be quite right. Any ideas on how to make it work with just one loop?
l = [5, 15, 6, 7, 11, 3, 8]
copy = l[:]
for i in range(len(l)):
for j in range(i):
l[i] += 1 if copy[i] >= copy[j] else 0
print(l) # [5, 16, 7, 9, 14, 3, 12]
Like #pavel said, you need to make a working copy. Then your idea works fine.
This is my Python program:
def highest_even(li):
for num in li:
if num % 2 != 0:
li.remove(num)
return li
print(highest_even([10,2,3,4,5,80,15,99]))
And the output is :
[10, 2, 4, 80, 99]
I want to know why 99 wasn't deleted.
Thanks.
It's generally a bad idea to modify a list that you're iterating over, all sorts of strange things can happen. The particular strange thing that's happening here has to do with the way Python iterates over lists.
Think of Python maintaining the position of the current item for iterating. When you delete an item, the others all shift "left" in the list but Python still advances the position. That means, if you have two odd numbers in a row (like 15 and 99)(a), deleting the 15 moves 99 to the left (to where the 15 was) but the next iteration will be looking at where the 99 was before shifting, not where it is now.
For example, consider the list [1, 3, 6, 8], these are the steps Python will take:
List Pos newList newPos comment
---- --- ------- ------ -------
[1, 3, 6, 8] 0 [3, 6, 8] 1 Odd 1, delete, advance.
[3, 6, 8] 1 [3, 6, 8] 2 Even 6, leave, advance.
[3, 6, 8] 2 [3, 6, 8] 3 Even 8, leave, advance.
[3, 6, 8] 3 iteration finished.
You can see that consecutive odd numbers cause a problem here, it will only delete alternate ones.
As to the solution: if, as your code suggests, you just want the even numbers in the list, you can use the much more succinct (and Pythonic) list comprehension (no need even for a function to do this):
myList = [10, 2, 3, 4, 5, 80, 15, 99]
evens = [item for item in myList if item % 2 == 0]
# gives [10, 2, 4, 80]
And, for completeness, since your function name seems to indicate you want the highest even number, that would be something like:
biggestEven = max([item for item in myList if item % 2 == 0])
# gives 80
(a) Your problem actually has nothing to do with the fact the 99 is at the end of the list, any consecutive odd numbers, anywhere in the list, would cause the same issue.
do not modify a list you iterate in
you can copy before iterate on
def highest_even(li):
for num in li.copy():
if num % 2 != 0:
li.remove(num)
return li
print(highest_even([10,2,3,4,5,80,15,99]))
Execution :
[10, 2, 4, 80]
As so many comments mention: it's unsafe (or at least it presents some unexpected behavior) to modify a list as you iterate over it. The usual fix here for lists that aren't giant is simply to copy the list when you go to iterate on it.
for num in li[:]:
# everything as before
That little slice syntax makes Python take the list li, create a new list of its entire contents, and iterate over that, instead. Now since you're removing things from li, but iterating over the copy of li made by slicing it, there's no issue.
this is because of you iterated through the list while editing it.
only_even = []
for n in lst:
if not n % 2:
only_even.append(n)
other methods
only_even = [n for n in lst if not n % 2]
only_even = list(filter(lambda x: not n % 2, lst))
You should never update the data structure you are iterating in, to maintain the invariants of the loops.
If you print the li, and num right after the for num in li:, you'll see that after you remove the element from the list, the next element is skipped, meaning that the indexed is moved forward, same thing happens to 99 element.
You can check it here.
def highest_even(li):
for num in li:
print(li, num)
if num % 2 != 0:
li.remove(num)
return li
gives the output:
In [3]: highest_even([10,2,3,4,5,80,15,99])
([10, 2, 3, 4, 5, 80, 15, 99], 10)
([10, 2, 3, 4, 5, 80, 15, 99], 2)
([10, 2, 3, 4, 5, 80, 15, 99], 3)
([10, 2, 4, 5, 80, 15, 99], 5)
([10, 2, 4, 80, 15, 99], 15)
Out[3]: [10, 2, 4, 80, 99]
You should not iterate through a list and delete the elements of the same list.
Since the index is used iterate in a loop.
0- 10
1- 2
2- 3
3- 4
4- 5
5- 80
6- 15
7- 99
And as when you delete the elements from list it skips the next element.
In your example, for the indexes 0 & 1 nothing changes. But when index =3 and as per the condition this element is removed and the list would be updated to [10,2,4,5,80,15,99].
After index =3 the next index is 4 and li[4] equals 5 and not 4. And your condition is not even checked for the element 4. It just happened to be even to be correct. Having some odd instead of 4 would again give you wrong output.
Same is the case with the last element 99. Since the previous element 15 or index = 6 is removed the length of the list reduced by 1 and it does not loop for index = 4 as index reached its max value of updated list is 5(after removing 3,5 & 15).
You shouldn't update a list while iterating over it. But you can make it work by going backwards or you will be cutting a tree branch while sitting on it.
li = [10,2,3,4,5,80,15,99]
for i in range(len(li) - 1, -1, -1):
if (i%2 != 0 ):
del li[i]
print(li)
What's happening there is that you are changing a list while you iterate over it, but the iterator on that list will not get updated with your changes.
you can try this:
for i in range len(li):
#!/usr/bin/env python
new_trace=[1,2,2,3,2,1,4,3,2,1,3,4,3,5,6,4,7,6,5,4,5,4,6,6,5,6,4,4,5,6,7,7,6,5,5,7,6,5]
def extractIntervals(new_trace):
listofAppearances=[[new_trace[0]],[0],[-1]]
for i in range(0,len(new_trace)-1,1):
if new_trace[i] in listofAppearances[0]:
continue
else:
listofAppearances[0].append(new_trace[i])
listofAppearances[1].append(i)
listofAppearances[2].append(-1)
print(listofAppearances)
for j in range(len(new_trace)-1,0,-1):
for k in range(0,len(listofAppearances[0])-1,1):
if (new_trace[j]==listofAppearances[0][k]) and (listofAppearances[2][k]==-1):
listofAppearances[2][k]=j
else:
continue
print(listofAppearances)
def main():
extractLivenessIntervals(new_trace)
if __name__ == "__main__":
main()
In my code above I am trying to extract Intervals of appearance (delimited by 1st and last appearance indexes of every number in the list) The way I do it is I parse once the list and if the number still doesn't exist in listOfAppearances, then I append it to first column, the index to the second column and I set the 3rd column to -1.
I parse again the list in reverse every element is look for in the listofAppearances and the corresponding 3rd column is changed to the current index if still set to -1.
This works, but the first iteration when parsing the list backward has some issue that I can't figure out. the result I have with this example of a list is:
[[1, 2, 3, 4, 5, 6, 7], [0, 1, 3, 6, 13, 14, 16], [-1, -1, -1, -1, -1, -1, -1]]
[[1, 2, 3, 4, 5, 6, 7], [0, 1, 3, 6, 13, 14, 16], [9, 8, 12, 27, 37, 36, -1]]
As you can see the last element of the second list is still set to -1 which I don't understand why! I inspected every inch of the code and I can't see why is it this way!
Just change
for k in range(0, len(listofAppearances[0])-1, 1):
to
for k in range(0, len(listofAppearances[0]), 1):
in line 17.
Edit: you can get the same result by:
def extractIntervals(new_trace):
listofAppearances = [0, 0, 0]
listofAppearances[0] = list(set(new_trace))
# returns new_trace without repeated elements
listofAppearances[1] = [new_trace.index(i) for i in list(set(new_trace))]
# returns a list with the index of the first occurrence
# in new_trace of each element in list(set(new_trace))
listofAppearances[2] = [len(new_trace) - 1 - new_trace[::-1].index(i) for i in list(set(new_trace))]
# returns a list with the index of the last occurrence
# in new_trace of each element in list(set(new_trace))
print(listofAppearances)
Might I suggest processing a stream of values? First define a few helper functions, then use them to group each element with the positions at which it occurs.
from itertools import groupby
from operator import itemgetter
second = itemgetter(1)
first_and_last = itemgetter(0, -1)
def sort_and_group(seq, k):
return groupby(sorted(seq, key=k), k)
def extract_intervals(new_trace):
tmp1 = sort_and_group(enumerate(new_trace), second)
tmp2 = [(val, *first_and_last([x for x,_ in positions])) for val, positions in tmp1]
return zip(*tmp2)
new_trace=[1,2,2,3,2,1,4,3,2,1,3,4,3,5,6,4,7,6,5,4,5,4,6,6,5,6,4,4,5,6,7,7,6,5,5,7,6,5]
print(list(extract_intervals(new_trace)))
tmp1 is a pairing of each element with the list of positions at which it occurs.
tmp2 is a list of triples, consisting of a list element and the first and last position at which it occurs.
The call to zip "unzips" the list of triples into three tuples: the elements, the first positions, and the last positions.
I need to design a function that takes a list of int as a parameter and in that list we look at the last element it will have some value v, Then we take v cards from the top of the deck and put them above the bottom most card in the deck.
Here is an example:
>>> test_list = [1, 28, 3, 4, 27, 8, 7, 6, 5]
>>> insert_top_to_bottom(test_list)
>>> test_list = [8, 7, 6, 1, 28, 3, 4, 27, 5]
value = deck[-1] # value of the last element
I believe this will do
def insert_top_to_bottom(test_list, v):
return test_list[v : -1] + test_list[:v] + [test_list[-1]]
test_list = [1, 28, 3, 4, 27, 8, 7, 6, 5]
test_list = insert_top_to_bottom(test_list, 5)
print test_list
Here is my attempt, though you should next time show your own attempts.
Please note that I did not include any type of checking or avoiding errors. Just the basics.
def top_to_bottom(l):
last = l[len(l)-1]
move_these = l[:last]
move_these.append(l[len(l)-1])
del l[:last]
del l[len(l)-1]
l.extend(move_these)
I hope I could help.
EDIT
I didn't make it a one-line funtion so you can understand a bit better.
Since you asked, here is some more explanaition about slicing.
my_list[x:y] is a basic slice. This will output my_list from index x to (but excluding index y).
You can leave out either, which will just fill up that part as far as possible.
For example, using
my_list[:5]
will give you my_list from the beginning to (but excluding!) index 5.
If the list is [0, 1, 2, 3, 4, 5, 6], it will give [0, 1, 2, 3, 4]. If you want to get a list from a certain index until the end, you leave out the 'y' part. Like so:
my_list[3:]
Applying that on the list previously stated, you will get [3, 4, 5, 6].
I hope you understood! Comment if you have any more questions.
I lost a little bit of time in this Python for statement:
class MyListContainer:
def __init__(self):
self.list = []
def purge(self):
for object in self.list:
if (object.my_cond()):
self.list.remove(object)
return self.list
container = MyListContainer()
# now suppose both obj.my_cond() return True
obj1 = MyCustomObject(par)
obj2 = MyCustomObject(other_par)
container.list = [obj1, obj2]
# returning not an empty list but [obj2]
container.purge()
It doesn't work as I expected because when the cycle in "purge" delete the first object in list the second one is shifted to the beginning of the list and the cycle is ended.
I solved duplicating self.list before the for cycle:
...
local_list = self.list[:]
for object in local_list:
...
I suppose that the for statement stop working because I'm changing the length of the original list. Can someone clarify this point ?
And is there a more "elegant" way to solve this problem ? If I have more than few elements inside the list, duplicating it every time does not seem a good idea.
Maybe the filter() function is the right one but i whish to have some other approach if any.
I'm a newbie.
To summarize your useful answers:
Never edit a list you are looping
Duplicate the list or use list comprehensions
Duplicating a list could not waste your memory or in this case who's mind about it
Don't try. Just don't. Make a copy or generate a new list.
Just make yourself a new list:
def purge(self):
self.list = [object for object in self.list if not object.my_cond()]
return self.list
Reserve any optimization until you've profiled and found that this method really is the bottleneck of your application. (I bet it won't be.)
Filter (or list comprehension) IS the way to go. If you want to do it inplace, something like this would work:
purge = []
for i,object in enumerate(self.list):
if object.mycond()
purge.append(i)
for i in reversed(purge):
del self.list[i]
Or alternatively, the purge list can be made with a comprehension, a shortcut version looks like:
for i in reversed([ i for (i,o) in enumerate(self.list) if o.mycond() ]):
del self.list[i]
In python variables are actually labels to data. Duplicating a list is, for the most part, making a new set of pointers to the data from the first list. Don't feel too bad about it.
List comprehensions are your friend.
e.g.
>>> a = range(20)
>>> a
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
>>> [ x for x in a if x % 2 == 0 ]
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
Your second solution in which you duplicate the list is the right way to go. Afterward you can just replace the old list with the duplicate if need be.
It's perfectly safe to shorten the list in place if you do it in reverse!
>>> a=range(20)
>>> for i in reversed(range(len(a))):
... if a[i]%2: del a[i]
...
>>> a
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
Another way is to reassign the whole slice
>>> a=range(20)
>>> a[:]=(x for x in a if not x%2)
>>> a
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
If the items in the list are unique, this works too
>>> a=range(20)
>>> for item in reversed(a):
... if item%2: a.remove(item)
...
>>> a
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
Here is some more explanation in response to yuri's comment
Suppose we have
>>> a=[0,1,2,3,4,5]
Now trying naively to delete the 3rd and 4th items
>>> del a[3]
>>> del a[4]
>>> a
[0, 1, 2, 4] # didn't work because the position of all the item with index >=3 was changed
However if we do the del's in the opposite order
>>> a=[0,1,2,3,4,5]
>>> del a[4]
>>> del a[3]
>>> a
[0, 1, 2, 5] # this is the desired result
Now extend that idea over a for loop with a removal condition, and you see that removal from the live list is possible
indeces = []
minus = 0
for i in range(self.list):
if cond(self.list[i]):
indeces.append(i)
for i in indeces:
self.list = self.list[:(i-minus)].extend(self.list[i-minus+1:])