Need Clarification - Python For loops using a list [duplicate] - python

This question already has answers here:
Loop "Forgets" to Remove Some Items [duplicate]
(10 answers)
Closed 8 years ago.
def check(temp):
for i in temp:
if type(i) == str:
temp.remove(i)
temp = ['a', 'b']
print(temp) ==> Output: ['a','b']
check(temp)
print(temp) ==> Output: ['b']
When run with
temp = [ 'a', 1 ], Output is [1]
temp = [ 1, 'a', 'b', 'c', 2 ], Output is [ 1, 'b', 2 ]
Could someone care to explain how the result is evaluated.. Thnx

You are modifying the list while iterating it. It will skip elements because the list changes during the iteration. Removing items with list.remove() will also remove the first occurence of that element, so there might be some unexpected results.
The canonical way of removing elements from a list is to construct a new list, like so:
>>> def check(temp):
... return list(x for x in temp if not isinstance(x, str))
Or you could return a regular list comprehension:
>>> def check(temp):
... return [x for x in temp if not isinstance(x, str)]
You should generally test for types with isinstance() instead of type(). type has no idea about inheritance for example.
Examples:
>>> check(['a', 'b', 1])
[1]
>>> check([ 1, 'a', 'b', 'c', 2 ])
[1, 2]
>>> check(['a', 'b', 'c', 'd'])
[]

You can use ,
def check(temp):
return [i for i in temp if type(i)!=str]
temp = [ 1, 'a', 'b', 'c', 2 ]
print check(temp)
Output:
[1, 2]
OR
def check(temp):
return [i for i in temp if not isinstance(i, str)]
temp = [ 1, 'a', 'b', 'c', 2 ,"e",4,5,6,7]
print check(temp)
output:
[1, 2, 4, 5, 6, 7]

>>> text = ['a', 'b', 1, {}]
>>> filter(lambda x: type(x) == str, text)
['a', 'b']
function will be like:
>>> def check(temp):
... return list(filter(lambda x: type(x) == str, temp))
...
>>> check(text)
['a', 'b']

Related

Check if a value is the first occurence of a list and mark as 1 in Python

I have a list and I want to mark the first occurrence of each element as 1, and other occurrences as 0s. How should I do that?
Inital Input:
my_lst = ['a', 'b', 'c', 'c', 'a', 'd']
Expected outputs:
[1,1,1,0,0,1]
You can use itertools.count and collections.defaultdict for the task:
from itertools import count
from collections import defaultdict
my_lst = ['a', 'b', 'c', 'c', 'a', 'd']
d = defaultdict(count)
out = [int(next(d[v])==0) for v in my_lst]
print(out)
Prints:
[1, 1, 1, 0, 0, 1]
If you want a barebones python solution, this monstrosity would work:
[*map(int, map(lambda x, y: x == my_lst.index(y), *zip(*enumerate(my_lst))))]
Out[30]: [1, 1, 1, 0, 0, 1]
For all items in my_lst, it returns 1 if its index is the index of the first occurrence.
you will need to keep track of which items you saw already so here's an example code:
seen_chars = set()
output = []
for c in my_lst:
if c not in seen_chars:
seen_chars.add(c)
output.append(1)
else:
output.append(0)
Hope that helped

create simple list within a while loop python [duplicate]

This question already has answers here:
Python list doesn't reflect variable change
(6 answers)
Closed 2 years ago.
I want to create a simple list within a while loop in python
I'm using this code
def get_list(input):
create_cell = []
for line in input:
create_cell.append(line)
return create_cell
x=0
c = [x,'a','b']
while x < 5:
new_row = get_list(c)
print (new_row)
x = x + 1
It gives the following output
[0, 'a', 'b']
[0, 'a', 'b']
[0, 'a', 'b']
[0, 'a', 'b']
[0, 'a', 'b']
The output what I want is:
[0, 'a', 'b']
[1, 'a', 'b']
[2, 'a', 'b']
[3, 'a', 'b']
[4, 'a', 'b']
Assigning to x doesn't change c. You need to update that as well:
while x < 5:
new_row = get_list(c)
print (new_row)
x = x + 1
c[0] = x

