Search code examples
objective-cfor-loopmapreducereactive-programmingreactive-cocoa

How can I simplify my nested for-loops with ReactiveCocoa?


Say I have 2 NSDictionaries that I don't know beforehand like:

NSDictionary *dictA = @{ @"key1" : @1,
                         @"key2" : @2  };

NSDictionary *dictB = @{ @"key1" : @"a string" };

I want to find the first match between the keys of dictB and the keys or values of dictA. Each key of dictB will either be a NSNumber or a string. If it's a number, try to find a match from the values of dictA. If it's a string, try to find a match from the keys of dictA.

Using for loops, it would looks something like this:

id match;
for (id key in dictA ) {
    for (id _key in dictB {
        if ( [_key is kindOfClass:NSNumber.class] && _key == dictA[key] ) {
            match = _key
            goto outer;
        }
        else if ( [_key is kindOfClass:NSString.class] && [_key isEqualToString:key] ) {
            match = _key
            goto outer;
        }
    }
};
outer:;

NSString *message = match ? @"A match was found" : @"No match was found";
NSLog(message);

How could I rewrite this with ReactiveCocoa using RACSequence and RACStream methods so it looks something like:

// shortened pseudo code:
// id match = [dictA.rac_sequence compare with dictB.rac_sequence using block and return first match];

Solution

  • You basically would like to create the cartesian product of the dictionaries and make a selection on it. There is no default operator in ReactiveCocoa that I know of that would do this for you. (In LINQ there are operators for this.) In RAC the simplest solution is to use the scanWithStart:combine: method to implement this operation. Once the cartesian is ready, the filter: and take:1 operations will produce the sequence of your choice.

    NSDictionary *adic = @{@"aa":@"vb", @"ab": @"va"};
    NSDictionary *bdic = @{@"ba": @"va", @"bb":@"vb"};;
    
    RACSequence *aseq = adic.rac_keySequence;
    RACSequence *bseq = bdic.rac_keySequence;
    
    RACSequence *cartesian = [[aseq scanWithStart:nil combine:^id(id running, id next_a) {
        return [bseq scanWithStart:nil combine:^id(id running, id next_b) {
            return RACTuplePack(next_a, next_b);
        }];
    }] flatten];
    
    RACSequence *filteredCartesian = [cartesian filter:^BOOL(RACTuple *value) {
        RACTupleUnpack(NSString *key_a, NSString *key_b) = value;
        // business logic with keys
        return false;
    }];
    
    RACSequence *firstMatch = [filteredCartesian take:1];