This question already has answers here:
Why does this iterative list-growing code give IndexError: list assignment index out of range? How can I repeatedly add (append) elements to a list?
(9 answers)
Closed 2 years ago.
m learning python from the google tutorials. am stuck on an exercise related to lists.
getting an index error
lis[j]=words.pop()[i]
IndexError: string index out of range
i need to sort the list but the words starting with x should be the first ones.
code is
def front_x(words):
i=0
lis=[]
j=0
k=0
words.sort()
while i<len(words):
if words[i][0:1]=="x":
lis[j]=words.pop()[i]
j+=1
i+=1
lis.extend(words)
while k<len(lis):
print(lis[k])
k+=1
return
lis is an empty list, any index will raise an exception.
If you wanted to add elements to that list, use lis.append() instead.
Note that you can loop over sequences directly, there is no need to keep your own counter:
def front_x(words):
lis = []
words.sort()
for word in words:
if word.startswith("x"):
lis.append(word)
for entry in lis:
print(entry)
You can reduce this further by immediately printing all words that start with x, no need to build a separate list:
def front_x(words):
for word in sorted(words):
if word.startswith("x"):
print(word)
If you wanted to sort the list with all x words coming first, use a custom sort key:
def front_x(words):
return sorted(words, key=lambda w: (not w.startswith('x'), w))
sorts the words first by the boolean flag for .startswith('x'); False is sorted before True so we negate that test, then the words themselves.
Demo:
>>> words = ['foo', 'bar', 'xbaz', 'eggs', 'xspam', 'xham']
>>> sorted(words, key=lambda w: (not w.startswith('x'), w))
['xbaz', 'xham', 'xspam', 'bar', 'eggs', 'foo']
i need to sort the list but the words starting with x should be the first ones.
Complementary to the custom search key in #Martijn's extended answer, you could also try this, which is closer to your original approach and might be easier to understand:
def front_x(words):
has_x, hasnt = [], []
for word in sorted(words):
if word.startswith('x'):
has_x.append(word)
else:
hasnt.append(word)
return has_x + hasnt
Concerning what was wrong with your original code, there are actually three problems with the line
lis[j]=words.pop()[i]
lis[j] only works if the list already has a jth element, but as you are adding items to an initially empty list, you should use lis.append(...) instead.
You want to remove the word starting with "x" at index i from the list, but pop() will always remove the last item. pop() is for stacks; never remove items from a list while looping it with an index!
You apply the [i] operator after you've popped the item from the list, i.e., you are accessing the ith letter of the word, which may be much shorter; thus the IndexError
Related
If I have a list similar to
mylist = ["danny","florence","micheal","klrv","yrcv"]
And I want to remove the strings that do not have vowels in them and put them in a new list, resulting in me have two lists. How do I do it?
I tried coding it manually
mylist_no_vowel = ["klrv","yrcv"]
Then using .remove to make original list smaller.
But I feel there is a code to automate it instead of doing it manually.
The quick-and-dirty one-liner is:
mylist = ["danny","florence","micheal","klrv","yrcv"]
mylist_no_vowel = [word for word in mylist if not any(character in 'aeiou' for character in word)]
Explanation
The second line uses a syntax called a list comprehension, which is a Python idiom for building a list (in this case, by filtering another list). The code is equivalent to:
mylist = ["danny","florence","micheal","klrv","yrcv"]
mylist_no_vowel = []
for word in mylist:
if not any(character in 'aeiou' for character in word):
mylist_no_vowel.append(word)
Translating, we start an empty list called mylist_no_vowel, then we iterate through each word in the original list mylist. For each word, we check if any of their characters is a vowel, using the any function. If that's not the case, we add the word to the mylist_no_vowel list.
Note that I changed your starting list variable name from list to mylist, since list is actually a reserved word in Python - you're overwriting the built-in list function by naming a variable like that!
The following codes should serve the purpose.
mylist = ["danny", "florence", "micheal", "klrv", "yrcv"]
mylist_no_vowel = list(filter(lambda x: set('aeiou') & set(x)==set(), mylist))
print(mylist_no_vowel)
Here is the result, which is the same as your expected result:
['klrv', 'yrcv']
The idea is to use the anonymous function to filter out each element x of mylist that contains vowels via keeping each element x of mylist such that the intersection of set(x) and set('aeiou') is the empty set set().
This question already has answers here:
How to remove items from a list while iterating?
(25 answers)
Closed 2 years ago.
Can someone explain to me why this code removes only one element from the list not each as I would expect?
So I have a list, let's say it is: [1,2,'a','b']
I want to remove each string from the list.
def filter_list(l):
for i in l:
if type(i) == str:
l.remove(i)
filter_list([1,2,'a','b'])
So after this I receive = [1,2,'b'] which is confusing.
Check this out:
l = [1,2,'a','b']
print([item for item in l if isinstance(item, int)])
I suggest you use isinstance built-in function instead of type. And also it is better to check is an integer in case of your list has an item that different from string or integer.
If you want to remove it, you can simply assign the new list on the old one.
l = [1,2,'a','b']
l = [item for item in l if isinstance(item, int)]
print(l)
Why you cannot over loop:
Let me explain.
def filter_list(l):
for index, i in enumerate(l):
if type(i) == str:
l.remove(i)
return l
filter_list([1,2,'a','b'])
1st iteration the index of for loop is 0:
i becomes 1, and no need to remove it.
2nd iteration the index of for loop is 1:
i becomes 2, and no need to remove it.
3rd iteration the index of for loop is 2:
i becomes 'a', and need to remove it.
At this point, we simply removed the 'a'. The length of the list changed.
It decreased by 1. The new list became [1, 2, 'b'] because we removed 'a'.
4th iteration the index of for loop is 3:
Since we removed the variable in the previous iteration, the list no longer has the index of 3. The loop will raise StopIteration error(IndexError), and breaks.
That is why we cannot do this properly.
Lambda Function:
[*filter(lambda x: isinstance(x, int), l)]
Data:
l = [1,2,'a','b']
I have a list:
foolist = ['123-asd', '234-asd', '345-asd']
I want to find the index of the string that contains '234'. At the moment I have this:
boollist = ['234' in i for i in foolist]
which would produce a list of True and Falses for each index for foolist.
Then to find the index, [print(i) for i,j in enumerate(foolist) if j==True]
This seems abit unecessary, does anyone have a more eloquent method of finding the index of a string in list based on a part of that string
You can use next with a generator expression and split by a specific character to reflect the structure of your strings:
foolist = ['123-asd','234-asd','345-asd']
res = next(i for i, j in enumerate(foolist) if '123' in j.split('-')[0]) # 0
If there can be multiple matches, you can use a list comprehension:
res = [i for i, j in enumerate(foolist) if '123' in j.split('-')[0]] # [0]
For equality, you should use j.split('-')[0]] == '123' as your condition instead.
Note on print within a comprehension
It's good practice to avoid print in a list comprehension, as it will return None. You will notice that, while your terminal will print the indices it finds, you are also building a list of None items with length equal to the number of matches. For all intents, that list has no use.
If you are only interested in printing items, you can use a for loop with a generator expression:
for idx in (i for i, j in enumerate(foolist) if '123' in j.split('-')[0]):
print(idx)
I have to create this function that has as inputs a String and a list of strings; and as output a list of the indices of strings that contain the String. I have done it, but then I should ordinate the indices according to the occurrences of the String in the strings. How can i do that? This is my code:
I added the 'count' under 'if' to count the occurrences, how can i use it to ordinate the indices according to that?
You can add a list of counts in each string to your function,
def function(s,lst):
l=[]
counts = []
for i in range(len(lst)):
if s in lst[i]:
counts += [lst[i].count(s)]
l += [i]
return l, counts
Here counts is a list in which each entry is the count of occurrences of s in the string in your input list. The function now returns two lists in a tuple, for example with the first tuple element being l and the second being counts. Note that i=-1 is redundant here as i is an element of the iterable made with range and assigning a value to it before the loop doesn't change it's loop value.
You can now sort the first list based on the second list using a line modified from this post,
out_fun = function(s,inp)
out = [x for x,_ in sorted(zip(out_fun[0],out_fun[1]), key = lambda x: x[1], reverse=True)]
inp is the list of strings, for example inp = ["hello", "cure", "access code"]. out_fun is the return tuple of two lists from the function function. s is the string of interest - here as in your original example it is 'c'.
What this line does is that it first creates a list of tuples using zip, where each first element of the tuple is is element from the list of indices and the second is from the list of occurrences. The program then sorts the tuples based on the second element in reverse order (largest first). The list comprehension fetches only the first element from each tuple in the sorted result, which is again the index list.
If you have questions about this solution, feel free to ask. You have a Python 2.7 tag - in Python 3.X you would need to use list(zip()) as zip returns a zip object rather than a list.
This is a more concise version of your program:
def function(s,lst):
t = [(i,x.count(s)) for i,x in enumerate(lst) if s in x]
return t
It uses a list comprehension to create and return a list of tuples t with first element being the index of the string that has the character s and second being the count. This is not necessarily more efficient, that would need to be checked. But it's a clean one-liner that at least to me is more readable.
The list of tuples can than be sorted in a similar way to the previous program, based on second tuple element i.e. count,
out_fun = function(s,inp)
out = [x for x,_ in sorted(out_fun, key = lambda x: x[1], reverse=True)]
I'm doing an exercise as following:
# B. front_x
# Given a list of strings, return a list with the strings
# in sorted order, except group all the strings that begin with 'x' first.
# e.g. ['mix', 'xyz', 'apple', 'xanadu', 'aardvark'] yields
# ['xanadu', 'xyz', 'aardvark', 'apple', 'mix']
# Hint: this can be done by making 2 lists and sorting each of them
# before combining them.
sample solution:
def front_x(words):
listX = []
listO = []
for w in words:
if w.startswith('x'):
listX.append(w)
else:
listO.append(w)
listX.sort()
listO.sort()
return listX + listO
my solution:
def front_x(words):
listX = []
for w in words:
if w.startswith('x'):
listX.append(w)
words.remove(w)
listX.sort()
words.sort()
return listX + words
as I tested my solution, the result is a little weird. Here is the source code with my solution: http://dl.dropbox.com/u/559353/list1.py. You might want to try it out.
The problem is that you loop over the list and remove elements from it (modifying it):
for w in words:
if w.startswith('x'):
listX.append(w)
words.remove(w)
Example:
>>> a = range(5)
>>> for i in a:
... a.remove(i)
...
>>> a
[1, 3]
This code works as follows:
Get first element, remove it.
Move to the next element. But it is not 1 anymore because we removed 0 previously and thus 1 become the new first element. The next element is therefore 2 and 1 is skipped.
Same for 3 and 4.
Two main differences:
Removing an element from a list inside loop where the list is being iterated doesn't quite work in Python. If you were using Java you would get an exception saying that you are modifying a collection that is being iterated. Python doesn't shout this error apparently. #Felix_Kling explains it quite well in his answer.
Also you are modifying the input parameter words. So the caller of your function front_x will see words modified after the execution of the function. This behaviour, unless is explicitly expected, is better to be avoided. Imagine that your program is doing something else with words. Keeping two lists as in the sample solution is a better approach.
Altering the list you're iterating over results in undefined behaviour. That's why the sample solution creates two new lists instead of deleting from the source list.
for w in words:
if w.startswith('x'):
listX.append(w)
words.remove(w) # Problem here!
See this question for a discussion on this matter. It basically boils down to list iterators iterating through the indexes of the list without going back and checking for modifications (which would be expensive!).
If you want to avoid creating a second list, you will have to perform two iterations. One to iterate over words to create listX and another to iterate over listX deleting from words.
That hint is misleading and unnecessary, you can do this without sorting and combining two lists independently:
>>> items = ['mix', 'xyz', 'apple', 'xanadu', 'aardvark']
>>> sorted(items, key=lambda item: (item[0]!='x', item))
['xanadu', 'xyz', 'aardvark', 'apple', 'mix']
The built-in sorted() function takes an option key argument that tells it what to sort by. In this case, you want to create a tuples like (False, 'xanadu') or (True, 'apple') for each element of the original list, which you can do with a lambda.