Python list comprehension - conditional for-loop - python-3.x

I've written some code like so which works fine and does what I want:
#!/usr/bin/env python3
myList = [[0], [1, 2], {'val': [3, 4, 5]}, [6]]
flat1 = []
for sublist in myList:
if type(sublist) is list:
for item in sublist:
flat1.append(item)
else:
for item in sublist['val']:
flat1.append(item)
print(flat1)
So it's a twist on the standard nested list flattening. The twist is that some nested items are not actually lists, but rather dictionaries with a 'val' (and we want the items in the 'val' list in those cases).
I'm trying to make it into a list comprehension. Here is what I've tried:
flat2 = [item for sublist in myList for item in sublist if type(sublist) is list else for item in sublist['val']]
print(flat2)
I get the following error with a caret pointing to the 'else'.
SyntaxError: invalid syntax
And yes what I've written into the list comprehension seems like nonsense and no doubt the compiler has no idea what I'm trying to do.
Is it possible to do what I'm trying to do with a list comprehension?

[item
for sublist in myList
for item in (sublist if type(sublist) is list else sublist['val'])
]

First off, I recommend against this approach. Why do you want to take what is already pretty readable and condense it so that it's unreadable?
flattened = [i for iterable in
(j if type(j) is list else j["val"] for j in myList)
for i in iterable]
Update after reading comment
One nice thing to do is to break out the list comprehension into two steps:
iterables = (j if type(j) is list else j["val"] for j in myList)
flattened = [i for iterable in iterables for i in iterable]
This is more readable -- and no less computationally efficient. Note the use of parentheses in iterables -- which makes it a generator that is lazily evaluated. That way, if myList is really long, you're still only reading through it once and you don't have to wait to read all the way through before proceeding.

Related

Python 3 list comprehension in list of lists to convert types

Consider the following list of lists:
list1 = [['1.1', '1.2', '1.3'], ['2.1', '2.2', '2.3'], ...]
To comprehend a list of strings to convert them to floats one could use
list1[0] = [float(i) for i in list1[0]]
But my attempt to comprehend a list of lists of floats didn't quite work:
list1 = [[float(j) for j in list1[i]] for i in list1]
due to
TypeError: list indices must be integers or slices, not list
Is there a way to do this sort of list comprehension without using loops explicitly?
[[float(j) for j in i] for i in list1]
shall do it

Simple way to remove duplicate item in a list [duplicate]

This question already has answers here:
How do I remove duplicates from a list, while preserving order?
(30 answers)
Closed 4 years ago.
the program says "TypeError: 'int' object is not iterable"
list=[3,3,2]
print(list)
k=0
for i in list:
for l in list:
if(l>i):
k=l
for j in k:
if(i==j):
del list[i]
print(list)
An easy way to do this is with np.unique.
l=[3,3,2]
print(np.unique(l))
Hope that helps!
Without using any numpy the easiest way I can think of is to start with a new list and then loop through the old list and append the values to the new list that are new. You can cheaply keep track of what has already been used with a set.
def delete_duplicates(old_list):
used = set()
new_list= []
for i in old_list:
if i not in used:
used.add(i)
new_list.append(i)
return new_list
Also, a couple tips on your code. You are getting a TypeError from the for j in k line, it should be for j in range(k). k is just an integer so you can't iterate over it, but range(k) creates an iterable that will do what you want.
Just build another list
>>> list1=[3,2,3]
>>> list2=[]
>>> for i in list1:
... if i in list2:
... pass
... else:
... list2.append(i)
...
>>> list2
[3, 2]
You can always add list1 = list2 at the end if you prefer.
You can use set()
t = [3, 3, 2]
print(t) # prints [3, 3, 2]
t = list(set(t))
print(t) # prints [2, 3]
To remove a duplicate item in a list and get list with unique element, you can always use set() like below:
example:
>>>list1 = [1,1,2,2,3,3,3]
>>>new_unique_list = list(set(list1))
>>> new_unique_list
>>>[1, 2, 3]
You have the following line in your code which produces the error:
for j in k:
k is an int and cannot be iterated over. You probably meant to write for j in list.
There are a couple good answers already. If you really want to write the code yourself however, I'd recommend functional style instead of working in place (i.e. modifying the original array). For example like the following function which is basically a port of Haskell's Data.List.nub.
def nub(list):
'''
Remove duplicate elements from a list.
Singleton lists and empty lists cannot contain duplicates and are therefore returned immediately.
For lists with length gte to two split into head and tail, filter the head from the tail list and then recurse on the filtered list.
'''
if len(list) <= 1: return list
else:
head, *tail = list
return [head] + nub([i for i in tail if i != head])
This is—in my opinion—easier to read and saves you the trouble associated with multiple iteration indexes (since you create a new list).

Recursion happens too many times and list is not iterable