selecting elements from list of list based on length and intersection

l1 = [['a', 'b', 'c'],
['a', 'd', 'c'],
['a', 'e'],
['a', 'd', 'c'],
['a', 'f', 'c'],
['a', 'e'],
['p', 'q', 'r']]
l2 = [1, 1, 1, 2, 0, 0, 0]
I have two lists as represented above. l1 is a list of lists and l2 is another list with some kind of score.
Problem: For all the lists in l1 with a score of 0 (from l2), find those lists which are either entirely different or have the least length.
For example: if i have the lists [1, 2, 3], [2, 3], [5, 7] all with score 0, i will choose [5, 7] because these elements are not present in any other lists and [2, 3] since it has an intersection with [1, 2, 3] but is of a smaller length.
How I do this now:
l = [x for x, y in zip(l1, l2) if y == 0]
lx = [(x, y) for x, y in zip(l1, l2) if y > 0]
c = list(itertools.combinations(l, 2))
un_usable = []
usable = []
for i, j in c:
intersection = len(set(i).intersection(set(j)))
if intersection > 0:
if len(i) < len(j):
usable.append(i)
un_usable.append(j)
else:
usable.append(j)
un_usable.append(i)
for i, j in c:
intersection = len(set(i).intersection(set(j)))
if intersection == 0:
if i not in un_usable and i not in usable:
usable.append(i)
if j not in un_usable and j not in usable:
usable.append(j)
final = lx + [(x, 0) for x in usable]
and final gives me:
[(['a', 'b', 'c'], 1),
(['a', 'd', 'c'], 1),
(['a', 'e'], 1),
(['a', 'd', 'c'], 2),
(['a', 'e'], 0),
(['p', 'q', 'r'], 0)]
which is the required result.
EDIT: to handle equal lengths:
l1 = [['a', 'b', 'c'],
['a', 'd', 'c'],
['a', 'e'],
['a', 'd', 'c'],
['a', 'f', 'c'],
['a', 'e'],
['p', 'q', 'r'],
['a', 'k']]
l2 = [1, 1, 1, 2, 0, 0, 0, 0]
l = [x for x, y in zip(l1, l2) if y == 0]
lx = [(x, y) for x, y in zip(l1, l2) if y > 0]
c = list(itertools.combinations(l, 2))
un_usable = []
usable = []
for i, j in c:
intersection = len(set(i).intersection(set(j)))
if intersection > 0:
if len(i) < len(j):
usable.append(i)
un_usable.append(j)
elif len(i) == len(j):
usable.append(i)
usable.append(j)
else:
usable.append(j)
un_usable.append(i)
usable = [list(x) for x in set(tuple(x) for x in usable)]
un_usable = [list(x) for x in set(tuple(x) for x in un_usable)]
for i, j in c:
intersection = len(set(i).intersection(set(j)))
if intersection == 0:
if i not in un_usable and i not in usable:
usable.append(i)
if j not in un_usable and j not in usable:
usable.append(j)
final = lx + [(x, 0) for x in usable]
Is there a better, faster & pythonic way of achieving the same?
Assuming I understood everything correctly, here is an O(N) two-pass algorithm.
Steps:
Select lists with zero score.
For each element of each zero-score list, find the length of the shortest zero-score list in which the element occurs. Let's call this the length score of the element.
For each list, find the minimum of length scores of all elements of the list. If the result is less than the length of the list, the list is discarded.
def select_lsts(lsts, scores):
# pick out zero score lists
z_lsts = [lst for lst, score in zip(lsts, scores) if score == 0]
# keep track of the shortest length of any list in which an element occurs
len_shortest = dict()
for lst in z_lsts:
ln = len(lst)
for c in lst:
len_shortest[c] = min(ln, len_shortest.get(c, float('inf')))
# check if the list is of minimum length for each of its chars
for lst in z_lsts:
len_lst = len(lst)
if any(len_shortest[c] < len_lst for c in lst):
continue
yield lst

