2

I have an array, NSMutableArray *stringArray that looks like this

stringArray = 

[0]String1
[1]String2
[2]String3
[3]String4
[4]String5
[5]String6

How would I go about splitting this array into two arrays based on even/odd indexes?

Example:

NSMutableArray *oddArray = ([1], [3], [5]);

NSMutableArray *evenArray = ([0], [2], [4]);

Thanks in advance!

rmaddy
  • 314,917
  • 42
  • 532
  • 579
hmzfier
  • 554
  • 9
  • 21

4 Answers4

5

create two mutable arrays, use enumerateObjectsWithBlock: on the source array and check idx % 2 to put it into first or second array

Using the ternary operator:

NSArray *array = @[@1,@2,@3,@4,@5,@6,@7,@8,@9,@10,@11,@12];
NSMutableArray *even = [@[] mutableCopy];
NSMutableArray *odd = [@[] mutableCopy];
[array enumerateObjectsUsingBlock:^(id object, NSUInteger idx, BOOL *stop) {
    NSMutableArray *evenOrOdd = (idx % 2) ? even : odd;
    [evenOrOdd addObject:object];
}];

If you like super compact code you could use the ternary operator like

[((idx % 2) ? even : odd) addObject:object];

If you want to split the array to N arrays, you can do

NSArray *array = @[@1,@2,@3,@4,@5,@6,@7,@8,@9,@10,@11,@12];

NSArray *resultArrays = @[[@[] mutableCopy],
                          [@[] mutableCopy],
                          [@[] mutableCopy]];

[array enumerateObjectsUsingBlock:^(id object, NSUInteger idx, BOOL *stop) {
        [resultArrays[idx % resultArrays.count] addObject:object];
}];

In Objective-C Categories should come to your mind to create re-uasable code:

@interface NSArray (SplittingInto)
-(NSArray *)arraysBySplittingInto:(NSUInteger)N;
@end

@implementation NSArray (SplittingInto)
-(NSArray *)arraysBySplittingInto:(NSUInteger)N
{
    NSAssert(N > 0, @"N cant be less than 1");
    NSMutableArray *resultArrays = [@[] mutableCopy];
    for (NSUInteger i =0 ; i<N; ++i) {
        [resultArrays addObject:[@[] mutableCopy]];
    }

    [self enumerateObjectsUsingBlock:^(id object, NSUInteger idx, BOOL *stop) {
                                [resultArrays[idx% resultArrays.count] addObject:object];
                            }];
    return resultArrays;
}
@end

Now you can do

NSArray *array = [@[@1,@2,@3,@4,@5,@6,@7,@8,@9,@10,@11,@12] arraysBySplittingInto:2];

array contains

(
        (
        1,
        3,
        5,
        7,
        9,
        11
    ),
        (
        2,
        4,
        6,
        8,
        10,
        12
    )
)
vikingosegundo
  • 52,040
  • 14
  • 137
  • 178
  • The concurrent solution would require locking on the mutable arrays. – Tricertops Nov 30 '14 at 12:04
  • To use concurrency, you could dispatch two operations, one for `even` and one for `odd` array and enumerate the original array twice. This may speed things up for very large arrays. – Tricertops Nov 30 '14 at 12:09
  • There you go, there’s my answer. ***You*** wrote a concurrent solution that is broken. Now I wrote working concurrent solution and benchmarked it. _PS: I didn’t vote you down._ – Tricertops Nov 30 '14 at 12:56
  • `NSMutableArray` is not thread-safe, so mutating it concurrently would result in some elements missing. https://en.wikipedia.org/wiki/Synchronization_(computer_science) – Tricertops Nov 30 '14 at 13:00
  • I deleted the concurrent option, as actually it is about of the scope of this question. – vikingosegundo Nov 30 '14 at 13:14
3

Create two NSIndexSets, one for the even indexes and one for the odd, then use objectsAtIndexes: to extract the corresponding slices of the array.

rickster
  • 124,678
  • 26
  • 272
  • 326
