How to Use List Comprehensions in Objective-C

Written by: on August 22

Over the last year and a half, I’ve found myself using Python extensively, mainly for various utility tasks at work. One feature I took to instantly is list comprehensions. They allow convenient and elegant construction of new lists through the application of transformations to an existing list.

Let’s take a look at a basic Python list comprehension which simply transforms a list of integers into a list of the squares of its elements:

>>> originals = [1, 2, 3, 4, 5]
>>> squares = [x * x for x in originals]
>>> squares
[1, 4, 9, 16, 25]

Taking a look at [x * x for x in originals], we see several syntactic elements – the square brackets (indicating a list), the x * x clause (the output function), and the for-in loop construct (the input). The meaning is made clear by the interactive Python session; a new list is generated by iterating the input and applying the output function to each element. List comprehensions can also support the use of nested for loops as well as for predicates.

With Objective-C blocks, we can get something close to the original syntax, perhaps not quite as elegant but still useful. DEListComprehension supports predicates, indexed comprehensions (taking different behaviors based on the array index), parallel comprehensions, and nested comprehensions.

The basic idea for all of these functions and methods is to define output blocks for the transformation and the predicate blocks for filtering. Output blocks can take single elements as their arguments (for a standard list comprehension) or NSArray instances (for a parallel or nested comprehension).

Let’s take a look at the example above, re-written with DEListComprehensions:

NSArray *originals = @[ @1, @2, @3, @4, @5 ];
NSArray *squares = [originals deComprehensionWithOutput:^id(NSNumber *x, NSUInteger index) {
                                                            return @([x integerValue] * [x integerValue]);
}
                                              predicate:nil];
NSLog(@"squares = %@", squares);

The above code produces the following output which matches the output of our Python snippet:

squares = (1,4,9,16,25)

Having shown the use of the output block, we turn now to the use of the predicate block. Suppose I wanted to yield only the squares of even-numbered elements of originals. The use of a predicate block as shown below enables this:

NSArray *originals = @[@1, @2, @3, @4, @5];
NSArray *squares = [originals deComprehensionWithOutput:^id(id x, NSUInteger idx) {
return @([x integerValue] * [x integerValue]);
}
                                              predicate:^BOOL(id x, NSUInteger idx) {
                                                            return [x integerValue] % 2 == 0;
}];
NSLog(@"squares = %@",squares);

The output of this snippet of code shows that only 2 and 4 were passed into the transformation function in order to build the new list.

squares = (4,16)

The above examples are fairly straightforward, but please be aware that we can use the index of the object as part of the predicate or as part of the output function.

The NSArray category method we’ve reviewed so far is built on the idea of constructing a new list by iterating a single list. However, there are two more options; we can generate new lists by either iterating multiple arrays in parallel or by nesting the iteration of several arrays.

Suppose we want to sum the elements of three lists, a, b, and c, producing list r, such that r[i] = a[i] + b[i] +c[i]. We can do this with multiListParallelComprehension().

NSArray *a = @[@1, @2, @3];
NSArray *b = @[@1, @2, @3];
NSArray *c = @[@3, @4, @5];
NSArray *r = multiListParallelComprehension(@[a,b,c], ^id(NSArray *inputs) {
                                                            NSInteger sum = 0;
                                                            for (NSNumber *n in inputs) {
                                                                sum += [n integerValue];
                                                            }
                                                            return @(sum);
                                                        }, nil);
NSLog(@"result = %@", r);

result = (5,8,11) Let’s also look at the nested multiple-array list comprehensions. MultiListNestedComprehension() constructs a new list out of the combinations formed by a nested iteration of each list array that is in the array passed as the lists argument. The predicate and output blocks are invoked, passing the combination as the inputs parameter. The order within lists defines the depth of the iteration. So, if you pass the list @[shallow, middle, deep] (where shallow, middle, deep are NSArray instances), the iteration will happen as follows:

for (id x in shallow) {
    for (id y in middle) {
        for (id z in deeper) {
            // @[x, y, z] will be passed to the output and predicate blocks as the inputs argument
        }
    }
}

Let’s take a look:

NSArray *first = @[@1, @2, @3, @4];
NSArray *second = @[@5, @6, @7, @8];
NSArray *result = multiListNestedComprehension(@[first, second], ^id(NSArray *inputs) {
                                                                    NSInteger product = 1;
                                                                    for (NSNumber *n in inputs) {
                                                                        product = product * [n integerValue];
                                                                    }
                                                                    return @(product);
                                                                }, nil);
NSLog(@"result = %@", result);

The above code gives us the output:

result = (5,6,7,8,10,12,14,16,15,18,21,24,20,24,28,32)

The examples presented here are contrived, but, hopefully, DEListComprehension can provide an extra level of convenience and expressivity to code which utilizes them. A function and an NSArray category provide the basic case of applying the predicate and transform function to a single list, while two other functions handle the cases of parallel and nested iterations of multiple lists. Please try it out, contribute to the code on github, or leave a comment letting us know what you think!

Like what you read? Get Insights updates!

* indicates required

Carl Veazey

Carl was a Software Engineer for Double Encore, Inc., a leading mobile development company, where he has been a core team member on apps with millions of downloads. He is also a graduate student at the University of Colorado at Boulder. When not at work, school, or spending time with his family, he actively contributes to Stack Overflow.

Article


Add your voice to the discussion: