11# Copyright 2009-2015 Ram Rachum.
22# This program is distributed under the MIT license.
33
4- '''
5- Defines the `OrderedSet` class.
6-
7- See its documentation for more details.
8- '''
9- # todo: revamp
10-
114import collections
125
136from python_toolbox import comparison_tools
147
15-
168KEY , PREV , NEXT = range (3 )
179
1810
19- class OrderedSet (collections .MutableSet , collections .Sequence ):
20- '''
21- A set with an order.
22-
23- You can also think of this as a list which doesn't allow duplicate items
24- and whose `__contains__` method is O(1).
11+ class BaseOrderedSet (collections .Set , collections .Sequence ):
2512 '''
13+ Base class for `OrderedSet` and `FrozenOrderedSet`, i.e. set with an order.
2614
15+ This behaves like a `set` except items have an order. (By default they're
16+ ordered by insertion order, but that order can be changed.)
17+ '''
18+
2719 def __init__ (self , iterable = None ):
2820 self .clear ()
2921 if iterable is not None :
3022 self |= iterable
3123
32-
33- def clear (self ):
34- self ._end = []
35- self ._end += [None , self ._end , self ._end ]
36- self ._map = {}
37-
38-
3924 def __getitem__ (self , index ):
4025 for i , item in enumerate (self ):
4126 if i == index :
4227 return item
4328 else :
4429 raise IndexError
4530
46-
4731 def __len__ (self ):
4832 return len (self ._map )
4933
5034 def __contains__ (self , key ):
5135 return key in self ._map
5236
5337 def add (self , key ):
54- """
38+ '''
5539 Add an element to a set.
5640
5741 This has no effect if the element is already present.
58- """
42+ '''
5943 if key not in self ._map :
6044 end = self ._end
6145 curr = end [PREV ]
@@ -86,28 +70,36 @@ def __reversed__(self):
8670 yield curr [KEY ]
8771 curr = curr [PREV ]
8872
89- def pop (self , last = True ):
90- """Remove and return an arbitrary set element."""
91- if not self :
92- raise KeyError ('set is empty' )
93- key = next (reversed (self ) if last else iter (self ))
94- self .discard (key )
95- return key
96-
9773 def __repr__ (self ):
9874 if not self :
9975 return '%s()' % (self .__class__ .__name__ ,)
10076 return '%s(%r)' % (self .__class__ .__name__ , list (self ))
10177
10278 def __eq__ (self , other ):
103- if isinstance (other , OrderedSet ):
104- return len (self ) == len (other ) and list (self ) == list (other )
105- return set (self ) == set (other )
79+ return (
80+ isinstance (other , collections .Set ) and
81+ isinstance (other , collections .Sequence ) and
82+ len (self ) == len (other ) and
83+ tuple (self ) == tuple (other )
84+ )
85+
86+
87+ class FrozenOrderedSet (BaseOrderedSet ):
88+ '''
89+ A `frozenset` with an order.
90+
91+ This behaves like a `frozenset` (i.e. a set that can't be changed after
92+ creation) except items have an order. (By default they're ordered by
93+ insertion order, but that order can be changed.)
94+ '''
95+
96+ class OrderedSet (BaseOrderedSet , collections .MutableSet ):
97+ '''
98+ A `set` with an order.
10699
107- def __del__ (self ):
108- self .clear () # remove circular references
109- # todo: is this really needed? i'm worried about this making the gc not
110- # drop circulary-referencing objects.
100+ This behaves like a `set` except items have an order. (By default they're
101+ ordered by insertion order, but that order can be changed.)
102+ '''
111103
112104 def move_to_end (self , key ):
113105 '''
@@ -137,5 +129,83 @@ def sort(self, key=None, reverse=False):
137129
138130 self .clear ()
139131 self |= sorted_members
132+
133+ def clear (self ):
134+ self ._end = []
135+ self ._end += [None , self ._end , self ._end ]
136+ self ._map = {}
140137
141-
138+
139+ def add (self , key ):
140+ '''
141+ Add an element to a set.
142+
143+ This has no effect if the element is already present.
144+ '''
145+ if key not in self ._map :
146+ end = self ._end
147+ curr = end [PREV ]
148+ curr [NEXT ] = end [PREV ] = self ._map [key ] = [key , curr , end ]
149+
150+ def discard (self , key ):
151+ """
152+ Remove an element from a set if it is a member.
153+
154+ If the element is not a member, do nothing.
155+ """
156+ if key in self ._map :
157+ key , prev , next = self ._map .pop (key )
158+ prev [NEXT ] = next
159+ next [PREV ] = prev
160+
161+ def pop (self , last = True ):
162+ '''Remove and return an arbitrary set element.'''
163+ if not self :
164+ raise KeyError ('set is empty' )
165+ key = next (reversed (self ) if last else iter (self ))
166+ self .discard (key )
167+ return key
168+
169+
170+ class EmittingOrderedSet (OrderedSet ):
171+ '''An ordered set that emits to `.emitter` every time it's modified.'''
172+
173+ def __init__ (self , emitter , items = ()):
174+ if emitter :
175+ from python_toolbox .emitting import Emitter
176+ assert isinstance (emitter , Emitter )
177+ self .emitter = emitter
178+ OrderedSet .__init__ (self , items )
179+
180+
181+ def add (self , key ):
182+ '''
183+ Add an element to a set.
184+
185+ This has no effect if the element is already present.
186+ '''
187+ if key not in self ._map :
188+ end = self ._end
189+ curr = end [PREV ]
190+ curr [NEXT ] = end [PREV ] = self ._map [key ] = [key , curr , end ]
191+ if self .emitter :
192+ self .emitter .emit ()
193+
194+
195+ def discard (self , key ):
196+ '''
197+ Remove an element from a set if it is a member.
198+
199+ If the element is not a member, do nothing.
200+ '''
201+ if key in self ._map :
202+ key , prev , next = self ._map .pop (key )
203+ prev [NEXT ] = next
204+ next [PREV ] = prev
205+ if self .emitter :
206+ self .emitter .emit ()
207+
208+
209+ def set_emitter (self , emitter ):
210+ '''Set `emitter` to be emitted with on every modification.'''
211+ self .emitter = emitter
0 commit comments