Convert nested iterables to list - python

Is there an easy way in python (using itertools, or otherwise) to convert a nested iterable f into its corresponding list or tuple? I'd like to save f so I can iterate over it multiple times, which means that if some nested elements of f are generators, I'll be in trouble.
I'll give an example input/output.
>>> g = iter(range(2))
>>> my_input = [1, [2, 3], ((4), 5), [6, g]]
>>> magical_function(my_input)
[1, [2, 3], [[4], 5], [6, [0, 1]]]
It would be fine if the output consisted of tuples, too. The issue is that iterating over g "consumes" it, so it can't be used again.

This seems like it would be best to do by checking if each element is iterable, and calling a recursive function over it if it is iterable. Just as a quick draw-up, I would try something like:
import collections
g = iter(range(2))
my_input = [1, [2, 3], ((4), 5), [6, g]]
def unfold(iterable):
ret = []
for element in iterable:
if isinstance(element, collections.Iterable):
ret.append(unfold(element))
else:
ret.append(element)
return ret
n = unfold(my_input)
print(n)
print(n)
which returns
$ python3 so.py
[1, [2, 3], [4, 5], [6, [0, 1]]]
[1, [2, 3], [4, 5], [6, [0, 1]]]
It's not the prettiest way, and you can find ways to improve it (it puts everything in a list instead of preserving tuples), but here is the general idea I would use.

Related

Python casting leads to weird mutability result

Why is it that when I write the code block as:
def rev(li):
li.reverse()
l = [[1, 2, 3], [4, 5, 6]]
map(rev, l)
print(l)
The resulting list is the same as the original list, but when I rewrite the code to now cast the map object into a list as such:
def rev(li):
li.reverse()
l = [[1, 2, 3], [4, 5, 6]]
list(map(rev, l))
print(l)
It does reverse the inner lists to give me [[3, 2, 1], [6, 5, 4]]. How does casting the map object to a list change mutability rules for the list l?

Quicker way to join multiple lists passed as args to a Python function?

So I have a function which takes a variable number of lists as an argument, then combines those lists into one single list:
def comb_lists(*lists):
sublist = []
for l in lists:
sublist.extend(l)
print(sublist)
>>> comb_lists([1, 2], [3, 4], [5, 6])
[1, 2, 3, 4, 5, 6]
And it works. But I was just wondering if there was a simpler solution? I tried a list comprehension using list unpacking, but that returned a SyntaxError:
def comb_lists(*lists):
sublist = [*l for l in lists]
>>> comb_lists([1, 2], [3, 4], [5, 6])
SyntaxError: iterable unpacking cannot be used in comprehension
Is there any neater or quicker way to do this?
EDIT: itertools looks really useful for this sort of thing. I'd be interested to know if there's any way of doing it that doesn't rely on imports though.
here is the simplest solution
result = sum(lists, [])
There's built-in function chain.form_iterable() in itertools module to do this:
>>> from itertools import chain
>>> my_list = [[1, 2], [3, 4], [5, 6]]
>>> list(chain.from_iterable(my_list))
[1, 2, 3, 4, 5, 6]
If you do not want to import any module, you can write nested list comprehension to achieve this as:
>>> my_list = [[1, 2], [3, 4], [5, 6]]
>>> [e for l in my_list for e in l]
[1, 2, 3, 4, 5, 6]

How to slice 2d list according to multiple identical first column elements

