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$
13
$\begingroup$

I would do it like this

Values[PositionIndex[list]]
| improve this answer | |
$\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.

| improve this answer | |
$\endgroup$
  • $\begingroup$ Is this still faster than PositionIndex? $\endgroup$ – CA Trevillian Dec 30 '19 at 19:26
  • 1
    $\begingroup$ @CATrevillian yes much faster, see benchmarks above. $\endgroup$ – Roman Dec 30 '19 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}}

| improve this answer | |
$\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}}

| improve this answer | |
$\endgroup$
1
$\begingroup$

A solution with SequencePosition looks like:

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

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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