Python list comprehension ignore None results? - python-3.x

I have the following toy example function and list comprehension:
def foo(lst):
if lst:
return lst[0] + lst[1]
[foo(l) for l in [[], [1,2], [1,4]]]
The result is:
[None, 3, 4]
How can I avoid the Nones, I would like to avoid calling if foo(l) is not None inside the list comp.
Please advise.

If you want to avoid calling the function more than once, you can make a generator that yields based on the result of the function. It's a little more code, but avoids making a list with a bunch of None values which have to be filtered later, and also avoids calling the function twice in the list comprehension:
def expensive_function(lst):
# sometimes returns None...hard to know when without calling
if lst:
return lst[0] + lst[1]
def gen_results(l):
for a in l:
res = expensive_function(a)
if res:
yield res
inp = [[], [1,2], [1,4]]
list(gen_results(inp))
# [3, 5]
Also, since generators are lazy, you don't need to make a list if you don't need a list.

Related

How do I convert a list of tuples and number to a tuple

Hi Stackoverflow geeks,
I have this list:
[(('Male', 'White'), 6488)]
& I want to convert it to a tuple like this:
('Male', 'White', 6488)
I appreciate if you help me with the code.
This is a generalised solution to flatten lists that contain a possible combination of iterables and non-iterables of various types:
import functools
from typing import Any, Iterable, List
def ext(x: List, y: Any) -> List:
"""
Returns x, extended or appended to include the individual constituents of y.
x is always a list. y may or may not be.
"""
# Because strings are iterable, we don't want them to be broken down
# into individual characters - so we check that y is iterable but not a
# string.
if isinstance(y, Iterable) and not isinstance(y, str):
x.extend(y)
else:
x.append(y)
return x
Use the above function with functools.reduce to work your way through the original iterable, accumulating its contents into a single list.
You provide [] as an initial value for x passed into ext, to guarantee that it is always a list for the purposes of ext. Once you have that list, convert it into a tuple to give you your desired result.
Trying this on your original problem - arr1 below, you need to run this approach twice, because your original iterable only contains one iterable of its own:
arr1 = [('Male', 'White'), 6488]
result1 = tuple(functools.reduce(ext, arr1, []))
print (result1)
# Result -> (('Male', 'White'), 6488)
result1 = tuple(functools.reduce(ext, result1, []))
print(result1)
# Result -> ('Male', 'White', 6488)
Trying it on a more complex object:
arr2 = [('foo'), 'bar', ['bleep', 'bloop'], {'a': 1, 'b': 2}]
result2 = tuple(functools.reduce(ext, arr2, []))
print(result2)
# Result -> ('foo', 'bar', 'bleep', 'bloop', 'a', 'b')
Note that with dictionaries, you only get the keys back. If you want key/value pairs, you'll need to add a specific guard for this, like so:
def ext2(x: List, y: Any) -> List:
"""
Reworking ext to deal differently with dictionaries in the original iterable.
"""
if isinstance(y, Iterable) and not isinstance(y, str):
if isinstance(y, dict):
x.extend(y.items())
else:
x.extend(y)
else:
x.append(y)
return x
Trying it again on the last example::
arr2 = [('foo'), 'bar', ['bleep', 'bloop'], {'a': 1, 'b': 2}]
result3 = tuple(functools.reduce(ext2, arr2, []))
print(result3)
# Result -> ('foo', 'bar', 'bleep', 'bloop', ('a', 1), ('b', 2))

List comprehension flat list?

I have the following toy function:
def foo(a):
return [a+5]
And I am running the following code:
my_lst = [foo(x) for x in range(10) if x%2 == 0]
Getting:
[[5], [7], [9], [11], [13]]
But need:
[5,7,9,11,13]
But I want to get a plain list, not a list of lists.
How can I do it without itertools.chain.from_iterable(my_lst) but with list comprehension?
What is the best practice? itertools or list comprehension in this case?
Please advice.
I have tried:
[j[0] for j in [foo(x) for x in range(10) if x%2 == 0]]
Should I do it like this or there is a better way?
With list comprehension, it's done using two for loops:
my_lst = [subx for x in range(10) if x%2 == 0 for subx in foo(x)]
Also if you have a list of lists of one elements, you can "transpose" after the fact the list using zip:
bad_lst = [foo(x) for x in range(10) if x%2 == 0]
[my_lst] = zip(*bad_lst)
Or you can "transpose" using a list comprehension combined with list unpacking as well if you truly want a list as output and not a tuple:
bad_lst = [foo(x) for x in range(10) if x%2 == 0]
my_lst = [x for [x] in bad_lst]
alternatively to Laernes answer, you can also use itertools as :
list(itertools.chain(*[foo(x) for x in range(10) if x%2 == 0]))
or
list(itertools.chain.from_iterable([foo(x) for x in range(10) if x%2 == 0]))
More options here:
Your function should return a number, not a list:
def foo(a):
return a+5

Why does Python list comprehension print a list of "None"s in the end?