I'm trying to figure out if there is an efficient way of slicing a 2d-list according to the first element of each inner list, when there are multiple identical first elements.
I haven't used numpy, in fact I've never used it before, but I'm open to it. Honestly, I could come up with a very cumbersome way to do this without it, but I'm hoping for more efficiency.
list_to_slice = [[1, 2], [2, 2], [2, 3], [3, 3]]
My goal in this example is to slice the 2d list according to the first element. The goal output is:
[[1, 2], [2, 2], [2, 3]]
EDIT:
Okay, what I want to do is slice all of the inner list elements of the original list with the first element "2". Everything from there to the beginning of the list. The basic command to do so would be:
list_to_slice[0:3]
I'm trying to see if there is an efficient command anywhere, in numpy or elsewhere, to slice the list according to its content and not its location in the list. Like I said, I could probably find a way to do this, I'm just curious if there is a simpler command available in any libraries out there.
Thanks!
itertools.takewhile is made specifically for this kind of thing. It will turn your list into an itertator that takes values from the list until a condition is not longer true and then stop:
from itertools import takewhile
list_to_slice = [[1, 2], [2, 2], [2, 3], [3, 3]]
sl = takewhile(lambda t: t[0] < 3, list_to_slice) # take items while first element is less than 3
list(sl)
# [[1, 2], [2, 2], [2, 3]]
I think you are trying to use the indexes of the first element to select other items in the list. Below is a straightforward solution.
list_to_slice = [[1, 2], [2, 2], [2, 3], [3, 3]]
targets = list_to_slice[0]
result = [targets] + [list_to_slice[targets[0]]] + [list_to_slice[targets[1]]]
print(result)
>>> [[1, 2], [2, 2], [2, 3]]
A more general purpose solution
def first_slice(list_to_slice):
targets = list_to_slice[0]
result = [targets]
for target in targets:
result.append(list_to_slice[target])
return result
first_slice([[1, 2], [2, 2], [2, 3], [3, 3]])
>>> [[1, 2], [2, 2], [2, 3]]

How to do Math Functions on Lists within a List

I'm very new to python (using python3) and I'm trying to add numbers from one list to another list. The only problem is that the second list is a list of lists. For example:
[[1, 2, 3], [4, 5, 6]]
What I want is to, say, add 1 to each item in the first list and 2 to each item in the second, returning something like this:
[[2, 3, 4], [6, 7, 8]]
I tried this:
original_lst = [[1, 2, 3], [4, 5, 6]]
trasposition_lst = [1, 2]
new_lst = [x+y for x,y in zip(original_lst, transposition_ls)]
print(new_lst)
When I do this, I get an error
can only concatenate list (not "int") to list
This leads me to believe that I can't operate in this way on the lists as long as they are nested within another list. I want to do this operation without flattening the nested list. Is there a solution?
One approach using enumerate
Demo:
l = [[1, 2, 3], [4, 5, 6]]
print( [[j+i for j in v] for i,v in enumerate(l, 1)] )
Output:
[[2, 3, 4], [6, 7, 8]]
You can use enumerate:
l = [[1, 2, 3], [4, 5, 6]]
new_l = [[c+i for c in a] for i, a in enumerate(l, 1)]
Output:
[[2, 3, 4], [6, 7, 8]]
Why don't use numpy instead?
import numpy as np
mat = np.array([[1, 2, 3], [4, 5, 6]])
mul = np.array([1,2])
m = np.ones(mat.shape)
res = (m.T *mul).T + mat
You were very close with you original method. Just fell one step short.
Small addition
original_lst = [[1, 2, 3], [4, 5, 6]]
transposition_lst = [1, 2]
new_lst = [[xx + y for xx in x] for x, y in zip(original_lst, transposition_lst)]
print(new_lst)
Output
[[2, 3, 4], [6, 7, 8]]
Reasoning
If you print your original zip it is easy to see the issue. Your original zip yielded this:
In:
original_lst = [[1, 2, 3], [4, 5, 6]]
transposition_lst = [1, 2]
for x,y in zip(original_lst, transposition_lst):
print(x, y)
Output
[1, 2, 3] 1
[4, 5, 6] 2
Now it is easy to see that you are trying to add an integer to a list (hence the error). Which python doesn't understand. if they were both integers it would add them or if they were both lists it would combine them.
To fix this you need to do one extra step with your code to add the integer to each value in the list. Hence the addition of the extra list comprehension in the solution above.
A different approach than numpy that could work even for lists of different lengths is
lst = [[1, 2, 3], [4, 5, 6, 7]]
c = [1, 2]
res = [[l + c[i] for l in lst[i]] for i in range(len(c))]

Python: Delete all list indices meeting a certain condition

