@@ -172,8 +172,34 @@ def targeted_attack(
172172 logger .info ("No characters will be replaced (percentage too low or no eligible characters)" )
173173 return text
174174
175- # Select top scoring replacements to apply
176- replacements_to_apply = replacement_options [:num_to_replace ]
175+ # Group replacements by score and select randomly within each score group
176+ replacements_to_apply = [] # We won't apply all possible replacements, just the best ones
177+ score_groups = {}
178+
179+ # Group replacement options by score
180+ for option in replacement_options :
181+ score = option [3 ]
182+ if score not in score_groups :
183+ score_groups [score ] = []
184+ score_groups [score ].append (option )
185+
186+ # Sort scores in descending order
187+ sorted_scores = sorted (score_groups .keys (), reverse = True )
188+
189+ # Select replacements, prioritizing higher scores but choosing randomly within each score group
190+ remaining_to_select = num_to_replace
191+ for score in sorted_scores :
192+ if remaining_to_select <= 0 :
193+ break
194+
195+ candidates = score_groups [score ]
196+ # Randomly shuffle candidates within this score group
197+ random_state .shuffle (candidates )
198+
199+ # Take as many as we need (or all available if fewer than needed)
200+ to_take = min (remaining_to_select , len (candidates ))
201+ replacements_to_apply .extend (candidates [:to_take ])
202+ remaining_to_select -= to_take
177203
178204 # Apply replacements (starting from the end to avoid index shifting problems)
179205 replacements_to_apply .sort (key = lambda x : x [0 ], reverse = True )
0 commit comments