The condition skips 2 members of a list [duplicate] - python

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Modifying list while iterating
I have been given a task to make a code in python that will remove all members that occures more than once in a list and leave 1 copy of it.
Condition: it should be case-insesitive
So I've written down the following code:
string = raw_input()
list1 = string.split(",")
low_case_list = list1[:] #for case-insesitive
for i in range(len(low_case_list)):
low_case_list[i] = low_case_list[i].lower()
for member in low_case_list:
if(low_case_list.count(member) > 1):
del list1[low_case_list.index(member)]
del low_case_list[low_case_list.index(member)]
after the input I get this list: [a,b,c,d,A,B,C,D,a,b,c,d]
and after I do the operation on it: [B,D,a,b,c,d]
my question is, why it skips 'B' and 'D' when it removes the members?

Why not just convert your list into a set with all elements converted to lower-case, and then back to a list. You can use a generator for converting every element to lowercase.
You can do it like this: -
>>> l = ['a', 'b', 'c', 'A', 'B', 'C', 'a', 'b', 'c']
>>> new_list = list(set(elem.lower() for elem in l))
>>> new_list
['a', 'c', 'b']
Note that, order may be changed because, set does not maintain the order of it's elements.

You could try something like this instead:
input = raw_input().split(',')
unique = set([s.lower() for s in input])
result = list(unique)

Try this, should be simple.
Given your list li:
lowcase = [elem.lower() for elem in li]
output = []
for el in lowcase:
if el not in output: output.append(el)
return output # if necessary, otherwise a simple li = output
Or, in a faster and more elegant way, you could replace the whole for loop with:
[output.append(el) for el in lowcase if el not in output]
Your code should be buggy because you refer to the index of the element, but the list changes size during the loop, so indices change too.
EDIT: didn't think about sets, obviously they're the best solution here.

Related

Replace elements in a list of lists python

I have a list of lists as follows:
list=[]
*some code to append elements to list*
list=[['a','bob'],['a','bob'],['a','john']]
I want to go through this list and change all instances of 'bob to 'b' and leave others unchanged.
for x in list:
for a in x:
if "bob" in a:
a.replace("bob", 'b')
After printing out x it is still the same as list, but not as follows:
list=[['a','b'],['a','b'],['a','john']]
Why is the change not being reflected in list?
Because str.replace doesn't work in-place, it returns a copy. As immutable objects, you need to assign the strings to elements in your list of lists.
You can assign directly to your list of lists if you extract indexing integers via enumerate:
L = [['a','bob'],['a','bob'],['a','john']]
for i, x in enumerate(L):
for j, a in enumerate(x):
if 'bob' in a:
L[i][j] = a.replace('bob', 'b')
Result:
[['a', 'b'], ['a', 'b'], ['a', 'john']]
More Pythonic would be to use a list comprehension to create a new list. For example, if only the second of two values contains names which need checking:
L = [[i, j if j != 'bob' else 'b'] for i, j in L]
You can try using a dictionary object of python
import numpy as np
L = [['a','bob'],['a','bob'],['a','john']]
dic = {'bob':'b'} # you can specify more changes here
new_list = [dic.get(n, n) for n in np.concatenate(L)]
print(np.reshape(new_list,[-1,2]).tolist())
Result is
[['a', 'b'], ['a', 'b'], ['a', 'john']]
I'm going to use a simple example, but basically x is another variable and isn't linked to the list element. You have to change the list element directly in order to alter the list.
l=[1,2,3,4]
for x in l:
x=x+1
This doesn't change the list
l=[1,2,3,4]
for i,x in enumerate(l):
l[i]=x+1
this changes the list
I might be a little to the party, but a more Pythonic way of doing this is using a map and a list comprehension. It can operate on a list of the list with any number of values.
l = [['a','bob'],['a','bob'],['a','john']]
[list(map(lambda x: x if x != 'bob' else 'b', i)) for i in l]
it gives you the desired output
[['a', 'b'], ['a', 'b'], ['a', 'john']]
The main idea is that the inner loop is iterating through the inner loop and using the simple lambda function to perform the replacement.
I hope that this helps anyone else who is looking out for something similar.
This is the case because you are only changing the temporary variable a.
list = [1,2,3]
for i in list:
i+=1
list will still be [1,2,3]
you have to edit the string based on its index in the list

