9
$\begingroup$

I am looking for a simple way to group the list-indices of equal elements of a list. Examples:

{1,2,3,3} -> {{1}, {2}, {3,4}}
(the elements at positions 1 and 2 are unique, and those at positions 3 and 4 are equal)

{f,f,g,g,f} -> {{1,2,5}, {3,4}}
(the elements at positions 1,2,5 are equal, and those at positions 3 and 4 are equal)

For now my code is

F[L_] := Values@GroupBy[MapIndexed[List, L], First -> Last, Flatten]

or

G[L_] := GatherBy[MapIndexed[List, A], First][[All, All, 2, 1]]

both of which look very clumsy. Is there a direct way to group? Something along the lines of

GroupBy[L, First -> index]

maybe?

benchmarks

Thanks for all the contributions! Here's a ranking by runtime for specific parameters:

L = RandomInteger[{0, 10^3}, 10^6];

(* Carl's ResourceFunction call (run twice to get timing right) *)
a1 = Values@ResourceFunction["GroupByList"][Range@Length@L, L]; //AbsoluteTiming//First
(* Chris's simplest call *)
a2 = Values@PositionIndex[L]; //AbsoluteTiming//First
(* ubpdqn *)
a3 = Reap[MapIndexed[Sow[#2[[1]], #1] &, L]][[2]]; //AbsoluteTiming//First
(* my second crummy suggestion *)
a4 = GatherBy[MapIndexed[List, L], First][[All, All, 2, 1]]; //AbsoluteTiming//First
(* my first crummy suggestion *)
a5 = Values@GroupBy[MapIndexed[List, L], First -> Last, Flatten]; //AbsoluteTiming//First
(* KennyColnago *)
a6 = Map[SequencePosition[L, {#}][[All, 1]] &, DeleteDuplicates[L]]; //AbsoluteTiming//First
(* OkkesDulgerci *)
a7 = Flatten /@ (Position[L, #] & /@ DeleteDuplicates@L); //AbsoluteTiming//First

(*     0.028881 s for a1    *)
(*     0.086623 s for a2    *)
(*     0.997497 s for a3    *)
(*     1.44011  s for a4    *)
(*     1.8618   s for a5    *)
(*    13.86     s for a6    *)
(*    31.6595   s for a7    *)

a1 == a2 == a3 == a4 == a5 == a6 == a7
(*    True    *)
$\endgroup$
0

5 Answers 5

13
$\begingroup$

I would do it like this

Values[PositionIndex[list]]
$\endgroup$
5
$\begingroup$

You can use the ResourceFunction GroupByList to do this:

list = {1, 2, 3, 3};
ResourceFunction["GroupByList"][Range @ Length @ list, list]

<|1 -> {1}, 2 -> {2}, 3 -> {3, 4}|>

list = {f, f, g, g, f};
ResourceFunction["GroupByList"][Range @ Length @ list, list]

<|f -> {1, 2, 5}, g -> {3, 4}|>

Use Values to convert the Association to a list.

$\endgroup$
2
  • $\begingroup$ Is this still faster than PositionIndex? $\endgroup$ Dec 30, 2019 at 19:26
  • 1
    $\begingroup$ @CATrevillian yes much faster, see benchmarks above. $\endgroup$
    – Roman
    Dec 30, 2019 at 20:50
4
$\begingroup$

You can also used Reap/Sow, e.g.

pi[u_] := Reap[MapIndexed[Sow[#2[[1]], #1] &, u]][[2]]

So pi[{f, f, g, g, f}] yields

{{1, 2, 5}, {3, 4}}

$\endgroup$
2
$\begingroup$

I am not sure this is simple enough but here is one way to do it.

L = {1, 2, 3, 3};
G[L_] := Flatten /@ (Position[L, #] & /@ DeleteDuplicates@L)
G[L]

{{1}, {2}, {3, 4}}

  L = {f,f,g,g,f};
  G[L]

{{1, 2, 5}, {3, 4}}

$\endgroup$
1
$\begingroup$

A solution with SequencePosition looks like:

GroupListIndices[a_List] := Map[SequencePosition[a, {#}][[All, 1]]&, DeleteDuplicates[a]]
$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.