9

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.

Alexey Popkov
  • 61,809
  • 7
  • 149
  • 368
CA30
  • 151
  • 7

4 Answers4

12

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"}} *)

or

{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:

Clear@groupByPositions
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"}} *)
rm -rf
  • 88,781
  • 21
  • 293
  • 472
6

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]
Aky
  • 2,719
  • 12
  • 19
  • Thanks, I'm getting the impression that Sow and Reap are really worth learning about. – CA30 Apr 04 '14 at 16:32
  • 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. – Aky Apr 04 '14 at 17:15
  • @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. – Mr.Wizard Apr 04 '14 at 19:33
  • I can only comment that anything Mr.Wizard has to say about Mathematica is worth heeding! – Aky Apr 04 '14 at 19:51
  • 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.) – Mr.Wizard Apr 04 '14 at 20:10
  • @Mr.Wizard Something like that (i.e. generating the tag from the predicate itself) had crossed my mind at some point, and it's actually what made me think of GatherBy. I agree it's more elegant than manually assigning tags. Re your second point (about ordering the output) I mentioned that in my answer. (Unless I missed the point you're making.) – Aky Apr 04 '14 at 20:19
  • I see that you did mention that; sorry for overlooking it! Anyway, you already have my +1. – Mr.Wizard Apr 04 '14 at 20:25
4

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"}}

Carl Woll
  • 130,679
  • 6
  • 243
  • 355
2
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]];

Examples:

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 &})

True

kglr
  • 394,356
  • 18
  • 477
  • 896