to get right down to it, I'm trying to iterate through a list of coordinate pairs in python and delete all cases where one of the coordinates is negative. For example:
in the array:
map = [[-1, 2], [5, -3], [2, 3], [1, -1], [7, 1]]
I want to remove all the pairs in which either coordinate is < 0, leaving:
map = [[2, 3], [7, 1]]
My problem is that python lists cannot have any gaps, so if I loop like this:
i = 0
for pair in map:
for coord in pair:
if coord < 0:
del map[i]
i += 1
All the indices shift when the element is deleted, messing up the iteration and causing all sorts of problems. I've tried storing the indices of the bad elements in another list and then looping through and deleting those elements, but I have the same problem: once one is gone, the whole list shifts and indices are no longer accurate.
Is there something I'm missing?
Thanks.
If the list is not large, then the easiest way is to create a new list:
In [7]: old_map = [[-1, 2], [5, -3], [2, 3], [1, -1], [7, 1]]
In [8]: new_map=[[x,y] for x,y in a_map if not (x<0 or y<0)]
In [9]: new_map
Out[9]: [[2, 3], [7, 1]]
You can follow this up with old_map = new_map if you want to discard the other pairs.
If the list is so large creating a new list of comparable size is a problem, then you can delete elements from a list in-place -- the trick is to delete them from the tail-end first:
the_map = [[-1, 2], [5, -3], [2, 3], [1, -1], [7, 1]]
for i in range(len(the_map)-1,-1,-1):
pair=the_map[i]
for coord in pair:
if coord < 0:
del the_map[i]
print(the_map)
yields
[[2, 3], [7, 1]]
PS. map is such a useful built-in Python function. It is best not to name a variable map since this overrides the built-in.
You can use list comprehension for this:
>>> mymap = [[-1, 2], [5, -3], [2, 3], [1, -1], [7, 1]]
>>> mymap = [m for m in mymap if m[0] > 0 and m[1] > 0]
>>> mymap
[[2, 3], [7, 1]]
If you do not have any other references to the map list, a list comprehension works best:
map = [[a,b] for (a,b) in map if a > 0 and b > 0]
If you do have other references and need to actually remove elements from the list referenced by map, you have to iterate over a copy of map:
for coord in map[:]:
if coord[0] < 0 or coord[1] < 0:
map.remove(coord)
Personally, I prefer in-place modification:
li = [[-1, 2], [5, -3], [2, 3], [1, -1], [7, 1]]
print li,'\n'
N = len(li)
for i,(a,b) in enumerate(li[::-1], start=1):
if a<0 or b<0:
del li[N-i]
print li
->
[[-1, 2], [5, -3], [2, 3], [1, -1], [7, 1]]
[[2, 3], [7, 1]]
If you wish to do this in place, without creating a new list, simply use a for loop with index running from len(map)-1 down to 0.
for index in range(len(map)-1,-1,-1):
if hasNegativeCoord(map[index]):
del(map[index])
Not very Pythonic, I admit.
If the list is small enough, it's more efficient to make a copy containing just the elements you need, as detailed in the other answers.
However, if the list is too large, or for some other reason you need to remove the elements from the list object in place, I've found the following little helper function quite useful:
def filter_in_place(func, target, invert=False):
"remove all elements of target where func(elem) is false"
pos = len(target)-1
while pos >= 0:
if (not func(target[pos])) ^ invert:
del target[pos]
pos -= 1
In your example, this could be applied as follows:
>>> data = [[-1, 2], [5, -3], [2, 3], [1, -1], [7, 1]]
>>> def is_good(elem):
return elem[0] >= 0 and elem[1] >= 0
>>> filter_in_place(is_good, data)
>>> data
[[2, 3], [7, 1]]
(This is just a list-oriented version of filter_in_place, one which supports all base Python datatypes is a bit more complex).
itertools.ifilter()/ifilterfalse() exist to do exactly this: filter an iterable by a predicate (not in-place, obviously).
Better still, avoid creating and allocating the entire filtered list object if at all possible, just iterate over it:
import itertools
l = [(4,-5), (-8,2), (-2,-3), (4,7)]
# Option 1: create a new filtered list
l_filtered = list( itertools.ifilter(lambda p: p[0]>0 and p[1]>0, l) )
# Option 2:
for p in itertools.ifilter(lambda p: p[0]>0 and p[1]>0, l):
... <subsequent code on your filtered list>
You probably want del pair instead.

Categories

Resources