1

I have a list of namedtuples which I would like to write to a numpy array. The tuples have attributes 'colors', a set of two colors, and 'number', an integer, and are of the form:

from collections import namedtuple
import numpy as np

NamedTuple = namedtuple('tuple',['colors','number'])

L = [NamedTuple({'violet', 'blue'}, 4),  
    NamedTuple({'orange', 'blue'}, 1),  
    NamedTuple({'green', 'blue'}, 3),  
    NamedTuple({'orange', 'red'}, 2)]
L
>>>[tuple(colors={'blue', 'violet'}, number=4)...]
L[3].colors
>>>{'orange', 'red'}

I would like to write from L, for example, a 2x2 array such that:

Array[1][1].colors
>>>{'orange', 'red'}

Doing

Array = numpy.array(L)
>>>[[{'blue', 'violet'} 4]
   [{'blue', 'orange'} 1]
   [{'blue', 'green'} 3]
   [{'red', 'orange'} 2]

gives me an array of tuples, not namedtuples, which have 'no attribute 'colors''
Worse yet, if I try to reshape Array into a 2x2 I find out that each attribute of my namedtuples has been written as a different object in the array.

numpy.reshape(Array,(2,2))
>>>...error...
>>>'ValueError: cannot reshape array of size 8 into shape (2,2)'

I would have thought the above array was size 4?

How can I get an array of namedtuples without mutating the namedtuples, such that I can call for different attributes from each element in the array?

The reason I would like to use namedtuples as my data structure is so it is easy and readable to call each object's .color or .number attribute.
The reason I would like to use a numpy array rather than a standard nested list is because this array is going to be a dynamic object throughout the project which will often be searched through and changed, and I know how poor python's standard lists are for these things.
For context, I am ultimately trying to build a program which plays a card game of my own invention. The namedtuples represent the cards with their colors and numbers. The Array represents a tableau of cards which players can change and move around. These namedtuples are going to be shuffled around quite a bit and I don't want to have to worry about their data structures getting changed.

Zero
  • 33
  • 1
  • 6

1 Answers1

2

Becauuse NamedTuple is a subclass of tuple, constructing an object array from L results in a (n,2) array, same as if we given it as list of tuples or list of lists:

In [4]: np.array(L, object)
Out[4]: 
array([[{'violet', 'blue'}, 4],
       [{'blue', 'orange'}, 1],
       [{'blue', 'green'}, 3],
       [{'red', 'orange'}, 2]], dtype=object)
In [5]: _.shape
Out[5]: (4, 2)

Here's one trick - append a None object to the list, first:

In [13]: arr = np.array(L+[None], object)[:-1]
In [14]: arr
Out[14]: 
array([tuple(colors={'violet', 'blue'}, number=4),
       tuple(colors={'blue', 'orange'}, number=1),
       tuple(colors={'blue', 'green'}, number=3),
       tuple(colors={'red', 'orange'}, number=2)], dtype=object)
In [15]: arr.shape
Out[15]: (4,)
In [16]: arr = np.reshape(arr,(2,2))
In [17]: arr
Out[17]: 
array([[tuple(colors={'violet', 'blue'}, number=4),
        tuple(colors={'blue', 'orange'}, number=1)],
       [tuple(colors={'blue', 'green'}, number=3),
        tuple(colors={'red', 'orange'}, number=2)]], dtype=object)

I've found in previous questions that frompyfunc is the most convenient tool (and fastest) for accessing elements of such an array.

Most efficient way to set attributes on objects in array

In [18]: np.frompyfunc(lambda x: x.colors, 1,1)(arr)
Out[18]: 
array([[{'violet', 'blue'}, {'blue', 'orange'}],
       [{'blue', 'green'}, {'red', 'orange'}]], dtype=object)

To construct a structured array, we can do:

In [19]: arr1 = np.array(L, dtype=[('colors', object), ('number', int)])
In [20]: arr1
Out[20]: 
array([({'violet', 'blue'}, 4), ({'blue', 'orange'}, 1),
       ({'blue', 'green'}, 3), ({'red', 'orange'}, 2)],
      dtype=[('colors', 'O'), ('number', '<i8')])

NamedTuple is a subclass of tuple, so it works as data input (e.g. list of tuples).

In [22]: arr1['colors']
Out[22]: 
array([{'violet', 'blue'}, {'blue', 'orange'}, {'blue', 'green'},
       {'red', 'orange'}], dtype=object)
In [23]: arr1['number']
Out[23]: array([4, 1, 3, 2])

Initializing an 1d array and assigning elements works:

In [30]: arr2 = np.empty(4, object)
In [31]: arr2[:] = L
In [32]: arr2
Out[32]: 
array([tuple(colors={'violet', 'blue'}, number=4),
       tuple(colors={'blue', 'orange'}, number=1),
       tuple(colors={'blue', 'green'}, number=3),
       tuple(colors={'red', 'orange'}, number=2)], dtype=object)

Filling a np.empty((2,2), object) will be a bit trickier.


I can construct an object array from the fields of the structured array (or any other pair of lists of inputs) with:

In [44]: np.frompyfunc(NamedTuple, 2,1)(arr1['colors'], arr1['number'])
Out[44]: 
array([tuple(colors={'violet', 'blue'}, number=4),
       tuple(colors={'blue', 'orange'}, number=1),
       tuple(colors={'blue', 'green'}, number=3),
       tuple(colors={'red', 'orange'}, number=2)], dtype=object)
hpaulj
  • 221,503
  • 14
  • 230
  • 353