web-dev-qa-db-fra.com

Structure de données pour les tableaux qui partagent certains éléments - Python

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.

14
Denziloe

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']
2
Daniel Mesejo

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']
1
a_guest

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']]]
1
Ajax1234

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']
1
a_guest

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.

0
Lorenzo