Indeksowalny słaby uporządkowany zestaw w Pythonie

Zastanawiałem się, czy jest łatwy sposób na zbudowanie indeksowalnego słabego uporządkowanego zestawu w Pythonie. Sam próbowałem go zbudować. Oto co wymyśliłem:

"""
An indexable, ordered set of objects, which are held by weak reference.
"""
from nose.tools import *
import blist
import weakref


class WeakOrderedSet(blist.weaksortedset):
    """
    A blist.weaksortedset whose key is the insertion order.
    """
    def __init__(self, iterable=()):
        self.insertion_order = weakref.WeakKeyDictionary()  # value_type to int
        self.last_key = 0
        super().__init__(key=self.insertion_order.__getitem__)
        for item in iterable:
            self.add(item)

    def __delitem__(self, index):
        values = super().__getitem__(index)
        super().__delitem__(index)
        if not isinstance(index, slice):
            # values is just one element
            values = [values]
        for value in values:
            if value not in self:
                del self.insertion_order[value]

    def add(self, value):
        # Choose a key so that value is on the end.
        if value not in self.insertion_order:
            key = self.last_key
            self.last_key += 1
            self.insertion_order[value] = key
        super().add(value)

    def discard(self, value):
        super().discard(value)
        if value not in self:
            del self.insertion_order[value]

    def remove(self, value):
        super().remove(value)
        if value not in self:
            del self.insertion_order[value]

    def pop(self, *args, **kwargs):
        value = super().pop(*args, **kwargs)
        if value not in self:
            del self.insertion_order[value]

    def clear(self):
        super().clear()
        self.insertion_order.clear()

    def update(self, *args):
        for arg in args:
            for item in arg:
                self.add(item)


if __name__ == '__main__':
    class Dummy:
        def __init__(self, value):
            self.value = value

    x = [Dummy(i) for i in range(10)]
    w = WeakOrderedSet(reversed(x))
    del w[2:8]
    assert_equals([9,8,1,0], [i.value for i in w])
    del w[0]
    assert_equals([8,1,0], [i.value for i in w])
    del x
    assert_equals([], [i.value for i in w])
Czy jest na to łatwiejszy sposób?
Author: Raymond Hettinger, 2011-10-19

2 answers

Najprostszym sposobem jest wykorzystanie istniejących komponentów w bibliotece standardowej.

OrderedDict i MutableSet ABC ułatwiają pisanie zamówionego zestawu.

Podobnie, możesz ponownie użyć istniejącego weakref.WeakSet i zastąp jego podstawową metodę set () zestawem OrderedSet.

Indeksowanie jest trudniejsze do osiągnięcia-w ten najprostszy sposób można go przekonwertować na Listę w razie potrzeby. Jest to konieczne, ponieważ zestawy i dykty są wewnętrznie rzadkie.

import collections.abc
import weakref

class OrderedSet(collections.abc.MutableSet):
    def __init__(self, values=()):
        self._od = collections.OrderedDict().fromkeys(values)
    def __len__(self):
        return len(self._od)
    def __iter__(self):
        return iter(self._od)
    def __contains__(self, value):
        return value in self._od
    def add(self, value):
        self._od[value] = None
    def discard(self, value):
        self._od.pop(value, None)

class OrderedWeakrefSet(weakref.WeakSet):
    def __init__(self, values=()):
        super(OrderedWeakrefSet, self).__init__()
        self.data = OrderedSet()
        for elem in values:
            self.add(elem)

Użyj go tak:

>>> names = OrderedSet(['Alice', 'Bob', 'Carol', 'Bob', 'Dave', 'Edna'])
>>> len(names)
5
>>> 'Bob' in names
True
>>> s = list(names)
>>> s[2]
'Carol'
>>> s[4]
'Edna'

Uwaga począwszy od Pythona 3.7, regularne dicty są gwarantowane do uporządkowania, więc można zastąpić dict dla OrderedDict w tym przepisie i wszystko będzie działać dobrze: -)

 27
Author: Raymond Hettinger,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-02-09 03:54:45

Raymond ma świetną i zwięzłą odpowiedź, jak zwykle, ale właściwie przyjechałem tu jakiś czas temu zainteresowany częścią indeksowalną, bardziej niż częścią weakref. W końcu zbudowałem własną odpowiedź, która stała się typem IndexedSet w bibliotece narzędzi boltonów . Zasadniczo, to wszystkie najlepsze części API list i set, połączone.

>>> x = IndexedSet(list(range(4)) + list(range(8)))
>>> x
IndexedSet([0, 1, 2, 3, 4, 5, 6, 7])
>>> x - set(range(2))
IndexedSet([2, 3, 4, 5, 6, 7])
>>> x[-1]
7
>>> fcr = IndexedSet('freecreditreport.com')
>>> ''.join(fcr[:fcr.index('.')])
'frecditpo'

Jeśli część weakref jest krytyczna, możesz prawdopodobnie dodać ją poprzez dziedziczenie lub bezpośrednią modyfikację kopii kodu (moduł jest samodzielny, pure-Python i kompatybilne z 2/3).

 1
Author: Mahmoud Hashemi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-02-07 20:49:33