Deleting duplicates of a list without touching specified elemets
This should be reasonably fast:
ClearAll[value];
value[exceptions_List] := value[Alternatives @@ DeleteDuplicates@exceptions];
value[exceptions_][x_] /; MatchQ[x, exceptions] := Unique[];
value[exceptions_][x_] := x;
OP's examples:
list = {1, 3, 5, 5, 12, 12, 6, 11, 7, 7, 9, 10, 2, 2, 4};
positions = {3, 9};
DeleteDuplicatesBy[list, value[list[[positions]]]]
(* {1, 3, 5, 5, 12, 6, 11, 7, 7, 9, 10, 2, 4} *)
list = {1, 2, 3, 3, 4, 3};
positions = {3};
DeleteDuplicatesBy[list, value[list[[positions]]]]
(* {1, 2, 3, 3, 4, 3} *)
Update. Even faster:
ddxc[expr_, exceptions_List] := Module[{value},
(value[#] := Unique[]) & /@ exceptions;
value[x_] := x;
DeleteDuplicatesBy[expr, value]
];
SeedRandom[0];
list = RandomInteger[1000, 10^5];
positions = Range[100];
DeleteDuplicatesBy[list, value[list[[positions]]]] // Length // AbsoluteTiming
ddxc[list, list[[positions]]] // Length // AbsoluteTiming
(*
{0.661751, 10668}
{0.136582, 10668}
*)
Clarifiation: The performance concern regarding Unique
appears to no longer apply in recent versions. The code below still tests faster for me but deprecation of methods using Unique
is presently unfounded.
This question is related to How to Gather a list with some elements considered unique and I propose a similar solution to what I offered there.
fn1[a_, p_] :=
Module[{f, x, i = 1},
Scan[(f[#] := x[i++]) &, p];
GatherBy[a, f][[All, 1]]
]
fn1[{1, 3, 5, 5, 12, 12, 6, 11, 7, 7, 9, 10, 2, 2, 4}, {5, 7}]
{1, 3, 5, 5, 12, 6, 11, 7, 7, 9, 10, 2, 4}
Compared to Michael's fastest function:
SeedRandom[0];
list = RandomInteger[1*^6, 2*^6];
positions = Range[100];
fn1[list, list[[positions]]] // Length // AbsoluteTiming
ddxc[list, list[[positions]]] // Length // AbsoluteTiming
{2.90443, 865482} {7.5404, 865482}
DeleteDuplicates[list, And[#1 == #2, Not@MemberQ[list[[positions]], #1]] &]