Indexing nested list by a list - python

given a nested list like
>>> m= [[3, 1], [2, 7]]
I can get an element like this
>>> m[1][0]
2
How can i get the same value if the index is given in a list, i.e. as [1, 0]?
I am looking for something that the Q programming language offers with dot like in code below
q) m: (3 1; 2 7)
q) m[1][0]
2
q) m . 1 0
2

As a quick-n-dirty solution, you can abuse functools.reduce like this:
from functools import reduce
def get_recursive(lst, idx_list):
return reduce(list.__getitem__, [lst, *idx_list])
>>> y = [[3, 1], [2, 7]]
>>> get_recursive(y, [0, 1])
1
>>> get_recursive(y, [1, 0])
2
There are quite a few corner cases to handle (plus you'd have to be sure the path exists or else handle any errors that arise) but this should get you started.

just define a recursive function that takes the index of the passed list, then passes this index and the sliced index list to itself, until the sliced index list is empty:
def get(m,l):
if not l:
return m
return get(m[l[0]],l[1:])
print(get([[3, 1], [2, 7]],[1,0]))
prints: 2

You can convert to NumPy array first:
import numpy as np
m = [[3, 1], [2, 7]]
np.array(m)[1,0]
Output:
2

This solution requires imports, but only from the standard library. Very similar to #cs95's solution, but slightly cleaner in my view.
from functools import reduce
from operator import getitem
nested_list = [[[['test'], ['test1']], ['test2']], ['test3', 'test4']]
indices = [0, 1, 0]
assert reduce(getitem, indices, nested_list) == 'test2'

Related

How to iterate the first index value twice before going to the next index position?

I'm trying to make a for loop that iterates each index twice before going to the next one, for example if I have the following list:
l = [1,2,3]
I would like to iterate it if it was in this way:
l = [1,1,2,2,3,3]
could someone help me with this problem please?
The most obvious thing would be a generator function that yields each item in the iterable twice:
def twice(arr):
for val in arr:
yield val
yield val
for x in twice([1, 2, 3]):
print(x)
If you need a list, then
l = list(twice([1, 2, 3]))
You could make a list comprehension that repeats the elements and flattens the result:
l = [1,2,3]
repeat = 2
[n for i in l for n in [i]*repeat]
# [1, 1, 2, 2, 3, 3]
You can solve this by using NumPy.
import numpy as np
l = np.repeat([1,2,3],2)
Repeat repeats elements of an array the specified number of times. This also returns a NumPy array. This can be converted back to a list if you wish with list(l). However, NumPy arrays act similar to lists so most of the time you don't need to change anything.
Unsurprisingly, more-itertools has that:
>>> from more_itertools import repeat_each
>>> for x in repeat_each([1, 2, 3]):
... print(x)
...
1
1
2
2
3
3
(It also has an optional second parameter for telling it how often to repeat each. For example repeat_each([1, 2, 3], 5). Default is 2.)
l = [1, 2, 3]
lst = []
for x in l:
for i in range(2):
lst.append(x)
print(lst)
# [1, 1, 2, 2, 3, 3]

Itertools for * arguments

I want to have a function to generate cross product of arbitrary number of arrays.
# Code to generate cross product of 3 arrays
M = [1, 1]
N = [2, 3]
K = [4, 5]
for M, N, K in itertools.product(M, N, K)
If I want to introduce a function using *, what is a good way to achieve that?
I tried the following code, but ended with an error: "TypeError: 'builtin_function_or_method' object is not iterable"
# def cross_product(*inputs):
return itertools.product(inputs)
cross_product(M, N, K)
You can actually just use unpacking without a helper function:
import itertools
L = [[1, 1],
[2, 3],
[4, 5]]
for x in itertools.product(*L):
print(x)

Use splat operator in multidimensional list index

Say I have the list
a = [[1, 2, 3],
[4, 5, 6]]
and I have an index that I want to use to access an element of this list.
index = [1,2]
I want to use something like
a[*index] = 9
to mean a[index[0]][index[1]] = 9, but this doesn't work and neither does a[**index] = 9. Is there a similar way to do this without having a chain of index calls?
I would like a method to do this without using any libraries that must be imported.
First of all a[c, d, e] is equivalent to a[(c, d, e)] which is equivalent to a.__getitem__((c, d, e)). Note the double parentheses. Any __getitem__ implementation that wants to play nice with the Python data model always expects exactly one (explicit) argument.
That's why unpacking values from index inside the [] does not make much sense. a[*index] will give you a SyntaxError and a.__getitem__(*index) gives you a TypeError (because you are providing too many arguments).
Standard Python lists expect integer arguments to __getitem__, but numpy supports indexing with tuples (a numpy array still only takes exactly one argument for __getitem__, but it's allowed to be a tuple).
Demo:
>>> import numpy as np
>>> a = np.array([[1,2,3], [4,5,6]])
>>> a[(1,2)]
6
Of course, you can omit the parentheses, because
>>> a[1,2]
6
is exactly equivalent.
You can use reduce(), which is part of the standard library:
>>> a = [[1, 2, 3],
... [4, 5, 6]]
>>> index = [1, 2]
>>> import functools, operator
>>> functools.reduce(operator.getitem, index, a)
6
Or, you can write your own class that supports that kind of multi-dimensional indexing:
import functools, operator
class Matrix:
def __init__(self, lst):
self._lst = lst
def __getitem__(self, index):
return functools.reduce(operator.getitem, index, self._lst)
a = Matrix([[1, 2, 3],
[4, 5, 6]])
index = [1, 2]
print(a[index]) # -> 6
Otherwise, this is not possible using just lists and without loops or other functions.

Is there a way to set a list value in a range of integer in python

I want to perform the following:
>>> [0-2, 4] #case 1
[-2, 4] #I want the output to be [0, 1, 2, 4]
I know I can perform the same in this way:
>>> list(range(3)) + [4] #case 2
[0, 1, 2, 4]
But I am curious is there any way to achieve the same result using the case 1 method (or something similar)? Do I need to override the integer '-' operator or do I need to do anything with the list?
>>> [*range(0,3), 4]
[0, 1, 2, 4]
Should come closest. Python 3 only.
The answer by #timgeb is great, but this returns a list, and by default range returns an "an immutable sequence type" and so using itertools.chain would give an iterable that is more closely related to range().
import itertools
itertools.chain(range(3), range(4,5))
which (once converting to a list with list() so we can see the contents) would give:
[0, 1, 2, 4]
Or you could make your own generator:
def joinRanges(r1, r2):
for i in r1:
yield i
for i in r2:
yield i
which achieves the same effect as before when calling with:
joinRanges(range(3), range(4,5))

Python Multidimensional Arrays - most efficient way to count number of non-zero entries

Hi there on a Saturday Fun Night,
I am getting around in python and I am quite enjoying it.
Assume I have a python array:
x = [1, 0, 0, 1, 3]
What is the fastest way to count all non zero elements in the list (ans: 3) ? Also I would like to do it without for loops if possible - the most succint and terse manner possibe, say something conceptually like
[counter += 1 for y in x if y > 0]
Now - my real problem is that I have a multi dimensional array and what I really want to avoid is doing the following:
for p in range(BINS):
for q in range(BINS):
for r in range(BINS):
if (mat3D[p][q][r] > 0): some_feature_set_count += 1
From the little python I have seen, my gut feeling is that there is a really clean syntax (and efficient) way how to do this.
Ideas, anyone?
For the single-dimensional case:
sum(1 for i in x if i)
For the multi-dimensional case, you can either nest:
sum(sum(1 for i in row if i) for row in rows)
or do it all within the one construct:
sum(1 for row in rows
for i in row if i)
If you are using numpy as suggested by the fact that you're using multi-dimensional arrays in Python, the following is similar to #Marcelo's answer, but a tad cleaner:
>>> a = numpy.array([[1,2,3,0],[0,4,2,0]])
>>> sum(1 for i in a.flat if i)
5
>>>
If you go with numpy and your 3D array is a numpy array, this one-liner will do the trick:
numpy.where(your_array_name != 0, 1, 0).sum()
example:
In [23]: import numpy
In [24]: a = numpy.array([ [[0, 1, 2], [0, 0, 7], [9, 2, 0]], [[0, 0, 0], [1, 4, 6], [9, 0, 3]], [[1, 3, 2], [3, 4, 0], [1, 7, 9]] ])
In [25]: numpy.where(a != 0, 1, 0).sum()
Out[25]: 18
While perhaps not concise, this is my choice of how to solve this which works for any dimension:
def sum(li):
s = 0
for l in li:
if isinstance(l, list):
s += sum(l)
elif l:
s += 1
return s
def zeros(n):
return len(filter(lambda x:type(x)==int and x!=0,n))+sum(map(zeros,filter(lambda x:type(x)==list,n)))
Can't really say if it is the fastest way but it is recursive and works with N dimensional lists.
zeros([1,2,3,4,0,[1,2,3,0,[1,2,3,0,0,0]]]) => 10
I would have slightly changed Marcelo's answer to the following:
len([x for x in my_list if x != 0])
The sum() above tricked me for a second, as I thought he was getting the total value instead of count until I seen the 1 hovering at the start. I'd rather be explicit with len().
Using chain to reduce array lookups:
from itertools import chain
BINS = [[[2,2,2],[0,0,0],[1,2,0]],
[[1,0,0],[0,0,2],[1,2,0]],
[[0,0,0],[1,1,1],[1,3,0]]]
sum(1 for c in chain.from_iterable(chain.from_iterable(BINS)) if c > 0)
14
I haven't done any performance checks on this. But it doesn't use any significant memory.
Note that it is using a generator expression, not a list comprehension. Adding the [list comprehension] syntax will create an array to be summed instead of feeding one number at a time to sum.

Categories