@@ -247,6 +247,19 @@ impl PySetInner {
247247 Ok ( result)
248248 }
249249
250+ fn difference_multi (
251+ & self ,
252+ mut others : impl core:: iter:: Iterator < Item = ArgIterable > ,
253+ vm : & VirtualMachine ,
254+ ) -> PyResult < Self > {
255+ let Some ( other) = others. next ( ) else {
256+ return Ok ( self . copy ( ) ) ;
257+ } ;
258+ let result = self . difference_new ( other, vm) ?;
259+ result. difference_update ( others, vm) ?;
260+ Ok ( result)
261+ }
262+
250263 fn len ( & self ) -> usize {
251264 self . content . len ( )
252265 }
@@ -315,28 +328,10 @@ impl PySetInner {
315328 }
316329
317330 pub ( super ) fn intersection ( & self , other : ArgIterable , vm : & VirtualMachine ) -> PyResult < Self > {
318- let ( target, elements) = if let Some ( other_set) = extract_set ( other. as_object ( ) ) {
319- if core:: ptr:: eq ( self , other_set) {
320- return Ok ( self . copy ( ) ) ;
321- }
322- let ( target, source) = if self . len ( ) < other_set. len ( ) {
323- ( other_set, self )
324- } else {
325- ( self , other_set)
326- } ;
327- ( target, Some ( source. content . keys_with_hashes ( ) ) )
328- } else {
329- ( self , None )
330- } ;
331- let set = Self :: default ( ) ;
332- if let Some ( elements) = elements {
333- for ( obj, hash) in elements {
334- if target. contains_known_hash ( & obj, hash, vm) ? {
335- set. add_known_hash ( & obj, hash, vm) ?;
336- }
337- }
338- return Ok ( set) ;
331+ if let Some ( other_set) = extract_set ( other. as_object ( ) ) {
332+ return self . intersection_set ( other_set, vm) ;
339333 }
334+ let set = Self :: default ( ) ;
340335 for item in other. iter ( vm) ? {
341336 let obj = item?;
342337 let hash = obj. hash ( vm) ?;
@@ -350,20 +345,63 @@ impl PySetInner {
350345 Ok ( set)
351346 }
352347
353- pub ( super ) fn difference ( & self , other : ArgIterable , vm : & VirtualMachine ) -> PyResult < Self > {
354- let set = self . clone ( ) ;
355- if let Some ( elements) = Self :: cached_hashes ( other. as_object ( ) , vm) {
356- for ( item, hash) in elements {
357- set. content . delete_if_exists_known_hash ( vm, & * item, hash) ?;
358- }
359- return Ok ( set) ;
348+ fn intersection_set ( & self , other : & Self , vm : & VirtualMachine ) -> PyResult < Self > {
349+ if PyRc :: ptr_eq ( & self . content , & other. content ) {
350+ return Ok ( self . copy ( ) ) ;
360351 }
361- for item in other. iter ( vm) ? {
362- set. content . delete_if_exists ( vm, & * item?) ?;
352+ let ( target, source) = if self . len ( ) < other. len ( ) {
353+ ( other, self )
354+ } else {
355+ ( self , other)
356+ } ;
357+ let set = Self :: default ( ) ;
358+ for ( obj, hash) in source. content . keys_with_hashes ( ) {
359+ if target. contains_known_hash ( & obj, hash, vm) ? {
360+ set. add_known_hash ( & obj, hash, vm) ?;
361+ }
363362 }
364363 Ok ( set)
365364 }
366365
366+ fn difference_new ( & self , other : ArgIterable , vm : & VirtualMachine ) -> PyResult < Self > {
367+ // Scanning the left side avoids visiting and retaining a much larger
368+ // exclusion collection. Match CPython's comparison direction as well.
369+ if let Some ( other_set) = extract_set ( other. as_object ( ) ) {
370+ if self . len ( ) >> 2 <= other_set. len ( ) {
371+ return self
372+ . difference_by ( |key, hash| other_set. contains_known_hash ( key, hash, vm) , vm) ;
373+ }
374+ } else if let Some ( dict) = other. as_object ( ) . downcast_ref_if_exact :: < PyDict > ( vm)
375+ && self . len ( ) >> 2 <= dict. _as_dict_inner ( ) . len ( )
376+ {
377+ return self . difference_by (
378+ |key, hash| dict. _as_dict_inner ( ) . contains_known_hash ( vm, key, hash) ,
379+ vm,
380+ ) ;
381+ }
382+ self . copy ( ) . difference ( other, vm)
383+ }
384+
385+ // The dict-view caller supplies a private working table.
386+ pub ( super ) fn difference ( & self , other : ArgIterable , vm : & VirtualMachine ) -> PyResult < Self > {
387+ self . difference_update ( core:: iter:: once ( other) , vm) ?;
388+ Ok ( self . clone ( ) )
389+ }
390+
391+ fn difference_by (
392+ & self ,
393+ contains : impl Fn ( & PyObject , PyHash ) -> PyResult < bool > ,
394+ vm : & VirtualMachine ,
395+ ) -> PyResult < Self > {
396+ let result = Self :: default ( ) ;
397+ for ( key, hash) in self . content . keys_with_hashes ( ) {
398+ if !contains ( & key, hash) ? {
399+ result. add_known_hash ( & key, hash, vm) ?;
400+ }
401+ }
402+ Ok ( result)
403+ }
404+
367405 pub ( super ) fn symmetric_difference (
368406 & self ,
369407 other : ArgIterable ,
@@ -530,10 +568,8 @@ impl PySetInner {
530568 vm : & VirtualMachine ,
531569 ) -> PyResult < ( ) > {
532570 let temp_inner = self . intersection_multi ( others, vm) ?;
533- self . clear ( ) ;
534- for ( obj, hash) in temp_inner. content . keys_with_hashes ( ) {
535- self . add_known_hash ( & obj, hash, vm) ?;
536- }
571+ let content = PyRc :: try_unwrap ( temp_inner. content ) . unwrap_or_else ( |table| ( * table) . clone ( ) ) ;
572+ self . content . replace_contents ( content) ;
537573 Ok ( ( ) )
538574 }
539575
@@ -543,7 +579,24 @@ impl PySetInner {
543579 vm : & VirtualMachine ,
544580 ) -> PyResult < ( ) > {
545581 for iterable in others {
546- if let Some ( elements) = Self :: cached_hashes ( iterable. as_object ( ) , vm) {
582+ let elements = if let Some ( other_set) = extract_set ( iterable. as_object ( ) ) {
583+ if PyRc :: ptr_eq ( & self . content , & other_set. content ) {
584+ self . clear ( ) ;
585+ continue ;
586+ }
587+ // Build the intersection first: besides bounding the work by
588+ // our size, this preserves CPython's equality/error ordering.
589+ Some ( if other_set. len ( ) >> 3 > self . len ( ) {
590+ self . intersection_set ( other_set, vm) ?
591+ . content
592+ . keys_with_hashes ( )
593+ } else {
594+ other_set. content . keys_with_hashes ( )
595+ } )
596+ } else {
597+ Self :: cached_hashes ( iterable. as_object ( ) , vm)
598+ } ;
599+ if let Some ( elements) = elements {
547600 for ( item, hash) in elements {
548601 self . content . delete_if_exists_known_hash ( vm, & * item, hash) ?;
549602 }
@@ -748,7 +801,9 @@ impl PySet {
748801 others : PosArgs < ArgIterable , NameOthers > ,
749802 vm : & VirtualMachine ,
750803 ) -> PyResult < Self > {
751- self . fold_op ( others. into_iter ( ) , PySetInner :: difference, vm)
804+ Ok ( Self {
805+ inner : self . inner . difference_multi ( others. into_iter ( ) , vm) ?,
806+ } )
752807 }
753808
754809 #[ pymethod]
@@ -807,11 +862,9 @@ impl PySet {
807862 vm : & VirtualMachine ,
808863 ) -> PyResult < PyArithmeticValue < Self > > {
809864 if let Ok ( other) = AnySet :: try_from_object ( vm, other) {
810- Ok ( PyArithmeticValue :: Implemented ( self . op (
811- other,
812- PySetInner :: difference,
813- vm,
814- ) ?) )
865+ Ok ( PyArithmeticValue :: Implemented ( Self {
866+ inner : self . inner . difference_new ( other. into_iterable ( vm) ?, vm) ?,
867+ } ) )
815868 } else {
816869 Ok ( PyArithmeticValue :: NotImplemented )
817870 }
@@ -826,8 +879,7 @@ impl PySet {
826879 Ok ( PyArithmeticValue :: Implemented ( Self {
827880 inner : other
828881 . as_inner ( )
829- . copy ( )
830- . difference ( ArgIterable :: try_from_object ( vm, zelf. into ( ) ) ?, vm) ?,
882+ . difference_new ( ArgIterable :: try_from_object ( vm, zelf. into ( ) ) ?, vm) ?,
831883 } ) )
832884 } else {
833885 Ok ( PyArithmeticValue :: NotImplemented )
@@ -1277,7 +1329,10 @@ impl PyFrozenSet {
12771329 others : PosArgs < ArgIterable , NameOthers > ,
12781330 vm : & VirtualMachine ,
12791331 ) -> PyResult < Self > {
1280- self . fold_op ( others. into_iter ( ) , PySetInner :: difference, vm)
1332+ Ok ( Self {
1333+ inner : self . inner . difference_multi ( others. into_iter ( ) , vm) ?,
1334+ ..Default :: default ( )
1335+ } )
12811336 }
12821337
12831338 #[ pymethod]
@@ -1337,11 +1392,10 @@ impl PyFrozenSet {
13371392 vm : & VirtualMachine ,
13381393 ) -> PyResult < PyArithmeticValue < Self > > {
13391394 if let Ok ( other) = AnySet :: try_from_object ( vm, other) {
1340- Ok ( PyArithmeticValue :: Implemented ( self . op (
1341- other,
1342- PySetInner :: difference,
1343- vm,
1344- ) ?) )
1395+ Ok ( PyArithmeticValue :: Implemented ( Self {
1396+ inner : self . inner . difference_new ( other. into_iterable ( vm) ?, vm) ?,
1397+ ..Default :: default ( )
1398+ } ) )
13451399 } else {
13461400 Ok ( PyArithmeticValue :: NotImplemented )
13471401 }
@@ -1356,8 +1410,7 @@ impl PyFrozenSet {
13561410 Ok ( PyArithmeticValue :: Implemented ( Self {
13571411 inner : other
13581412 . as_inner ( )
1359- . copy ( )
1360- . difference ( ArgIterable :: try_from_object ( vm, zelf. into ( ) ) ?, vm) ?,
1413+ . difference_new ( ArgIterable :: try_from_object ( vm, zelf. into ( ) ) ?, vm) ?,
13611414 ..Default :: default ( )
13621415 } ) )
13631416 } else {
0 commit comments