共享一些元素的数组的数据结构 - Python



我有一个数组集合,它们在某些元素上"重叠"。下面是一个包含 3 个字符数组的示例的图片:

array0↓
'A'      ↓array2
array1→'B' 'D' 'E'
'C'     'F'

重要的是,对数组的更改应遵循此结构。例如,如果我将 array0 中的"B"更改为"X",则 array1 中的"B"也应更改为"X"。

我的问题是,在 Python 中实现这一点的好、有效的方法是什么?

到目前为止,我想到了两件事:

首先,我可以创建一个定制的类,其实例包含一个完全不同的列表,以及有关它所具有的任何重叠的信息,并适当地实现更新方法,以便对列表的任何更改始终在重叠处为其他列表重复。不过,这似乎有点过度紧张,并且涉及复制数据。

第二,我可以通过使用这样的单例列表来做到这一点:

data = [['A'], ['B'], ['C'], ['D'], ['E'], ['F']]
array0 = [data[0], data[1], data[2]]
array1 = [data[1], data[3], data[4]]
array2 = [data[4], data[5]]
for array in array0, array1, array2:
print(array)
>>> [['A'], ['B'], ['C']]
>>> [['B'], ['D'], ['E']]
>>> [['E'], ['F']]
array0[1][0] = 'X'
for array in array0, array1, array2:
print(array)
>>> [['A'], ['X'], ['C']]
>>> [['X'], ['D'], ['E']]
>>> [['E'], ['F']]

但我觉得这可能是笨拙的,不是最好的方法。感谢您的任何建议。

我的建议是@a_guest提出的建议的变体。你可以有一个将元素标记为共享的包装类和一个用于处理这些元素的数据结构:

class SharedElement:
def __init__(self, val):
self.val = val
def update(self, val):
self.val = val
def __repr__(self):
return "SharedElement({0})".format(self.val)
def __str__(self):
return str(self.val)

class SharedList:
def __init__(self, lst):
self._lst = lst
def __getitem__(self, item):
if isinstance(self._lst[item], SharedElement):
return self._lst[item].val
return self._lst[item]
def __setitem__(self, key, value):
if isinstance(self._lst[key], SharedElement):
self._lst[key].update(value)

B = SharedElement('B')
E = SharedElement('E')
a = SharedList(['A', B, 'C'])
b = SharedList([B, 'D', E])
c = SharedList([E, 'F'])
b[0] = 'X'
print([val for val in a])
print([val for val in b])
print([val for val in c])

输出

['A', 'X', 'C']
['X', 'D', 'E']
['E', 'F']

您可以创建一个包装类,该类可以处理相同值的所有元素的更新:

arr = [[['A'], ['B'], ['C']], [['B'], ['D'], ['E']], [['E'], ['F']]]
class WrapArray:
def __init__(self, _data):
self.d = _data
def __getitem__(self, _x):
self.x = _x
class _wrapper:
def __init__(self, _inst):
self.ref = _inst
def __setitem__(self, _y, _val):
_place = self.ref.d[self.ref.x][_y][0]
self.ref.d[self.ref.x][_y][0] = _val
for i in range(len(self.ref.d)):
for b in range(len(self.ref.d[i])):
if self.ref.d[i][b][0] == _place:
self.ref.d[i][b] = [_val]
return _wrapper(self)
def __repr__(self):
return str(self.d)
array = WrapArray(arr)
array[1][0] = 'X'

输出:

[[['A'], ['X'], ['C']], [['X'], ['D'], ['E']], [['E'], ['F']]]

您可以子类list并使用专用的包装类来代理共享内容。这不涉及数据重复,因为它只存储调度到原始数据的共享数据的代理。它有点类似于您的嵌套列表方法,但它保持了正常的列表界面。下面是一个实现示例:

class Intersection:
def __init__(self, other, index):
self.other = other
self.index = index
def __repr__(self):
return repr(self.other[self.index])
@property
def value(self):
return self.other[self.index]
@value.setter
def value(self, v):
self.other[self.index] = v

class List(list):
def __getitem__(self, index):
item = super().__getitem__(index)
return item.value if isinstance(item, Intersection) else item
def __setitem__(self, index, value):
item = super().__getitem__(index)
if isinstance(item, Intersection):
item.value = value
else:
super().__setitem__(index, value)
def share(self, index):
return Intersection(self, index)

现在,您可以根据需要在列表之间共享数据:

a = List(['A', 'B', 'C'])
b = List([a.share(1), 'D', 'E'])
c = List([b.share(2), 'F'])
a[1] = 'X'
b[2] = 'Y'
print(a)
print(b)
print(c)

作为输出给出:

['A', 'X', 'C']
['X', 'D', 'Y']
['Y', 'F']

您可以使用专用类来适当地更新其他相交实例,正如您在第一个想法中指出的那样。我不认为数据重复是一个问题,因为对于可变数据,无论如何你都会存储引用,如果你使用大型不可变数据,你可以使用专用的包装类(例如 Python 3.7 引入了@dataclass装饰器(。

下面是一个实现示例:

from collections import defaultdict
class List(list):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._intersections = defaultdict(list)
def __setitem__(self, index, value):
super().__setitem__(index, value)
for i, other in self._intersections[index]:
other[i] = value
def intersect(self, other, at):
self._intersections[at[0]].append((at[1], other))

有了它,您可以像您的示例一样与列表相交:

a = List(['A', 'B', 'C'])
b = List(['B', 'D', 'E'])
c = List(['E', 'F'])
a.intersect(b, (1, 0))
b.intersect(c, (2, 0))
a[1] = 'X'
b[2] = 'Y'
print(a)
print(b)
print(c)

作为输出给出:

['A', 'X', 'C']
['X', 'D', 'Y']
['Y', 'F']

正如您在问题中指出的,相关信息是

array0ptr = [0, 1, 2]
array1ptr = [1, 3, 4]
array2ptr = [4, 5]

(我添加后缀 ptr,因为实际上这些元素是指针(。 这里的列表元素是指向应维护的对象的指针 在单独的列表中

ol = ['A', 'B', 'C', 'D', 'E']

实数组可以在运行时通过成员函数获得,例如

array0 = []
for i in range(len(array0ptr)):
array0.append(ol[array0ptr[i]])

现在你的观点是:假设对象列表变成

ol = ['A', 'B', 'intruder', 'C', 'D', 'E']

如何在我的数组中自动跟踪它?这些数组应变为:

array0ptr = [0, 1, 3]
array1ptr = [1, 4, 5]
array2ptr = [5, 6]

我认为最简单的答案是:保持列表固定!,以及 不允许插入或更改项目的顺序。维护简单 具有对象位置的不同哈希。在上述情况下,您将拥有

sl = ['A', 'B', 'C', 'D', 'E', 'intruder']
slorder = [0, 1, 3, 4, 5, 2]

然后可以编写成员函数来转储更新的对象列表, 阵列不会更改。如果你想删除对象,可能会很棘手,但这在任何情况下我都担心很棘手。

最新更新