Manipulation of list - Extract integers to new list [duplicate]

This question already has answers here:
How to remove items from a list while iterating?
(25 answers)
Closed 5 years ago.
I have a list which consist of string and integers. I have to pop only integers and put that in a separate list. My code:
list1=['a','b','c','d','e','f','g','h',1,2,3]
list=[]
x=0
for i in list1:
if isinstance(i,int) :
list.append(i)
list1.pop(x)
x += 1
print(list1)
print(list)
Output of above code
['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 2]
[1, 3]
My question is: Why are all the integers not removed by my code? What is wrong with my code?
You iterate and manipulate the same list at the same time:
for i in list1: # iteration
if isinstance(i,int):
list.append(i)
list1.pop(x) # manipulate
x += 1
This usually does not work: the for loop works with a cursor. If you meanwhile removes an item, you thus will start skipping elements.
It is better to simply use a declarative and Pythonic approach, like for instance the following:
list_int = [i for i in list1 if isinstance(i,int)]
list1 = [i for i in list1 if not isinstance(i,int)]
Furthermore you should not name a variable list, since then you remove the reference to the list class.

Not able to delete string item with colon in python list

So I'm having the following problem while coding in python: I have a few string items in a list like so:
['X','Y','Z','A', 'B:C', 'D']
I want to delete everything past 'Z'. I use the following code to attempt this:
for item in lines:
if ((item == "A")):
lines.remove(item)
if (item == "B:C"):
lines.remove(item)
if (item == "D"):
lines.remove(item)
A and D get removed perfectly. However, B:C is not removed and stays in the list...
Mind you, A, D, B:C etc represent strings, not characters (e.g. A could be Transaction failed! and B:C can represent WRITE failure: cannot be done!)
How can this be solved?
Modifying a list while iterating over it is usually a bad thing. Some of the elements get skipped when you remove the current element. You may be able to fix it by iterating over reversed(lines), but it is better to create a new list that doesn't have the elements that you want to drop:
to_remove = {'A', 'B:C', 'D'}
new_lines = [line for line in lines if line not in to_remove]
Or, if you want to modify in-place:
to_remove = {'A', 'B:C', 'D'}
lines[:] = [line for line in lines if line not in to_remove]
You may use the .index() method to find the index of a specific element inside a list.
Then after finding the z_index, you may create another list by slicing the first one.
Here's an example:
l1 = ['X','Y','Z','A', 'B:C', 'D']
#finding index of element 'Z'
z_index = l1.index('Z')
#slicing list from 0 until z_index
l2 = l1[:z_index]
print l2
Output:
['X', 'Y']
Generally, it is not a good idea to delete elements from a list you are iterating. In your case, you may consider creating a new list with the result you want:
l = ['X','Y','Z','A', 'B:C', 'D']
clean_l = [i for i in l if i not in ('A', 'B:C', 'D')]
Which is a good option if you know which elements you want to delete. However, if you know that you don't want anything after 'Z' regardless of their value, then just slice the list:
clean_l = l[:l.index('Z') + 1]
Firstly you would want to find the position of 'Z' by using the index() method.
x = ['X','Y','Z','A', 'B:C', 'D']
position = x.index('Z')
Then to delete everything after z i would do this:
del x[postion+1:]
You have to add one to the position otherwise it will delete 'Z' also

Remove list element without mutation

Assume you have a list
>>> m = ['a','b','c']
I'd like to make a new list n that has everything except for a given item in m (for example the item 'a'). However, when I use
>>> m.remove('a')
>>> m
m = ['b', 'c']
the original list is mutated (the value 'a' is removed from the original list). Is there a way to get a new list sans-'a' without mutating the original? So I mean that m should still be [ 'a', 'b', 'c' ], and I will get a new list, which has to be [ 'b', 'c' ].
I assume you mean that you want to create a new list without a given element, instead of changing the original list. One way is to use a list comprehension:
m = ['a', 'b', 'c']
n = [x for x in m if x != 'a']
n is now a copy of m, but without the 'a' element.
Another way would of course be to copy the list first
m = ['a', 'b', 'c']
n = m[:]
n.remove('a')
If removing a value by index, it is even simpler
n = m[:index] + m[index+1:]
There is a simple way to do that using built-in function :filter .
Here is ax example:
a = [1, 2, 3, 4]
b = filter(lambda x: x != 3, a)
If the order is unimportant, you can use set (besides, the removal seems to be fast in sets):
list(set(m) - set(['a']))
This will remove duplicate elements from your original list though
We can do it via built-in copy() function for list;
However, should assign a new name for the copy;
m = ['a','b','c']
m_copy=m.copy()
m_copy.remove('a')
print (m)
['a', 'b', 'c']
print(m_copy)
['b', 'c']
You can create a new list without the offending element with a list-comprehension. This will preserve the value of the original list.
l = ['a', 'b', 'c']
[s for s in l if s != 'a']
Another approach to list comprehension is numpy:
>>> import numpy
>>> a = [1, 2, 3, 4]
>>> list(numpy.remove(a, a.index(3)))
[1, 2, 4]
We can do it without using in built remove function and also without creating new list variable
Code:
# List m
m = ['a', 'b', 'c']
# Updated list m, without creating new list variable
m = [x for x in m if x != a]
print(m)
output
>>> ['b', 'c']
The question is useful as I sometimes have a list that I use throughout my given script but I need to at a certain step to apply a logic on a subset of the list elements. In that case I found it useful to use the same list but only exclude the needed element for that individual step, without the need to create a totally new list with a different name. For this you can use either:
list comprehension: say you have l=['a','b','c'] to exclude b, you can have [x for x in l if x!='b']
set [only if order is unimortant]: list(set(l) - set(['b'])), pay attention here that you pass 'b' as list ['b']

Python wont remove items from list

filtered_list = ['PerezHilton', 'tomCruise', 'q', 'p']
#BIO[user]['follows'] is just a list of strings say ['a', 'b', 'katieh']
#specs is also a string say eg. 'katieh'
for user in filtered_list:
if specs not in BIO[user]['follows']:
filtered_list.remove(user)
The above code for some reson gives this error "ValueError: list.remove(x): x not in list" but clearly 'p' is in the list so why is it not detecting 'p' but it is finding 'q'??
Im soo stumped but any help is appreciated, thanks
** SORRY i FIXED IT NOW *
The list comprehension that does this correctly in one line is at the bottom of the post. Here's some insight into the problem first.
Don't do things like:
for item in list_:
list_.remove(item)
because bad and confusing things happen.
>>> list_ = range(10)
>>> for item in list_:
... list_.remove(item)
...
>>> list_
[1, 3, 5, 7, 9]
Every time you remove an item, you change the indexes for the rest of the items which messes up the loop. One good way to remove items from a list while you're traversing it is to do it by index and work backwards so that removals don't affect the rest of the iterations. This is better because if you remove the 9'th element, then the 8'th element is still the 8'th element but the 10'th element becomes the 9'th element. If you've already dealt with that element, then you don't care what its index is.
>>> list_ = range(10)
>>> for i in xrange(len(list_) - 1, -1, -1):
... del list_[i]
...
>>> list_
[]
Or with a while loop:
i = len(list_)
while i:
i -= 1
del list_[i]
So in your case, the code would look something like
users[:] = [user for user in users if specs in BIO[user]['follows']]
because this is a filtering job and those are best done with list comprehensions. The point of the [:] is that it assigns to a slice of the list instead of clobbering the reference to the list. This means that every other reference to the list will be updated. It's essentially in-place, except that a copy is made before overwriting the original list. For the sake of completeness, here's how to do it with a while loop.
i = len(users)
while i:
i -= 1
if specs not in BIO[users[i]]['follows']:
del users[i]
You could do this if you wanted it done in place. No copy of the list is made here.
Why are you iterating?
>>> un = ['PerezHilton', 'tomCruise', 'q', 'p']
>>> un.remove('p')
>>> un
['PerezHilton', 'tomCruise', 'q']

Categories