I'm trying to make a secret santa programm. The input is in form of the list of names of people g. ["John", "Bob", "Alice"] and the list of emials ["John#gmail.com", "Bob#gmail.com", "Alice#outlook.com"]. I need to generate pairs of email adress and a random name which doesn't belong to the said email adress. For this I have written the function compare.
def compare(list_of_names, list_of_emails):
zipped_lists = zip(list_of_emails, list_of_names)
random.shuffle(list_of_emails)
zipped_shuffled_lists = zip(list_of_emails, list_of_names)
for pair in zipped_lists:
for shuffle_pair in zipped_shuffled_lists:
if shuffle_pair == pair:
return compare(list_of_names, list_of_emails)
return zipped_shuffled_lists
But instead of shuffling like it should it just creates a recursion. i still can't find out why. After a finite amount of time it should create two different lists that work. Also the shuffled_list_of_emails is not iterable, why?
EDIT:changed the code with shuffle because it works in place
zip is lazy!
I'm not sure why, but I'm too excited about this right now, so the answer might be a bit messy. Feel free to ask for clarification)
Let's step through your code:
def compare(list_of_names, list_of_emails):
# the `zip` object doesn't actually iterate over any of its arguments until you attempt to iterate over `zipped_lists`
zipped_lists = zip(list_of_emails, list_of_names)
# modify this IN-PLACE; but the `zip` object above has a pointer to this SAME list
random.shuffle(list_of_emails)
# since the very first `zip` object has `list_of_emails` as its argument, AND SO DOES THE ONE BELOW, they both point to the very same, SHUFFLED (!) list
zipped_shuffled_lists = zip(list_of_emails, list_of_names)
# now you're iterating over identical `zip` objects
for pair in zipped_lists:
for shuffle_pair in zipped_shuffled_lists:
# obviously, this is always true
if shuffle_pair == pair:
# say "hello" to infinite recursion, then!
return compare(list_of_names, list_of_emails)
return zipped_shuffled_lists
Let's recreate this in the Python interpreter!
>>> List = list(range(5))
>>> List
[0, 1, 2, 3, 4]
>>> zipped_1 = zip(List, range(5))
>>> import random
>>> random.shuffle(List)
>>> zipped_2 = zip(List, range(5))
>>> print(List)
[4, 2, 3, 0, 1]
>>> zipped_1, zipped_2 = list(zipped_1), list(zipped_2)
>>> zipped_1 == zipped_2
True
You see, two different zip objects applied to the same list at different times (before and after that list is modified in-place) produce the exact same result! Because zip doesn't do the zipping once you do zip(a, b), it will produce the zipped... uh, stuff... on-the-fly, while you're iterating over it!
So, to fix the issue, do not shuffle the original list, shuffle its copy:
list_of_emails_copy = list_of_emails.copy()
random.shuffle(list_of_emails_copy)
zipped_shuffled_lists = zip(list_of_emails_copy, list_of_names)
There's correct answer from #ForceBru already. But a will contribute a little.
You should avoid zip's lazy evaluation and unfold zips with, for example, list:
def compare(list_of_names, list_of_emails):
zipped_lists = list(zip(list_of_emails, list_of_names)) # eager evaluation instead of lazy
random.shuffle(list_of_emails) # shuffle lists
zipped_shuffled_lists = list(zip(list_of_emails, list_of_names)) # eager again
for pair in zipped_lists:
for shuffle_pair in zipped_shuffled_lists:
if shuffle_pair == pair:
return compare(list_of_names, list_of_emails)
return zipped_shuffled_lists
But I guess you need no recursion and can achieve your task easier:
def compare(list_of_names, list_of_emails):
zipped_lists = list(zip(list_of_emails, list_of_names))
random.shuffle(zipped_lists) # shuffle list of emails and names
result = []
shuffled_emails = [i[0] for i in zipped_lists]
for i, _ in enumerate(shuffled_emails):
result.append(zipped_lists[i-1][1]) # shift email relatively one position to the right
return list(zip(result, shuffled_emails))
This code links an name with an email of a previous name, which is randomly selected, and it guaranteed does not match.
There's no recursion, works fine for lists with two or more elements.

Merge items from separate lists into nested lists

Hello I am trying to merge two lists sequentially into sub lists. I wonder if this is possible without list comprehensions or a lambda operation as I'm still learning how to work with those approaches. Thank you
a = [0,1,2,3]
b = [4,5,6,7]
#desired output
c = [0,4],[1,5],[2,6],[3,7]
An approach that doesn't involve lambdas or list comprehensions (not sure what the issue is with list-comps) would be with map:
c = list(map(list, zip(a, b)))
This first zips the lists together, then creates a list instance for every tuple generated from zip with map and wraps it all up in list in order for map to yield all it's contents:
print(c)
[[0, 4], [1, 5], [2, 6], [3, 7]]
This, at least in my view, is less understandable than the equivalent comprehension John supplied in a comment.
Here’s a solution suitable for beginners!
c = []
a = [0,1,2,3]
b = [4,5,6,7]
for i in range(min(len(a), len(b))):
c.append([a[i], b[i]]) # writing [a[i], b[i]] creates a new list
print(c)

how do you check if element is already in the list through comprehension?

How do you check whether an element is already in the list when I am doing it in comprehension?
For example say in following comprehension I want to restrict duplicate numbers Though I am not looking for unique number at all, I want to prevent via an if condition.
[x for x in [1,2,3,1,2,3]]
I am looking for something like
[x for x in [1,2,3,1,2,3] if not in self]
I think what you are looking for is set comprehension and conversion to list. It would do what you want without any odd syntax.
ans = list({x for x in [1,2,3,1,2,3])})
Actaully that can be also be simplified to
ans = list(set([1,2,3,1,2,3]))
but I think the first one might be better in performance.
You can't access the comprehension as you're creating it (as far as I know; someone please correct me if I'm wrong!), but in your case you can just use a set, which eliminates duplicates.
uniques = set([1, 2, 3, 1, 2, 3])
print(uniques) # >>> set([1, 2, 3])
If your list comprehension needs to be more complex, you can index the comprehension from the set, rather than the original list.
mylist = [1, 2, 3, 1, 2, 3]
print([x*x for x in set(mylist)]) # >>> [1, 4, 9]
If you really need direct access to the list during creation, you need to use an explicit loop rather than a comprehension.
x = [1,2,3,1,2,3]
y = [x[n] for n in range(len(x)) if x.index(x[n]) == n]
?
I think at this point, it's probably more readable to simply write it using a for-loop rather than a comprehension.

Resources