From Python 3.6 prompt:
>>> [print(i) for i in range(3)]
0
1
2
[None, None, None]
A list comprehension creates a list containing the results of evaluating the expression for each iteration. As well as the "side-effect" of printing out whatever you give it, the print function returns None, so that is what gets stored in the list.
Since you're in an interactive console, the return value is printed out at the end. If you ran this as a script, the output wouldn't include [None, None, None].
If you're wanting to loop over 0, 1, 2 and print them out, I'm afraid you have to do it as a proper for-loop:
for i in range(3)
print(i)
Though I fully empathise with the desire to use the neat one-line syntax of a list comprehension! 😞
As other people have commented, you can achieve the same effect by constructing the list (using a list comprehension) and then printing that out:
print(*[i for i in range(3)], sep="\n")
You need the sep argument if you want them on a new line each; without it they'll be on one line, each separated by a space.
When you put the print statement there, it prints each of the value, then prints the list which has no values in it.
>>> this = print([(i) for i in range(3)])
[0, 1, 2]
>>> print(this)
None
>>>
This should show you the problem. What you want to use is
>>> [(i) for i in range(3)]
[0, 1, 2]

Split/partition list based on invariant/hash?

I have a list [a1,21,...] and would like to split it based on the value of a function f(a).
For example if the input is the list [0,1,2,3,4] and the function def f(x): return x % 3,
I would like to return a list [0,3], [1,4], [2], since the first group all takes values 0 under f, the 2nd group take value 1, etc...
Something like this works:
return [[x for x in lst if f(x) == val] for val in set(map(f,lst))],
But it does not seem optimal (nor pythonic) since the inner loop unnecessarily scans the entire list and computes same f values of the elements several times.
I'm looking for a solution that would compute the value of f ideally once for every element...
If you're not irrationally ;-) set on a one-liner, it's straightforward:
from collections import defaultdict
lst = [0,1,2,3,4]
f = lambda x: x % 3
d = defaultdict(list)
for x in lst:
d[f(x)].append(x)
print(list(d.values()))
displays what you want. f() is executed len(lst) times, which can't be beat
EDIT: or, if you must:
from itertools import groupby
print([[pair[1] for pair in grp]
for ignore, grp in
groupby(sorted((f(x), x) for x in lst),
key=lambda pair: pair[0])])
That doesn't require that f() produce values usable as dict keys, but incurs the extra expense of a sort, and is close to incomprehensible. Clarity is much more Pythonic than striving for one-liners.
#Tim Peters is right, and here is a mentioned setdefault and another itertool.groupby option.
Given
import itertools as it
iterable = range(5)
keyfunc = lambda x: x % 3
Code
setdefault
d = {}
for x in iterable:
d.setdefault(keyfunc(x), []).append(x)
list(d.values())
groupby
[list(g) for _, g in it.groupby(sorted(iterable, key=keyfunc), key=keyfunc)]
See also more on itertools.groupby

If I have duplicates in a list with brackets, what should I do

Suppose I have the following list:
m=[1,2,[1],1,2,[1]]
I wish to take away all duplicates. If it were not for the brackets inside the the list, then I could use:
m=list(set(m))
but when I do this, I get the error:
unhashable type 'set'.
What command will help me remove duplicates so that I could only be left with the list
m=[1,2,[1]]
Thank you
You can do something along these lines:
m=[1,2,[1],1,2,[1]]
seen=set()
nm=[]
for e in m:
try:
x={e}
x=e
except TypeError:
x=frozenset(e)
if x not in seen:
seen.add(x)
nm.append(e)
>>> nm
[1, 2, [1]]
From comments: This method preserves the order of the original list. If you want the numeric types in order first and the other types second, you can do:
sorted(nm, key=lambda e: 0 if isinstance(e, (int,float)) else 1)
The first step will be to convert the inner lists to tuples:
>> new_list = [tuple(i) if type(i) == list else i for i in m]
Then create a set to remove duplicates:
>> no_duplicates = set(new_list)
>> no_duplicates
{1, 2, (1,)}
and you can convert that into list if you wish.
For a more generic solution you can serialize each list item with pickle.dumps before passing them to set(), and then de-serialize the items with pickle.loads:
import pickle
m = list(map(pickle.loads, set(map(pickle.dumps, m))))
If you want the original order to be maintained, you can use a dict (which has become ordered since Python 3.6+) instead of a set:
import pickle
m = list(map(pickle.loads, {k: 1 for k in map(pickle.dumps, m)}))
Or if you need to be compatible with Python 3.5 or earlier versions, you can use collections.OrderedDict instead:
import pickle
from collections import OrderedDict
m = list(map(pickle.loads, OrderedDict((k, 1) for k in map(pickle.dumps, m))))
result = []
for i in m:
flag = True
for j in m:
if i == j:
flag = False
if flag:
result.append(i)
Result will be: [1,2,[1]]
There are ways to make this code shorter, but I'm writing it more verbosely for readability. Also, note that this method is O(n^2), so I wouldn't recommend for long lists. But benefits is the simplicity.
Simple Solution,
m=[1,2,[1],1,2,[1]]
l= []
for i in m:
if i not in l:
l.append(i)
print(l)
[1, 2, [1]]
[Program finished]

Resources