J'ai une collection de tableaux qui "se chevauchent" à certains éléments. Voici une image d'un exemple impliquant 3 tableaux de caractères:
array0↓
'A' ↓array2
array1→'B' 'D' 'E'
'C' 'F'
L'important est que les modifications apportées aux tableaux doivent respecter cette structure. Ainsi, par exemple, si je change le "B" dans array0 en "X", le "B" dans array1 devrait également changer en "X".
Ma question est la suivante: quel est le bon moyen d’implémenter cela en Python?
J'ai pensé jusqu'à présent à deux choses:
Premièrement, je peux créer une classe sur mesure, dont les instances contiennent une liste complètement distincte, ainsi que des informations sur ses chevauchements éventuels, et implémenter les méthodes de mise à jour de manière à ce que toute modification apportée à la liste soit toujours dupliquée pour les autres listes. . Cela semble toutefois un peu exagéré et implique la duplication des données.
Deuxièmement, je pourrais le faire en utilisant des listes de singleton comme ceci:
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']]
Mais je pense que c'est peut-être un hacky et pas le meilleur moyen. Merci pour toutes les suggestions.
La suggestion de mine est une variante de celle proposée par @a_guest. Vous pouvez avoir une classe wrapper qui marque les éléments comme partagés et une structure de données pour gérer ces éléments:
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])
Sortie
['A', 'X', 'C']
['X', 'D', 'E']
['E', 'F']
Vous pouvez utiliser une classe dédiée qui met à jour les autres instances en intersection de manière appropriée, comme vous l'avez indiqué avec votre première idée. Je ne considérerais pas la duplication de données comme un problème car pour les données mutables, vous stockez de toute façon les références et, si vous utilisez de grandes données immuables, vous pouvez utiliser une classe de wrapper dédiée (par exemple, Python 3.7 a introduit le @dataclass
décorateur).
Voici un exemple d'implémentation:
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))
Avec cela, vous pouvez intersecter les listes comme dans votre exemple:
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)
Ce qui donne en sortie:
['A', 'X', 'C']
['X', 'D', 'Y']
['Y', 'F']
Vous pouvez créer une classe wrapper pouvant gérer la mise à jour de tous les éléments de la même valeur:
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'
Sortie:
[[['A'], ['X'], ['C']], [['X'], ['D'], ['E']], [['E'], ['F']]]
Vous pouvez sous-classer list
et utiliser une classe d'encapsuleur dédiée pour proxy le contenu partagé. Cela n'implique aucune duplication des données, car il stocke uniquement le proxy pour les données partagées qui sont envoyées aux données d'origine. C'est un peu similaire à votre approche de liste imbriquée mais elle maintient l'interface de liste normale. Voici un exemple d'implémentation:
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)
Vous pouvez maintenant partager les données entre vos listes selon vos besoins:
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)
Ce qui donne en sortie:
['A', 'X', 'C']
['X', 'D', 'Y']
['Y', 'F']
Comme vous l'avez souligné dans votre question, l'information pertinente est que
array0ptr = [0, 1, 2]
array1ptr = [1, 3, 4]
array2ptr = [4, 5]
(J'ajoute le suffixe ptr car ces éléments sont pratiquement des pointeurs) . Ici, les éléments de la liste sont le pointeur sur les objets à conserver .__ dans une liste séparée
ol = ['A', 'B', 'C', 'D', 'E']
Les tableaux réels peuvent être obtenus au moment de l’exécution par des fonctions membres telles que
array0 = []
for i in range(len(array0ptr)):
array0.append(ol[array0ptr[i]])
Maintenant, votre point est le suivant: supposons que la liste d'objets devienne
ol = ['A', 'B', 'intruder', 'C', 'D', 'E']
Comment est-ce que je garde automagiquement ceci dans mes tableaux? Ces tableaux devraient devenir:
array0ptr = [0, 1, 3]
array1ptr = [1, 4, 5]
array2ptr = [5, 6]
Je pense que la réponse la plus simple est la suivante: maintenez la liste corrigée !, et..d.d. ne permettez pas l’insertion ou la modification de l’ordre des éléments. Il suffit de garder Un hachage différent avec la position de l’objet. Dans le cas ci-dessus, vous aurez
sl = ['A', 'B', 'C', 'D', 'E', 'intruder']
slorder = [0, 1, 3, 4, 5, 2]
il est alors possible d'écrire des fonctions membres qui vident la liste d'objets mise à jour, le tableau ne changerait pas. Ce qui peut être délicat, c’est si vous voulez supprimer des objets, mais c’est délicat en tout cas, je le crains.