Sorting a list: numbers in ascending, letters in descending

This question is actually adapted from one previously asked by Mat.S (image). Although it was deleted, I thought it was a good question, so I am reposting it with clearer requirements and my own solution.
Given a list of letters and numbers, say
['a', 2, 'b', 1, 'c', 3]
The requirement is to sort the numbers in ascending and letters in descending, without altering the relative position of letters and numbers. By this I mean if the unsorted list is:
[L, D, L, L, D] # L -> letter; # D -> digit
Then, the sorted list must also be
[L, D, L, L, D]
The letters and digits do not necessarily alternate in a regular pattern - they can appear in any arbitrary order
After sorting - numbers are ascending, letters are descending.
So for the example above the output is
['c', 1, 'b', 2, 'a', 3]
Another example:
In[]: [5, 'a', 'x', 3, 6, 'b']
Out[]: [3, 'x', 'b', 5, 6, 'a']
What would be a good way to do this?
Here is an optimized approach using defaultdict() and bisect():
In [14]: lst = [5, 'a', 'x', 3, 6, 'b']
In [15]: from collections import defaultdict
In [16]: import bisect
In [17]: def use_dict_with_bisect(lst):
d = defaultdict(list)
for i in lst:
bisect.insort(d[type(i)], i)
# since bisect doesn't accept key we need to reverse the sorted integers
d[int].sort(reverse=True)
return [d[type(i)].pop() for i in lst]
.....:
Demo :
In [18]: lst
Out[18]: [5, 'a', 'x', 3, 6, 'b']
In [19]: use_dict_with_bisect(lst)
Out[19]: [3, 'x', 'b', 5, 6, 'a']
In case you're dealing with larger lists it's more optimized to drop using bisect which has a complexity about O(n2)and just use python built-in sort() function with Nlog(n) complexity.
In [26]: def use_dict(lst):
d = defaultdict(list)
for i in lst:
d[type(i)].append(i)
d[int].sort(reverse=True); d[str].sort()
return [d[type(i)].pop() for i in lst]
Benchmark with other answers that shows the latest approach using dict and built-in sort is almost 1ms faster than the other approaches:
In [29]: def use_sorted1(lst):
letters = sorted(let for let in lst if isinstance(let,str))
numbers = sorted((num for num in lst if not isinstance(num,str)), reverse = True)
return [letters.pop() if isinstance(elt,str) else numbers.pop() for elt in lst]
.....:
In [31]: def use_sorted2(lst):
f1 = iter(sorted(filter(lambda x: isinstance(x, str), lst), reverse=True))
f2 = iter(sorted(filter(lambda x: not isinstance(x, str), lst)))
return [next(f1) if isinstance(x, str) else next(f2) for x in lst]
.....:
In [32]: %timeit use_sorted1(lst * 1000)
100 loops, best of 3: 3.05 ms per loop
In [33]: %timeit use_sorted2(lst * 1000)
100 loops, best of 3: 3.63 ms per loop
In [34]: %timeit use_dict(lst * 1000) # <-- WINNER
100 loops, best of 3: 2.15 ms per loop
Here is a benchmark that shows how using bisect can slow down the process for long lists:
In [37]: %timeit use_dict_with_bisect(lst * 1000)
100 loops, best of 3: 4.46 ms per loop
Look ma, no iter:
lst = ['a', 2, 'b', 1, 'c', 3]
letters = sorted(let for let in lst if isinstance(let,str))
numbers = sorted((num for num in lst if not isinstance(num,str)), reverse = True)
lst = [(letters if isinstance(elt,str) else numbers).pop()for elt in lst]
I'm looking for a way to turn this into a (horrible) one-liner, but no luck so far - suggestions welcome!
I took a crack at this by creating two generators and then taking from them conditionally:
f1 = iter(sorted(filter(lambda x: isinstance(x, str), lst), reverse=True))
f2 = iter(sorted(filter(lambda x: not isinstance(x, str), lst)))
[next(f1) if isinstance(x, str) else next(f2) for x in lst]
# ['c', 1, 'b', 2, 'a', 3]
In one line:
list(map(list, sorted(zip(lst[::2], lst[1::2]), key=lambda x: x[1] if hasattr(x[0], '__iter__') else x[0])))
Totally not recommended, but I had fun coding it.
from collections import deque
from operator import itemgetter
lst = ['a', 2, 'b', 1, 'c', 3]
is_str = [isinstance(e, str) for e in lst]
two_heads = deque(map(itemgetter(1), sorted(zip(is_str, lst))))
[two_heads.pop() if a_str else two_heads.popleft() for a_str in is_str]
Why don't we just sort list in ascending order, but ensure that numbers come before letters:
[D, D, L, L, L] # L -> letter; # D -> digit
We can achieve that in such a way:
>>> lst = [5, 'a', 'x', 3, 6, 'b']
>>> sorted(lst, key=lambda el: (isinstance(el, str), el))
[3, 5, 6, 'a', 'b', 'x']
Then we look over original array from left to right and if we encounter number, we pick element from the beginning of sorted array, otherwise from the end. The full verbose solution will then be:
def one_sort(lst):
s = sorted(lst, key=lambda el: (isinstance(el, str), el))
res = []
i, j = 0, len(s)
for el in lst:
if isinstance(el, str):
j -= 1
res.append(s[j])
else:
res.append(s[i])
i += 1
return res
lst = [5, 'a', 'x', 3, 6, 'b']
print(one_sort(lst)) # [3, 'x', 'b', 5, 6, 'a']
Much shorter but cryptic solution will be:
def one_sort_cryptic(lst):
s = sorted(lst, key=lambda el: (isinstance(el, str), el))
return [s.pop(-isinstance(el, str)) for el in lst]
lst = [5, 'a', 'x', 3, 6, 'b']
print(one_sort_cryptic(lst)) # [3, 'x', 'b', 5, 6, 'a']

