Constructing a Python set from a Numpy matrix

Viewed 42773

I'm trying to execute the following

>> from numpy import *
>> x = array([[3,2,3],[4,4,4]])
>> y = set(x)
TypeError: unhashable type: 'numpy.ndarray'

How can I easily and efficiently create a set with all the elements from the Numpy array?

6 Answers

If you want a set of the elements, here is another, probably faster way:

y = set(x.flatten())

PS: after performing comparisons between x.flat, x.flatten(), and x.ravel() on a 10x100 array, I found out that they all perform at about the same speed. For a 3x3 array, the fastest version is the iterator version:

y = set(x.flat)

which I would recommend because it is the less memory expensive version (it scales up well with the size of the array).

PPS: There is also a NumPy function that does something similar:

y = numpy.unique(x)

This does produce a NumPy array with the same element as set(x.flat), but as a NumPy array. This is very fast (almost 10 times faster), but if you need a set, then doing set(numpy.unique(x)) is a bit slower than the other procedures (building a set comes with a large overhead).

The immutable counterpart to an array is the tuple, hence, try convert the array of arrays into an array of tuples:

>> from numpy import *
>> x = array([[3,2,3],[4,4,4]])

>> x_hashable = map(tuple, x)

>> y = set(x_hashable)
set([(3, 2, 3), (4, 4, 4)])

If you want a set of the elements:

>> y = set(e for r in x
             for e in r)
set([2, 3, 4])

For a set of the rows:

>> y = set(tuple(r) for r in x)
set([(3, 2, 3), (4, 4, 4)])

I liked xperroni's idea. But I think implementation can be simplified using direct inheritance from ndarray instead of wrapping it.

from hashlib import sha1
from numpy import ndarray, array

class HashableNdarray(ndarray):
    @classmethod
    def create(cls, array):
        return HashableNdarray(shape=array.shape, dtype=array.dtype, buffer=array.copy())

    def __hash__(self):
        if not hasattr(self, '_HashableNdarray__hash'):
            self.__hash = int(sha1(self.view()).hexdigest(), 16)
        return self.__hash

    def __eq__(self, other):
        if not isinstance(other, HashableNdarray):
            return super().__eq__(other)
        return super().__eq__(super(HashableNdarray, other)).all()

NumPy ndarray can be viewed as derived class and used as hashable object. view(ndarray) can be used for back transformation, but it is not even needed in most cases.

>>> a = array([1,2,3])
>>> b = array([2,3,4])
>>> c = array([1,2,3])
>>> s = set()

>>> s.add(a.view(HashableNdarray))
>>> s.add(b.view(HashableNdarray))
>>> s.add(c.view(HashableNdarray))
>>> print(s)
{HashableNdarray([2, 3, 4]), HashableNdarray([1, 2, 3])}
>>> d = next(iter(s))
>>> print(d == a)
[False False False]
>>> import ctypes
>>> print(d.ctypes.data_as(ctypes.POINTER(ctypes.c_double)))
<__main__.LP_c_double object at 0x7f99f4dbe488>

Adding to @Eric Lebigot and his great post.

The following did the trick for building a tensor lookup table:

a = np.array([[1, 0, 0], [1, 0, 0], [2, 3, 4]])
np.unique(a, axis=0)

output:

array([[1, 0, 0], [2, 3, 4]])

np.unique documentation

Related