提高自定义表类中Python列表操作和比较的速度

0 投票
2 回答
597 浏览
提问于 2025-04-16 10:20

我正在使用以下这个类来创建一个表格,我需要找到一种方法,不仅让它运行得更快,还能让与它的交互更迅速:

class Table(object):
    """a three dimensional table object"""
    def __init__(self, xsize=1, ysize=1, zsize=1):
        self.xsize = xsize
        self.ysize = ysize
        self.zsize = zsize
        self.data = [0] * (xsize * ysize * zsize)

    def __getitem__(self, key):
        x, y, z = self.__extractIndices(key)
        return self.data[x + self.xsize * (y + self.ysize * z)]

    def __setitem__(self, key, value):
        x, y, z = self.__extractIndices(key)
        self.data[x + self.xsize * (y + self.ysize * z)] = value

    def __extractIndices(self, key):
        x = y = z = 0
        if (self.ysize > 1):
            if (self.zsize > 1):
                if len(key) != 3:
                    raise IndexError
                else:
                    x, y, z = key
            elif len(key) != 2:
                raise IndexError
            else:
                x, y = key
        elif not isinstance(key, int):
            raise IndexError
        else:
            x = key
        return (x, y, z)

    def resize(self, xsize=1, ysize=1, zsize=1):
        """resize the table preserving data"""
        oldlist = list(self.data)
        self.data = [0] * (xsize * ysize * zsize)
        self.xsize = xsize
        self.ysize = ysize
        self.zsize = zsize
        for i in range(0, oldlist):
            self.data[1] = oldlist[i]

在某个时刻,我需要检查两个列表中的数据是否与每个 z 值相等,所以我这样做了。self.dataself.map.data 是上面提到的表格类的实例。

    for x in range(self.map.width - 1):
        for y in range(self.map.height - 1):
            tempflag = False
            #layer 1
            if self.data[x, y, 0] != self.map.data[x, y, 0]:
                tempflag = True
                layer1flag = True
            #layer 2
            if self.data[x, y, 1] != self.map.data[x, y, 1]:
                tempflag = True
                layer2flag = True
            #layer 3
            if self.data[x, y, 2] != self.map.data[x, y, 2]:
                tempflag = True
                layer3flag = True
            #copy the data if it changed
            if tempflag:
                self.data = copy.deepcopy(self.map.data)
                previewflag = True

显然,这种方法是我能想到的最慢的方式,考虑到我比较的一些表格的大小是 200 * 200 * 3 = 120,000 条数据。我需要这个过程尽可能快。

我考虑过重写上面的比较方法,像这样切片所有一个 z 值的条目:

tempflag = False
#layer 1
slicepoint1 = 0
slicepoint2 = self.data.xsize * self.data.ysize * 1
data1 = self.data.data[slicepoint1:slicepoint2]
data2 = self.map.data.data[slicepoint1:slicepoint2]
if data1 != data2:
    tempflag = True
    layer1flag = True
#layer 2
slicepoint1 = self.data.xsize * self.data.ysize * 1
slicepoint2 = self.data.xsize * self.data.ysize * 2
data1 = self.data.data[slicepoint1:slicepoint2]
data2 = self.map.data.data[slicepoint1:slicepoint2]
if data1 != data2:
    tempflag = True
    layer2flag = True
#layer 3
slicepoint1 = self.data.xsize * self.data.ysize * 2
slicepoint2 = self.data.xsize * self.data.ysize * 3
data1 = self.data.data[slicepoint1:slicepoint2]
data2 = self.map.data.data[slicepoint1:slicepoint2]
if data1 != data2:
    tempflag = True
    layer3flag = True
#copy the data if it changed
if tempflag:
    self.data = copy.deepcopy(self.map.data)
    previewflag = True

虽然这样看起来会快一些,但我觉得仍然可以大幅提升速度。例如,我能不能使用 numpy 来构建表格类内部的数据列表呢?

我需要这个类和这个检查尽可能快地运行。

如果使用 numpy 能让我快速遍历表格,这样我就可以利用里面的数据进行 blit 操作来构建瓦片地图,那就太好了。

确实需要保持表格类的一般接口,特别是表格数据存储在 self.data 这个事实。

总结一下 使用 numpy 能否提高操作的速度?如果可以,我该怎么做呢?

2 个回答

2

我觉得可以,使用numpy的话,你可能会获得很大的速度提升。

不仅可以进行切片操作,还可以做矩形切片,甚至可能做立方体切片,举个例子:

>>> a = numpy.array([[1,2,3],[4,5,6],[7,8,9]])
>>> a[:2,1:]
array([[2, 3],
       [5, 6]])

我不太确定你想要实现什么,但你也可以很简单地逐个比较numpy数组中的元素:

>>> numpy.array([1,2,3])==numpy.array([9,2,3])
array([False,  True,  True], dtype=bool)

如果你还有更多问题,随时可以留言。

1

这绝对是使用NumPy的好机会!它不仅能让你的代码运行得更快,还能大大简化你的代码,因为NumPy已经帮你处理了索引和比较的部分。你需要看一些教程来学习NumPy——这里给你一些小提示,帮助你入门。

通常,我会直接从numpy.ndarray这个类派生出一个自定义的数组类,但你提到你确实需要data这个属性,而这和numpy.ndarray.data是冲突的。你的类可以简化为

class Table(object):
    def __init__(self, xsize=1, ysize=1, zsize=1):
        self.data = numpy.zeros((xsize, ysize, zsize))

    def __getitem__(self, key):
        return self.data[key]

    def __setitem__(self, key, value):
        self.data[key] = value

    def resize(self, xsize=1, ysize=1, zsize=1):
        # This only works for increasing the size of the data,
        # but is easy do adapt to other cases
        newdata = numpy.zeros((xsize, ysize, zsize))
        shape = self.data.shape
        newdata[:shape[0], :shape[1], :shape[2]] = self.data
        self.data = newdata

你的比较代码也可以简化为

eq = self.data == self.data.map
layerflags = eq.reshape(-1, 3).any(axis=0)
if layerflags.any():
    self.data[:] = self.map.data

而且它会快很多

撰写回答