Related
I want to rearrange or modify he sequence of elements (strings) in a list. This is the original list
['A', 'B', 'C', 'D', 'E', 'F', 'G']
I want to move E and F behind (or after?) B.
['A', 'B', 'E', 'F', 'C', 'D', 'G']
^^^ ^^^
The decision what to move comes from the user. There is no rule behind and no way to formulate that in an algorithm. In other words the action move something behind something other is input from the user; e.g. the user mark two elements with her/his mouse and drag an drop it behind another element.
My code works and is able to do this. But I wonder if there is a more efficient and pythonic way to do this. Maybe I missed some of Python's nice in-build features.
#!/usr/bin/env python3
# input data
original = list('ABCDEFG')
# move "EF" behind "B" (this is user input)
to_move = 'EF'
behind = 'B'
# expected result
rearanged = list('ABEFCDG')
# index for insertion
idx_behind = original.index(behind)
# each element to move
for c in reversed(to_move): # "reverse!"
# remove from original position
original.remove(c)
# add to new position
original.insert(idx_behind + 1, c)
# True
print(original == rearanged)
You can assume
Elements in original are unique.
to_move always exist in original.
behind always exist in original.
The elements in to_move are always adjacent.
Other example of possible input:
Move ['B'] behind F
Move ['A', 'B'] behind C
This is not possible:
Move ['A', 'F'] behind D
Don't use .remove when the goal is to erase from a specific position; though you may know what is at that position, .remove a) will search for it again, and b) remove the first occurrence, which is not necessarily the one you had in mind.
Don't remove elements one at a time if you want to remove several consecutive elements; that's why slices exist, and why the del operator works the way that it does. Not only is it already harder to iterate when you can say what you want directly, but you have to watch out for the usual problems with modifying a list while iterating over it.
Don't add elements one at a time if you want to add several elements that will be consecutive; instead, insert them all at once by slice assignment. Same reasons apply here.
Especially don't try to interleave insertion and removal operations. That's far more complex than necessary, and could cause problems if the insertion location overlaps the source location.
Thus:
original = list('ABCDEFG')
start = original.index('E')
# grabbing two consecutive elements:
to_move = original[start:start+2]
# removing them:
del original[start:start+2]
# now figure out where to insert in that result:
insertion_point = original.index('B') + 1
# and insert:
original[insertion_point:insertion_point] = to_move
If it is just a small number of items you want to rearrange, just swap the relevant elements:
lst = ['A', 'B', 'C', 'D', 'E', 'F', 'G']
lst[2], lst[4] = lst[4], lst[2] # switch 'C' and 'E'
lst[3], lst[5] = lst[5], lst[3] # switch 'D' and 'F'
lst
['A', 'B', 'E', 'F', 'C', 'D', 'G']
I wanted to extend the class list in python37 with some custom methods.
and ended up reading the UserList cpython code. After reading it new questions arose with regards of [:] usage.
If I understand correctly the `[:]` makes a slice copy of the whole
`self.data`. But I am trying to see what is the point of using `[:]`
at the left side of the `=` operator.
Is there any difference between option one and two? Tried in the python
interpreter, and both seem to have the same effect, am I missing
something?
letters = ['a', 'b', 'c', 'd', 'e', 'f', 'g']
# option (1)
letters[:] = []
# option (2)
letters = []
Now it comes my questions with regards to UserList code. I added comments with questions I have.
class UserList(_collections_abc.MutableSequence):
def __init__(self, initlist=None):
self.data = []
if initlist is not None:
if type(initlist) == type(self.data):
# NOTE: Is this if statement doing the same?
# if isinstance(initlist, list):
self.data[:] = initlist
# NOTE: wouldn't in this case self.data keep a reference to initlist
# instead of a copy?
# self.data[:] = initlist[:] # could one replace that line with this one?
elif isinstance(initlist, UserList):
self.data[:] = initlist.data[:]
# NOTE: would this line accomplish the same?
# self.data = initlist.data[:]
else:
self.data = list(initlist)
...
They don't behave the same if you have another reference to letters.
Scenario 1: modifying letters in place.
>>> letters = ['a', 'b', 'c', 'd', 'e', 'f', 'g']
>>> lst = letters
>>> letters[:] = []
>>> letters
>>> []
>>> lst
>>> []
Scenario 2, reassigning the name letters to an empty list.
>>> letters = ['a', 'b', 'c', 'd', 'e', 'f', 'g']
>>> lst = letters
>>> letters = []
>>> letters
>>> []
>>> lst
>>> ['a', 'b', 'c', 'd', 'e', 'f', 'g']
Since names are reassigned independently, lst does not see any change.
If you had
self.data = initlist
mutations to initlist would affect self.data (since they are the same object in memory).
When you specify a on the left side of the = operator, you are using Python's normal assignment, which changes the name a in the current context to point to the new value. This does not change the previous value to which a was pointing.
By specifying a[0:2] on the left side of the = operator, you are telling Python you want to use Slice Assignment. Slice Assignment is a special syntax for lists, where you can insert, delete, or replace contents from a list.
See: How assignment works with python list slice
Maybe this helps you.
This question already has answers here:
python : list index out of range error while iteratively popping elements
(12 answers)
Closed 6 years ago.
I am trying to remove duplicates from a list. I am trying to do that with below code.
>>> X
['a', 'b', 'c', 'd', 'e', 'f', 'a', 'b']
>>> for i in range(X_length) :
... j=i+1
... if X[i] == X[j] :
... X.pop([j])
But I am getting
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
IndexError: list index out of range
Please help.
When you start to remove items from a list, it changes in size. So, the ith index may no longer exist after certain removals:
>>> x = ['a', 'b', 'c', 'd', 'e']
>>> x[4]
'e'
>>> x.pop()
'e'
>>> x[4]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
IndexError: list index out of range
A simpler way to remove duplicate items is to convert your list to a set, which can only contain unique items. If you must have it as a list, you can convert it back to a list: list(set(X)). However, order is not preserved here.
If you want to remove consecutive duplicates, consider using a new array to store items that are not duplicates:
unique_x = []
for i in range(len(x) - 1):
if x[i] != x[i+1]:
unique_x.append(x[i])
unique_x.append(x[-1])
Note that our range bound is len(x) - 1 because otherwise, we would exceed the array bounds when using x[i+1].
#Rushy's answer is great and probably what I would recommend.
That said, if you want to remove consecutive duplicates and you want to do it in-place (by modifying the list rather than creating a second one), one common technique is to work your way backwards through the list:
def remove_consecutive_duplicates(lst):
for i in range(len(lst) - 1, 1, -1):
if lst[i] == lst[i-1]:
lst.pop(i)
x = ['a', 'b', 'b', 'c', 'd', 'd', 'd', 'e', 'f', 'f']
remove_consecutive_duplicates(x)
print(x) # ['a', 'b', 'c', 'd', 'e', 'f']
By starting at the end of the list and moving backwards, you avoid the problem of running off the end of the list because you've shortened it.
E.g. if you start with 'aabc' and move forwards, you'll use the indexes 0, 1, 2, and 3.
0
|
aabc
(Found a duplicate, so remove that element.)
1
|
abc
2
|
abc
3
|
abc <-- Error! You ran off the end of the list.
Going backwards, you'll use the indexes 3, 2, 1, and 0:
3
|
aabc
2
|
aabc
1
|
aabc
(Found a duplicate so remove that element.)
0
|
abc <-- No problem here!
In the last iteration of your list the value of j will be set to i + 1 which will be the length or 8 in this case. You then try to access X[j], but j is beyond the end of the list.
Instead, simply convert the list to a set:
>>> set(X)
{'e', 'f', 'd', 'c', 'a', 'b'}
unless you need to preserve order, in which case you'll need to look elsewhere for an ordered set.
It is generally not advised to mutate a sequence while iterating it since the sequence will be constantly changing. Here are some other approaches:
Given:
X = ['a', 'b', 'c', 'd', 'e', 'f', 'a', 'b']
If you are only interested in removing duplicates from a list (and order does not matter), you can use a set:
list(set(X))
['a', 'c', 'b', 'e', 'd', 'f']
If you want to maintain order and remove duplicates anywhere in the list, you can iterate while making a new list:
X_new = []
for i in X:
if i not in X_new:
X_new.append(i)
X_new
# Out: ['a', 'b', 'c', 'd', 'e', 'f']
If you would like to remove consecutive duplicates, consider #smarx's answer.
def main():
score = 0
answers = ['B', 'D', 'A', 'A', 'C', 'A', 'B', 'A', 'C', 'D', 'B', 'C', 'D', 'A', 'D', 'C', 'C', 'B', 'D', 'A']
testA = open('newfile.txt', 'r')
for line in testA:
gr = str(line)
if gr == str(answers[line]):
score+=1
testA.close()
checkpass(score)
def checkpass(score):
if score >= 15:
print("You passed")
else:
print("You failed")
main()
I'm trying to write some ^ code which takes a text file and compares its entries to the list recorded above. If the letter in the text file is the same as that of the list at simultaneous indexes, the accumulator should add one. Why can't I check if A == A or B==B in a list? Can someone explain what I'm doing wrong?
When you iterate over a file with for line in testA:, the line variable gets a string containing one line from the file.
But, you're trying to use this string as an index into your answers list (answers[line]). If each line is supposed to match up with the answers sequentially, you need to index one-by-one into the answers list. This means you want to use the line number as an index: answers[line_number].
Luckily, this is simple to do in Python, with enumerate():
for line_number,line_contents in enumerate(testA):
gr = str(line_contents )
if gr == str(answers[line_number]):
When iterating over something, enumerate will give you the index and the item, for every item in the iterable.
Note that line_contents is already a string, so there's no point in the str() cast. Also, since you're comparing each against a single character, you probably want to eliminate any whitespace from the input file as well. We can do that with a call to str.strip():
for line_number,line_contents in enumerate(testA):
gr = line_contents.strip()
if gr == str(answers[line_number]):
When working with files, it's best to use them in a with block. This makes sure that the file is automatically closed, even if something goes wrong during execution:
with open('newfile.txt', 'r') as testA:
for line_number,line_contents in enumerate(testA):
gr = line_contents.strip()
if gr == str(answers[line_number]):
score += 1
Let's look at the problematic part
for line in testA:
gr = str(line)
if gr == str(answers[line]):
score+=1
line is a str type, unlike Javascript which you may be accustomed to, for construct in python returns the value, not the key. So you could use enumerate which will provide a handy index which you can reference the answer like the other answer. However, there is a better construct for this which is zip, which will let you do this:
for answer, line in zip(answers, testA):
if answer == line.strip(): # strip off the trailing newline
score += 1
Combining zip and sum simplifies this function a lot
def main():
answers = ['B', 'D', 'A', 'A', 'C', 'A', 'B', 'A', 'C', 'D', 'B', 'C', 'D', 'A', 'D', 'C', 'C', 'B', 'D', 'A']
with open('newfile.txt', 'r') as testA:
score = sum(i.strip() == j for i, j in zip(testA, answer))
checkpass(score)
I'm not 100% sure on the language you're using, but for most programming languages lists are indexed starting from 0. Meaning that you would have to check the 0th entry of the array against the value you want.
In most languages, such as c++, the syntax is fairly easy to use (you might have to convert for the language you're using.
C++ syntax for checking against a value:
// Ignore the 'char' part if you want, that's not part of Python (I believe).
char someArray[] = { 'a', 'b', 'c' ... }; // Create the list, add a random number of elements
if (someArray[0] == 'a) // This checks to make sure the 0th entry is 'a' before continuing
{
... Do something ...
}
Basically the index is telling you where you want to evaluate within the array, so it requires an integer to do so. If you want to pass a string to it, it won't have any clue what you're talking about.
EDIT: I see by the tag it is in Python, but the general meaning I conveyed should still hold mostly.
Assuming the length of answers and the lines in testA are equal you add a counter for an indexer like so:
def main():
score = 0
answers = ['B', 'D', 'A', 'A', 'C', 'A', 'B', 'A', 'C', 'D', 'B', 'C', 'D', 'A', 'D', 'C', 'C', 'B', 'D', 'A']
testA = open('newfile.txt', 'r')
counter = 0 #initialize counter
for line in testA:
gr = str(line)
if gr == answers[counter]:
score+=1
counter += 1 #increment counter
testA.close()
checkpass(score)
def checkpass(score):
if score >= 15:
print("You passed")
else:
print("You failed")
main()
I am newbie to Python and I have a doubt regarding insert operation on the list.
Example 1:
mylist = ['a','b','c','d','e']
mylist.insert(len(mylist),'f')
print(mylist)
Output:
['a', 'b', 'c', 'd', 'e', 'f']
Example 2:
mylist = ['a','b','c','d','e']
mylist.insert(10,'f')
print(mylist)
Output:
['a', 'b', 'c', 'd', 'e', 'f']
In second example why it still inserts 'f' element in the list even if I am giving index 10 to insert method?
The list.insert function will insert before the specified index. Since the list is not that long anyways in your example, it goes on the end. Why not just use list.append if you want to put stuff on the end anyways.
x = ['a']
x.append('b')
print x
Output is
['a', 'b']
The concept here is "insert before the element with this index". To be able to insert at the end, you have to allow the invalid off-the-end index. Since there are no well-defined "off-the-end iterators" or anything in Python, it makes more sense to just allow all indices than to allow one invalid one, but no others.