Is it possible to index nested lists using tuples in python?
Asked Answered
V

7

6

I just started with python and very soon wondered if indexing a nested list with a tuple was possible. Something like: elements[(1,1)]

One example where I wanted to do that was something similar to the code below in which I save some positions of the matrix that I will later need to access in a tuple called index.

index = ( (0,0), (0,2), (2,0), (2,2) )

elements = [ [ 'a', 'b', 'c'],
             [ 'c', 'd', 'e'],
             [ 'f', 'g', 'h'] ]

for i in index:
    print (elements [ i[0] ] [ i[1] ])

    # I would like to do this:
    # print(elements[i])

It seems like a useful feature. Is there any way of doing it? Or perhaps a simple alternative?

Volscian answered 20/5, 2015 at 5:31 Comment(3)
What exactly is your question? Your example works as advertised?Sigismond
I wanted to know it there is a more direct way of indexing the list than elements[ i[0]] [i[1]]Volscian
FYI: Tuples and SequencesSigismond
S
2

Yes, you can do that. I wrote a similar example:

index = [ [0,0], [0,2], [2,0], [2,2] ]

elements = [ [ 'a', 'b', 'c'],
             [ 'c', 'd', 'e'],
             [ 'f', 'g', 'h'] ]

for i,j in index:
    print (elements [ i ] [ j ])

a c f h

Scope answered 20/5, 2015 at 5:36 Comment(4)
This is pretty much identical to the OP's example; except you've swapped out i[0] and i[1] for i, j tuple unpacking! :) +1Sigismond
Wait a sec, those square brackets do create lists and no tuples, don't they?Thad
Thank you for the fast response.I just tested and it works. It's not as simple as the syntax that I wanted but it's great anyway. Getting rid of the extra brackets makes it look more clean. I will mark as answered as soon as I canVolscian
The downside to this is that you have to know the exact dimensions.Autonomic
S
9

If you really want to use tuples for indexing you can implement your own class that extends list and redefines __getattr__ to work with tuples and use that:

class TList(list):
    def __getitem__(self, index):
        if hasattr(index, "__iter__"):
            # index is list-like, traverse downwards
            item = self
            for i in index:
                item = item[i]
            return item
        # index is not list-like, let list.__getitem__ handle it
        return super().__getitem__(index)

elements = TList([ [ 'a', 'b', 'c'],
                   [ 'c', 'd', 'e'],
                   [ 'f', 'g', 'h'] ])
index = ( (0,0), (0,2), (2,0), (2,2) )
for i in index:
    print(elements[i])

a
c
f
h

Sculptress answered 20/5, 2015 at 5:49 Comment(1)
Thanks! That's exactly what I wanted. But in my case, as with propably most people, the extra effort to make it work is not really worth it.Volscian
S
2

Yes, you can do that. I wrote a similar example:

index = [ [0,0], [0,2], [2,0], [2,2] ]

elements = [ [ 'a', 'b', 'c'],
             [ 'c', 'd', 'e'],
             [ 'f', 'g', 'h'] ]

for i,j in index:
    print (elements [ i ] [ j ])

a c f h

Scope answered 20/5, 2015 at 5:36 Comment(4)
This is pretty much identical to the OP's example; except you've swapped out i[0] and i[1] for i, j tuple unpacking! :) +1Sigismond
Wait a sec, those square brackets do create lists and no tuples, don't they?Thad
Thank you for the fast response.I just tested and it works. It's not as simple as the syntax that I wanted but it's great anyway. Getting rid of the extra brackets makes it look more clean. I will mark as answered as soon as I canVolscian
The downside to this is that you have to know the exact dimensions.Autonomic
T
1

Here are some answers that work without knowing the dimension in advance.

A recursive version:

def multiget_rec(arr, *indices):
    if len(indices)==0:
        return arr
    return multiget_rec(arr[indices[0]], *indices[1:])

A procedural version:

def multiget_proc(arr, *indices):
    while len(indices)>0:
        i, *indices = indices
        arr = arr[i]
    return arr

And a version based on reduce which could be used as a 1-liner:

from functools import reduce
def multiget_reduce(arr, *indices):
    return reduce(lambda a,i:a[i], indices, arr)

All can be called like

for i in index:
    print (multiget(elements, *i))

Edit:

Using reduce seems to be the fastest of the three methods, at least for small arrays. I also think it looks the cleanest.

$ python -m timeit -s "def multiget_rec(arr, *indices):" \
    -s "    if len(indices)==0: return arr" \
    -s "    return multiget_rec(arr[indices[0]], *indices[1:])" \
    -- "d = [[[3]]]" \
    "multiget_rec(d, 0,0,0)"
500000 loops, best of 5: 941 nsec per loop
$ python -m timeit -s "def multiget_proc(arr, *indices):" \
    -s "    while len(indices)>0:" \
    -s "        i, *indices = indices" \
    -s "    return arr" \
    -s "d = [[[3]]]" \
    -- "multiget_proc(d, 0,0,0)"
500000 loops, best of 5: 529 nsec per loop
$ python -m timeit -s "from functools import reduce" \
    -s "def multiget_reduce(arr, *indices):" \
    -s "    return reduce(lambda a,i:a[i], indices, arr)" \
    -s "d = [[[3]]]" \
    -- "multiget_reduce(d, 0,0,0)"
1000000 loops, best of 5: 384 nsec per loop
Troytroyer answered 21/6, 2021 at 5:51 Comment(0)
S
0
# I would like to do this:
# print(elements[i])

No you cannot index a specific value of a nested list in this way.

The only slightly better way would be to "unpack" the tuples are you're iterating over them:

Example:

for i, j in index:
    print(elements[i][j])

See: Tuples ans Sequences

Sigismond answered 20/5, 2015 at 5:41 Comment(0)
D
0

If you want to print everything in elements

index = ( (0,0), (0,2), (2,0), (2,2) )

elements = [ [ 'a', 'b', 'c'],
             [ 'c', 'd', 'e'],
             [ 'f', 'g', 'h'] ]

for row in elements:
    for i in range(len(row)):
        print (row[i])
Decameter answered 20/5, 2015 at 5:48 Comment(1)
Thanks! But I just wanted to visit some positions. That's why I wanted to save the indexes. I will need those positions more frequently than the rest of the matrix.Volscian
S
0

You can use list comprehensions:

index = ((0, 0), (0, 2), (2, 0), (2, 2))

elements = [['a', 'b', 'c'],
            ['c', 'd', 'e'],
            ['f', 'g', 'h']]

tmp = [print(elements[i][j]) for i,j in index]
Selfsame answered 20/5, 2015 at 5:54 Comment(1)
You are still acessing it with elements[i[0]][i[1]]. But I think that you could unpack the tuple and do it like this: tmp = [print(elements[i][j]) for i,j in index]Volscian
P
-1

I know this question is 6 years ago but my solution might still be useful. (or not)

Solution:

the_func = lambda func, index1, index2: func[index1][index2]

func = [[1,2,3,4],[5,6,7,8]]
index = [(1, 1), (0, 1)] # [6, 2]
print(the_func(func, *index[0]))
Packston answered 26/4, 2022 at 12:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.