1

There are following ways you can achieve that:-

The first and second one solution are already mentioned by the above two. Below are the implementation of the same:-

//First Solution
NSArray *ar=@[@"1",@"2",@"3",@"4",@"5"];
NSMutableArray *mut1=[NSMutableArray array];
NSMutableArray *mut2=[NSMutableArray array];
[ar enumerateObjectsUsingBlock:^(id object, NSUInteger idx, BOOL *stop) {
    if (idx%2==0)
    {
        [mut1 addObject:object];
    }
    else
    {
        [mut2 addObject:object];
    }
}];

//Second Solution
NSMutableIndexSet *idx1 = [NSMutableIndexSet indexSet];
NSMutableIndexSet *idx2 = [NSMutableIndexSet indexSet];
for (NSUInteger index=0; index <ar.count(); index++)
{
   if(index%2==0)
    {
        [idx1 addIndex:index];
    }
    else{
        [idx2 addIndex:index];
    }
}
NSArray *evenArr=[ar objectsAtIndexes:idx1];
NSArray *oddArr=[ar objectsAtIndexes:idx2];
NSLog(@"%@",evenArr);
NSLog(@"%@",oddArr);
Hussain Shabbir
  • 14,801
  • 5
  • 40
  • 56
0

Got some time for benchmarking and it turns out that when the input array has more than 10 million, it’s faster to use parallel execution.

Here is the concurrent solution that enumerates the input array twice to prevent race conditions on the output arrays.

static NSArray * concurrent(NSArray *input) {
    NSUInteger capacity = input.count / 2;
    NSArray *split = @[
                       [NSMutableArray arrayWithCapacity:capacity],
                       [NSMutableArray arrayWithCapacity:capacity],
                       ];
    [split enumerateObjectsWithOptions:NSEnumerationConcurrent
                            usingBlock:^(NSMutableArray *output, NSUInteger evenOdd, BOOL *stop) {
                                [input enumerateObjectsUsingBlock:^(id object, NSUInteger index, BOOL *stop) {
                                    if (index % 2 == evenOdd) {
                                        [output addObject:object];
                                    }
                                }];
                            }];
    return split;
}

I consider this to be the best serial solution, so I used it for benchmarking:

static NSArray * serial(NSArray *input) {
    NSUInteger capacity = input.count / 2;
    NSMutableArray *even = [NSMutableArray arrayWithCapacity:capacity];
    NSMutableArray *odd = [NSMutableArray arrayWithCapacity:capacity];

    [input enumerateObjectsUsingBlock:^(id object, NSUInteger index, BOOL *stop) {
        NSMutableArray *output = (index % 2) ? odd : even;
        [output addObject:object];
    }];
    return @[ even, odd ];
}

Results

  • 1 million elements
    • Serial: 54.081 ms
    • Concurrent: 65.958 ms (18% worse)
  • 10 million elements
    • Serial: 525.851 ms
    • Concurrent: 412.691 ms (27% better)
  • 100 million elements
    • Serial: 5244.798 ms
    • Concurrent: 4137.939 ms (27% better)

Average of 5 runs.
Input filled with NSNumbers.
Fastest smallest optimization -Os.

Tricertops
  • 8,492
  • 1
  • 39
  • 41
  • Your concurrent implementation is semi-concurrent, as it will use 2 threads at maximum. – vikingosegundo Nov 30 '14 at 13:13
  • @vikingosegundo Concurrent = more than one. When you are building two arrays, you can use two threads at most. – Tricertops Nov 30 '14 at 17:37
  • thats not true. you could use several temporary arrays and join them later. or c-arrays could be used. or … I just want to point out that with more complex algorithms a higher degree of concurrency can be achieved. But that would be out of the scope of the question and as [bbum said](http://stackoverflow.com/a/6196346/106435): addressing a performance problem by moving from serial to concurrent execution usually results having two problems: performance & concurrency. – vikingosegundo Nov 30 '14 at 19:48