7
$\begingroup$

In my situation, I have a large list of objects L={A,B,C,D,E,...} and a function Q[] applying to them, which will produce the values only from the set {0,2,4,6,8}.

(e.g. Q[] can be Length[] and my objects ABCDE are lists of certain sizes)

I want to split the list so that I will obtain 5 lists L0,L2,L4,L6,L8, each list contains only the index of the corresponding elements in L (Because I want to keep track of the location of those objects in the original list). Is there an effective way to do this, other than doing a for loop if statement and calling AppendTo[] adding the index each time?

(The Mathematica function GatherBy[] will only give the element of the list itself, but not the index in the original list which I want.)

For example, for the input list

L={{1,2},{3,4,5,6},{7,8,9,10},{11,12}}

and

Q=Length

I want

{L0,L2,L4,L6,L8} = {{},{1,4},{2,3},{},{}}

since sublists of length 2 are at positions 1 and 4, length 4 -- at 2 and 3, and there are no sublists of length 0, 6, 8.

$\endgroup$
6
  • $\begingroup$ Since you'll probably end up with an association, you could use Lookup with the optional argument to handle missing cases (L4 be an empty list). $\endgroup$
    – LLlAMnYP
    Jul 27, 2018 at 8:08
  • $\begingroup$ GatherBy[Range[Length@L], Q[L[[#]]]&]? $\endgroup$
    – kglr
    Jul 27, 2018 at 8:26
  • 2
    $\begingroup$ >>minimal example: L={{1,2},{3,4,5,6},{7,8,9,10},{11,12}}, Q=Length[], Then {L0,L2,L4,L6,L8} = {{},{1,4},{2,3},{},{}} $\endgroup$
    – Ivan
    Jul 27, 2018 at 8:52
  • $\begingroup$ Thanks for the edit but the input needs to contain information about values you care about. E.g. only 2 and 4 long lists exist but you want to lookup 0,2,4,6,8. $\endgroup$
    – Kuba
    Jul 27, 2018 at 9:43
  • $\begingroup$ @Kuba my bad, I should have left a comment when I edited this. Perhaps you or OP have ideas how to make things clearer? $\endgroup$
    – LLlAMnYP
    Jul 27, 2018 at 10:33

3 Answers 3

5
$\begingroup$
SeedRandom[123]
L = Table[RandomChoice[{1, 4} -> {{}, RandomChoice[CharacterRange["A", "G"], 
      RandomChoice[{2, 4, 6, 8}]]}], {15}]

{{"G", "E", "F", "C"}, {"G", "D", "B", "C", "D", "A"}, {"D", "B", "E", "E", "F", "D", "G", "G"}, {}, {"C", "C", "F", "A", "D", "C"}, {"B", "A", "G", "B"}, {"A", "E"}, {}, {}, {"C", "F"}, {"F", "E", "C", "E"}, {"G", "E"}, {"C", "E", "C", "A", "B", "A"}, {"G", "F", "G", "F", "D", "B", "A", "B"}, {"A", "C", "B", "E"}}

Q = Length;
{L0, L2, L4, L6, L8} = Pick[Range @ Length @ L, #- Q /@ L, 0] & /@ {0, 2, 4, 6, 8}

{{4, 8, 9}, {7, 10, 12}, {1, 6, 11, 15}, {2, 5, 13}, {3, 14}}

Or, as suggested by Kuba in comments:

{L0, L2, L4, L6, L8} = PositionIndex[Q /@ L] /@ {0, 2, 4, 6, 8} /. _Missing-> {}

same output

$\endgroup$
1
  • $\begingroup$ Thanks for the idea! and the "Missing" part also helps~ $\endgroup$
    – Ivan
    Jul 27, 2018 at 15:17
5
$\begingroup$

Pulling my approach out from the comments:

lists = {{1, 2}, {3, 4, 5, 6}, {7, 8, 9, 10}, {11, 12}};
func = Length;
values = {0, 2, 4, 6, 8};
Lookup[PositionIndex[func /@ lists], values, {}]
{{}, {1, 4}, {2, 3}, {}, {}}

I thought kglr's approach would be pretty fast (as Pick often is), but since we're dealing with unpacked arrays, it took a lot of special effort to get the most out of it. Here's a performance comparison.

SeedRandom[123]
lists = Table[
   RandomChoice[{1, 4} -> {{}, 
      RandomChoice[CharacterRange["A", "G"], 
       RandomChoice[{2, 4, 6, 8}]]}], {100000}];
values = {0, 2, 4, 6, 8} // Developer`ToPackedArray;
(r1 = Pick[ConstantArray[Range@Length@lists, Length@values], 
           ConstantArray[
             func /@ lists // Developer`ToPackedArray,
             Length@values
           ] - values, 0
      ]); // AbsoluteTiming (* my attempt to vectorize kglr's solution *)

(* kglr's solution as is *)
(r2 = Pick[Range@Length@lists, # - func /@ lists, 0] & /@ values); // AbsoluteTiming

(* my approach *)
(r3 = Lookup[PositionIndex[func /@ lists // Developer`ToPackedArray], values, {}]); // AbsoluteTiming

r1 === r2 === r3
{0.0450857, Null}
{0.122854, Null}
{0.0340838, Null}
True
$\endgroup$
3
$\begingroup$

I suggest that instead of {L0, L2, L4, L6, L8} you use {lq[0], lq[2], lq[4], lq[6], lq[8]}.

My approcach

lq = GroupBy[
 MapIndexed[{##} &, L]
 , Q@*First -> Last@*Last
 ]

But I have to admit that @Kuba's is the best solution to my taste

lq = PositionIndex[Q /@ L]
$\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.