@@ -173,6 +173,120 @@ def values(self) -> Generator[VT, None, None]: # type: ignore
173173 yield self ._value_cast (value )
174174
175175
176+ class UniqueList (types .List [VT ]):
177+ '''
178+ A list that only allows unique values. Duplicate values are ignored by
179+ default, but can be configured to raise an exception instead.
180+
181+ >>> l = UniqueList(1, 2, 3)
182+ >>> l.append(4)
183+ >>> l.append(4)
184+ >>> l.insert(0, 4)
185+ >>> l.insert(0, 5)
186+ >>> l[1] = 10
187+ >>> l
188+ [5, 10, 2, 3, 4]
189+
190+ >>> l = UniqueList(1, 2, 3, on_duplicate='raise')
191+ >>> l.append(4)
192+ >>> l.append(4)
193+ Traceback (most recent call last):
194+ ...
195+ ValueError: Duplicate value: 4
196+ >>> l.insert(0, 4)
197+ Traceback (most recent call last):
198+ ...
199+ ValueError: Duplicate value: 4
200+ >>> 4 in l
201+ True
202+ >>> l[0]
203+ 1
204+ >>> l[1] = 4
205+ Traceback (most recent call last):
206+ ...
207+ ValueError: Duplicate value: 4
208+ '''
209+
210+ _set : set [VT ]
211+
212+ def __init__ (
213+ self ,
214+ * args : VT ,
215+ on_duplicate : types .Literal ['raise' , 'ignore' ] = 'ignore' ,
216+ ):
217+ self .on_duplicate = on_duplicate
218+ self ._set = set ()
219+ super ().__init__ ()
220+ for arg in args :
221+ self .append (arg )
222+
223+ def insert (self , index : types .SupportsIndex , value : VT ) -> None :
224+ if value in self ._set :
225+ if self .on_duplicate == 'raise' :
226+ raise ValueError ('Duplicate value: %s' % value )
227+ else :
228+ return
229+
230+ self ._set .add (value )
231+ super ().insert (index , value )
232+
233+ def append (self , value : VT ) -> None :
234+ if value in self ._set :
235+ if self .on_duplicate == 'raise' :
236+ raise ValueError ('Duplicate value: %s' % value )
237+ else :
238+ return
239+
240+ self ._set .add (value )
241+ super ().append (value )
242+
243+ def __contains__ (self , item ):
244+ return item in self ._set
245+
246+ @types .overload
247+ def __setitem__ (self , indices : types .SupportsIndex , values : VT ) -> None :
248+ ...
249+
250+ @types .overload
251+ def __setitem__ (self , indices : slice , values : types .Iterable [VT ]) -> None :
252+ ...
253+
254+ def __setitem__ (self , indices , values ) -> None :
255+ if isinstance (indices , slice ):
256+ if self .on_duplicate == 'ignore' :
257+ raise RuntimeError (
258+ 'ignore mode while setting slices introduces ambiguous '
259+ 'behaviour and is therefore not supported'
260+ )
261+
262+ duplicates = set (values ) & self ._set
263+ if duplicates and values != self [indices ]:
264+ raise ValueError ('Duplicate values: %s' % duplicates )
265+
266+ self ._set .update (values )
267+ super ().__setitem__ (indices , values )
268+ else :
269+ if values in self ._set and values != self [indices ]:
270+ if self .on_duplicate == 'raise' :
271+ raise ValueError ('Duplicate value: %s' % values )
272+ else :
273+ return
274+
275+ self ._set .add (values )
276+ super ().__setitem__ (indices , values )
277+
278+ def __delitem__ (
279+ self , index : types .Union [types .SupportsIndex , slice ]
280+ ) -> None :
281+ if isinstance (index , slice ):
282+ for value in self [index ]:
283+ self ._set .remove (value )
284+ else :
285+ self ._set .remove (self [index ])
286+
287+ super ().__delitem__ (index )
288+
289+
176290if __name__ == '__main__' :
177291 import doctest
178292
0 commit comments