Nested List Comprehension with if/else - python

I have a function that takes a string in a format such as '1,3-5,7,19' and will output the list [1, 3, 4, 5, 7, 19].
However, I was thinking that maybe this was simple enough to do with a nested list comprehension.
My original function is:
result = []
for x in row_range.split(','):
if '-' in x:
for y in range(int(x.split('-')[0]), int(x.split('-')[1]) + 1)):
result.append(y)
else:
result.append(int(x))
I thought the comprehension would be something like:
result = [y for x in row_range.split(',') if '-' in x else int(x) for y in range(int(x.split('-')[0]), int(x.split('-')[1] + 1)]
or even
result = [y for x in row_range.split(',') if '-' in x for y in range(int(x.split('-')[0]), int(x.split('-')[1] + 1) else int(x)]
but these are SyntaxError. Moving the if/else to the front of the comprehension as
result = [y if '-' in x else int(x) for x in row_range.split(',') for y in range(int(x.split('-')[0]), int(x.split('-')[1]) + 1)]
results in an IndexError: list index out of range.
Is this possible? I already have a function that handles it nicely and is more readable but I am simply curious if this can be accomplished in python.

You could define a small helper function:
def foo(x):
x, y = map(int, x.split('-'))
return (x, y + 1)
Now, use a list comprehension with a nested loop.
>>> [y for x in row_range.split(',')
for y in ([int(x)] if '-' not in x else range(*foo(x)))]
[1, 3, 4, 5, 7, 19]

Alternative solution with re.sub() function:
import re
row_range = '1,3-5,7,8,10-14,19' # extended example
cb = lambda r: repr(list(range(int(r.group(1)), int(r.group(2))+1)))[1:-1]
result = [int(i) for i in re.sub(r'(\d+)-(\d+)', cb, row_range).split(',')]
print(result)
The output:
[1, 3, 4, 5, 7, 8, 10, 11, 12, 13, 14, 19]

Related

Trying to figure out how to iterate a list twice in a One function

I am trying to figure out how to iterate over fn to modify the list again with only even numbers, AFTER appending. The desire result is
[2,4,6,8,10,12,14,16,18,20]
is this possible? I do not want to add fn + sn or lst + lst2. I want to continue over this specific function.
lst = [1,2,3,4,5,6,7,8,9,10]
lst2 =[11,12,13,14,15,16,17,18,19,20]
def even(fn,sn):
for i in sn:
if i %2 ==0:
fn.append(i)
even(lst,lst2)
print(lst) # output [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 14, 16, 18, 20]
I know there are more efficient and pythonic ways like this example:
example 1:
def even2(fn,sn):
fn[:] = [x for x in fn + sn if x % 2 == 0]
example 2:
def even(fn, sn):
fn.extend(sn)
fn[:] = [x for x in fn if x % 2 == 0]
def even(fn,sn):
for i in sn:
if i %2 ==0:
fn.append(i)
fn = list(filter(lambda x: x %2 == 0, fn))
the last code block is what i tried and failed with
You don't need to rebuild the entire list; just extend it with the desired extra elements.
>>> lst = [1,2,3,4,5,6,7,8,9,10]
>>> lst2 =[11,12,13,14,15,16,17,18,19,20]
>>> def even(fn, sn):
... fn.extend(i for i in sn if i % 2 == 0)
...
>>> even(lst, lst2)
>>> lst
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 14, 16, 18, 20]
In the above expression i for i... is a generator expression that allows extend to iterate over all of the i elements without storing them in a separate list first.
You could do it in two steps:
remove odd numbers from fn
extend fn with the even numbers in sn
This function does it without creating any temporary copies or sublists:
lst = [1,2,3,4,5,6,7,8,9,10]
lst2 =[11,12,13,14,15,16,17,18,19,20]
def even(fn,sn):
for i,n in enumerate(reversed(fn),1-len(fn)): # delete odds backwards
if n%2: del fn[-i] # so indexes stay stable
fn.extend(n for n in sn if n%2==0) # extend with sn's evens
even(lst,lst2)
print(lst)
# [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
Note that you could extend fn first and then remove the odd numbers, but that would be inefficient:
def even(fn,sn):
fn.extend(sn) # add all sn values
for i,n in enumerate(reversed(fn),1-len(fn)): # delete odds backwards
if n%2: del fn[-i] # so indexes stay stable
A more efficient way would be to iterate through the two lists and assign the result to fn[:]. Doing this starting with fn will ensure that there is no conflict between the iterators and the assignment process:
def even(fn,sn):
fn[:] = (n for L in (fn,sn) for n in L if n%2==0)
The nested comprehension loops allow iterating through the two lists without actually concatenating them so there is no temporary copies or sublists in that solution either.
Does this meet your requirements? Pulling lst into the even() func as a global.
lst = [1,2,3,4,5,6,7,8,9,10]
lst2 =[11,12,13,14,15,16,17,18,19,20]
def even(fn, sn):
global lst
lst = [x for x in (fn + sn) if x % 2 == 0]
even(lst,lst2)
print(lst)

One line code using map, lambda and if condition

Using zip and list comprehension, create a new list, L3, that sums the two numbers if the number from L1 is greater than 10 and the number from L2 is less than 5. This can be accomplished in one line of code.
I have tried the codes below. Can anyone explain why python complains?
L1 = [1, 5, 2, 16, 32, 3, 54, 8, 100]
L2 = [1, 3, 10, 2, 42, 2, 3, 4, 3]
L3 = map(lambda (x1,x2): x1 + x2 if(x1 > 10 and x2 < 5) , zip(L1,L2))
L4 = map(lambda x: x[0] + x[1] if(x[0] > 10 and x[1] < 5) , zip(L1,L2))
print(L3)
print(L4)
This is an XY problem. You're using a map when it asks for a list comprehension. It's essentially asking for a filter, not a map.
>>> [x1+x2 for x1, x2 in zip(L1, L2) if x1>10 and x2<5]
[18, 57, 103]
You could do it in a functional style, but it's so ugly. Don't do this.
L3 = list(map(
lambda t: t[0]+t[1],
filter(
lambda t: t[0]>10 and t[1]<5,
zip(L1, L2)
)
))
print(L3) # -> [18, 57, 103]
Have a look here: The lambda function must have a return value and if your if statement is not true, there is none. Therefore, you would have to add an else statement, for example:
L4 = map(lambda x: x[0] + x[1] if (x[0] > 10 and x[1] < 5) else 0, zip(L1,L2))
Results in
[0, 0, 0, 18, 0, 0, 57, 0, 103]
Your first try (L3) does not work because lambda can no longer unpack tuples in python 3.x (see: here)
Please also note that map() returns an object and in order to print its content you would have to call
print(list(map_object))
# in your case:
print(list(L4))
# or set()
Edit based on comment:
if you really want a one-liner and as your first sentence says "using zip and list comprehension" and you want to leave out 0s, then this might work and is easier than map and lambda:
L3 = [x[0]+x[1] for x in zip(L1,L2) if (x[0] > 10 and x[1] < 5)]
Edit 2 if you really, really, really insist on using map and lambda:
L4 = map(lambda x: x[0] + x[1], filter(lambda x: (x[0] > 10 and x[1] < 5), zip(L1,L2)))

Modify function to accept tables as well (Python 3)

I've designed a simple function that looks at an inputted list of numbers, identifies the minimum and maximum values, then substitutes both of them for the midpoint value between them, the function is here:
def mainfunction(list_of_numbers):
smallest_number = list_of_numbers[0]
for a in list_of_numbers:
if a < smallest_number:
smallest_number = a
largest_number = list_of_numbers[0]
for b in list_of_numbers:
if b > largest_number:
largest_number = b
midpoint = (smallest_number + largest_number)/2
final_list = [x if (x != largest_number and x != smallest_number) else midpoint for x in list_of_numbers]
return final_list
print(mainfunction([10, 7, 14, 3, -200, 8, 1, -12, 250]))
Unfortunately, I can't get the function to work on TABLES of numbers, is there an easy way to convert a table of numbers into a list? Any info would be appreciated, cheers!
You can use itertools.chain
from itertools import chain
a = [[3,4], [15,16], [19,20]]
res = list(chain.from_iterable(a))
print(res)
Output:
[3, 4, 15, 16, 19, 20]
with list comprehension
res = [x for lst in a for x in lst]

How to to remove all zeros from a list

I want to remove all zeros from the list after sorting in descending order.
for x in range (1,count):
exec("col"+str(x) + "=[]")
with open (xvg_input, 'r') as num:
line_to_end = num.readlines()
for line in line_to_end:
if "#" not in line and "#" not in line:
line=list(map(float,line.split()))
for x in range (2,count):
exec("col" +str (x)+ ".append(line["+ str(x-1) + "])")
exec("col" +str(x) + ".sort(reverse = True)")
exec("while (col"+str(x) + ".count(0.000)):")
exec("col" +str(x) +".remove(0.000)")
I am getting the syntax error. I am not getting where I am doing wrong. I just want to sort in descending order and delete all the zeroes.
Does this make sense
def remove_values(the_list, val):
return [value for value in the_list if value != val]
x = [1, 0, 3, 4, 0, 0, 3]
x = remove_values(x, 0)
print x
# [1, 3, 4, 3]
Try using filter method:
list = [9,8,7,6,5,4,3,2,1,0,0,0,0,0,0]
filter(lambda x: x != 0,a) #iterates items, returning the ones that meet the condition in the lambda function
# [9, 8, 7, 6, 5, 4, 3, 2, 1]

Expand a range which looks like: "1-3,6,8-10" to [1,2,3, 6, 8,9,10]

I am trying to add an option to my program which allow the user to choose which steps of the program he wants to do.
I would like to be able to parse a string like "1-3,6,8-10" and get [1, 2, 3, 6, 8, 9, 10].
Do you know if something in Python which is doing that already exists ?
This function does what you asked. It assumes no negative numbers are used, otherwise it needs some changes to support that case.
def mixrange(s):
r = []
for i in s.split(','):
if '-' not in i:
r.append(int(i))
else:
l,h = map(int, i.split('-'))
r+= range(l,h+1)
return r
print mixrange('1-3,6,8-10')
One way using list comprehensions:
s = "1-3,6,8-10"
x = [ss.split('-') for ss in s.split(',')]
x = [range(int(i[0]),int(i[1])+1) if len(i) == 2 else i for i in x]
print([int(item) for sublist in x for item in sublist])
Outputs:
[1, 2, 3, 6, 8, 9, 10]
No builtin function as such, but can be done using xrange and generators:
from itertools import chain
s = "1-3,6,8-10"
spans = (el.partition('-')[::2] for el in s.split(','))
ranges = (xrange(int(s), int(e) + 1 if e else int(s) + 1) for s, e in spans)
all_nums = chain.from_iterable(ranges) # loop over, or materialse using `list`
# [1, 2, 3, 6, 8, 9, 10]
s = '1-3,6,8-10,13-16'
temp = [x.split('-') if '-' in x else x for x in s.split(',')]
# temp = [['1', '3'], '6', ['8', '10'], ['13', '16']]
res = []
for l in temp:
if isinstance(l, list):
a, b = map(int, l)
res += list(range(a, b + 1))
else:
res.append(int(l))
# res = [1, 2, 3, 6, 8, 9, 10, 13, 14, 15, 16]
A little function I just created:
def expand(st):
res = []
for item in st.split(','):
if '-' in item:
temp = map(int, item.split('-'))
res.extend(range(temp[0], temp[1]+1))
else:
res.append(int(item))
return res
s = '1-3,6,8-10'
print expand(s)
Returns:
[1, 2, 3, 6, 8, 9, 10]
def parseIntSet(nputstr=""):
selection = set()
invalid = set()
# tokens are comma seperated values
tokens = [x.strip() for x in nputstr.split(',')]
for i in tokens:
try:
# typically tokens are plain old integers
selection.add(int(i))
except:
# if not, then it might be a range
try:
token = [int(k.strip()) for k in i.split('-')]
if len(token) > 1:
token.sort()
# we have items seperated by a dash
# try to build a valid range
first = token[0]
last = token[len(token)-1]
for x in range(first, last+1):
selection.add(x)
except:
# not an int and not a range...
invalid.add(i)
# Report invalid tokens before returning valid selection
print "Invalid set: " + str(invalid)
return selection
Via: Parsing a list of numbers in Python
Aha, one line proof of concept anyone?
EDIT: Improved version
import itertools
s = "1-3,6,8-10"
print(list(itertools.chain.from_iterable(range(int(ranges[0]), int(ranges[1])+1) for ranges in ((el+[el[0]])[:2] for el in (miniRange.split('-') for miniRange in s.split(','))))))
Now split on several lines for readability:
print(list(itertools.chain.from_iterable(
range(
int(ranges[0]),
int(ranges[1])+1
)
for ranges in
(
(el+[el[0]])[:2] # Allows to get rid of the ternary condition by always adding a duplicate of the first element if it is alone
for el in
(miniRange.split('-') for miniRange in s.split(','))
)
)))

Categories