How do I delete the Nth list item from a list of lists (column delete)?

How do I delete a "column" from a list of lists?
Given:
L = [
["a","b","C","d"],
[ 1, 2, 3, 4 ],
["w","x","y","z"]
]
I would like to delete "column" 2 to get:
L = [
["a","b","d"],
[ 1, 2, 4 ],
["w","x","z"]
]
Is there a slice or del method that will do that? Something like:
del L[:][2]
You could loop.
for x in L:
del x[2]
If you're dealing with a lot of data, you can use a library that support sophisticated slicing like that. However, a simple list of lists doesn't slice.
just iterate through that list and delete the index which you want to delete.
for example
for sublist in list:
del sublist[index]
You can do it with a list comprehension:
>>> removed = [ l.pop(2) for l in L ]
>>> print L
[['a', 'b', 'd'], [1, 2, 4], ['w', 'x', 'z']]
>>> print removed
['d', 4, 'z']
It loops the list and pops every element in position 2.
You have got list of elements removed and the main list without these elements.
A slightly twisted version:
index = 2 # Delete column 2
[(x[0:index] + x[index+1:]) for x in L]
[(x[0], x[1], x[3]) for x in L]
It works fine.
This is a very easy way to remove whatever column you want.
L = [
["a","b","C","d"],
[ 1, 2, 3, 4 ],
["w","x","y","z"]
]
temp = [[x[0],x[1],x[3]] for x in L] #x[column that you do not want to remove]
print temp
O/P->[['a', 'b', 'd'], [1, 2, 4], ['w', 'x', 'z']]
L = [['a', 'b', 'C', 'd'], [1, 2, 3, 4], ['w', 'x', 'y', 'z']]
_ = [i.remove(i[2]) for i in L]
If you don't mind on creating new list then you can try the following:
filter_col = lambda lVals, iCol: [[x for i,x in enumerate(row) if i!=iCol] for row in lVals]
filter_out(L, 2)
An alternative to pop():
[x.__delitem__(n) for x in L]
Here n is the index of the elements to be deleted.

Categories