I have an array of arbitrary elements, say strings:

testArray = {"String A", "String B", "String C", "String D", "String E"};

How can I split this into two arrays, one for the element with odd indices in testArray and one for elements with even indices in testArray:

testArrayOdd = {"String A", "String C", "String E"};
testArrayEven = {"String B", "String D"};

Is there a simple way to do this that generalizes to more complicated partitionings based on, say, the primeness of the index?

Clarification!: The strings in the array can be anything "String 1" could be "knejibvei (junk)". We only care about the INDEX of the string in the original array. Sorry about this. For odd / even testing of elements, see here: Separate an array in two arrays, the even and odd terms being separated in these two arrays.


For the simple case of even and odd, you can do either:

{testArrayOdd, testArrayEven} = {testArray[[;; ;; 2]], testArray[[2 ;; ;; 2]]}
(* {{"String 1", "String 3", "String 5"}, {"String 2", "String 4"}} *)


{testArrayOdd, testArrayEven} = Partition[testArray, 2, 2, 1, {}] ~Flatten~ {2}

For a more general grouping based on an arbitrary predicate, you can write a custom function:

groupByPositions[list_, pred_] := 
    With[{trueList = testArray[[Select[Range@Length@list, pred]]]},
        {trueList, Complement[list, trueList]}

With this, the original problem becomes

{testArrayOdd, testArrayEven} = groupByPositions[testArray, OddQ]

and to group by prime indices:

groupByPositions[testArray, PrimeQ]
(* {{"String 2", "String 3", "String 5"}, {"String 1", "String 4"}} *)
| improve this answer | |
  • $\begingroup$ Congratulations on the list-manipulation gold badge! $\endgroup$ – Artes Apr 4 '14 at 14:44
  • $\begingroup$ I didn't even realize you could use the third argument of Flatten that way. Mind == blown. $\endgroup$ – Pillsy Apr 4 '14 at 17:50
  • $\begingroup$ Looks like you covered everything, and you even threw in some ~infix~ for me. +1 $\endgroup$ – Mr.Wizard Apr 4 '14 at 19:29
  • $\begingroup$ in your GroupByPositions code testArray should be 'list'. $\endgroup$ – ogerard Dec 26 '17 at 10:25

Just for the sake of variety:

MapIndexed could be used here, in conjunction with tagged Sowing and Reaping:

Reap[MapIndexed[If[PrimeQ@First@#2, Sow[#1, 1], Sow[#1, 2]] &, 
   testArray]] // Last (* partition based on index primeness *)

(* {{"String 1", "String 4"}, {"String 2", "String 3", "String 5"}} *)

I've used the integers 1 and 2 as tags. (The docs show symbols like x and y being used, but integers appear to work OK too.)

The list associated with the first tag to be encountered is given first, hence the order of the sublists above. If you wished to change that, you could add {2, 1} as an extra argument at the end of Reap.

(As Mr. Wizard suggests in his comment, a better expression is:

Reap[MapIndexed[# ~Sow~ PrimeQ[#2] &, testArray]][[2]]

where the tag is automatically generated as the predicate's value and the If is completely avoided.)

Personally I find GatherBy most natural here:

Part[testArray, #] & /@ GatherBy[Range@Length@testArray, PrimeQ]
| improve this answer | |
  • $\begingroup$ Thanks, I'm getting the impression that Sow and Reap are really worth learning about. $\endgroup$ – CA30 Apr 4 '14 at 16:32
  • $\begingroup$ Personally I feel that Sow and Reap are a bit of a throwback to procedural programming - not to say there aren't certain situations where they're useful, or that they aren't worth adding to your Mathematica toolbox. $\endgroup$ – Aky Apr 4 '14 at 17:15
  • $\begingroup$ @CA30 In my opinion they are very worth learning about due to the flexibility of the abstraction and it's relatively high performance. While e.g. GatherBy (added to the language after Sow/Reap) should be used where easily applicable as it will be both more concise and somewhat faster, Sow and Reap let you do things not otherwise possible without resorting to more verbose methods. $\endgroup$ – Mr.Wizard Apr 4 '14 at 19:33
  • $\begingroup$ I can only comment that anything Mr.Wizard has to say about Mathematica is worth heeding! $\endgroup$ – Aky Apr 4 '14 at 19:51
  • $\begingroup$ Thanks, Aky. Incidentally I would write the Sow/Reap method like this: Reap[MapIndexed[# ~Sow~ PrimeQ[#2] &, testArray]][[2]]. You can also declare a specific order of output using the second parameter of Reap, e.g.: Reap[MapIndexed[# ~Sow~ PrimeQ[#2] &, testArray], {True, False}][[2, All, 1]]. (This returns the prime-index elements in the first list rather than the second.) $\endgroup$ – Mr.Wizard Apr 4 '14 at 20:10

You could use my GatherByList function to do this:

GatherByList[list_, representatives_] := Module[{func},
    func /: Map[func, _] := representatives;
    GatherBy[list, func]

GatherByList[testArray, EvenQ[Range @ Length @ testArray]]
GatherByList[testArray, PrimeQ[Range @ Length @ testArray]]

{{"String A", "String C", "String E"}, {"String B", "String D"}}

{{"String A", "String D"}, {"String B", "String C", "String E"}}

| improve this answer | |
ClearAll[groupByIndex1, groupByIndex2]
groupByIndex1 = Function[{t}, Pick[#, #2 /@ (Range @ Length @ #), t]] /@ {True, False} &;

groupByIndex2 = Function[{d, f}, Values@Reverse@ KeySort @ 
  GroupBy[MapIndexed[{#, #2[[1]]} &, d], f[Last@#] & -> First]];


groupByIndex1[testArray, OddQ]

{{"String A", "String C", "String E"}, {"String B", "String D"}}

groupByIndex1[testArray, EvenQ]

{{"String B", "String D"}, {"String A", "String C", "String E"}}

groupByIndex1[testArray, PrimeQ]

{{"String B", "String C", "String E"}, {"String A", "String D"}}

groupByIndex1[testArray, 2 < # <= 4 &]

{{"String C", "String D"}, {"String A", "String B", "String E"}}

And @@ (groupByIndex1[testArray, #] == 
        groupByIndex2[testArray, #] & /@ {OddQ, EvenQ, PrimeQ, 2 < # <= 4 &})


| improve this answer | |

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.