33

I have two NSArrays:

NSArray *wants = [NSArray arrayWithObjects:
                  @"apples", 
                  @"oranges", 
                  @"pineapple", 
                  @"mango", 
                  @"strawberries", 
                  nil];
NSArray *needs = [NSArray arrayWithObjects:
                  @"apples", 
                  @"pineapple", 
                  @"strawberries", 
                  nil];

And I want to XOR them. Something like wants - needs so that what I have left is

[NSArray arrayWithObjects:
@"oranges", 
@"mango", 
nil];

I would normally go through some heavy looping, but I am sure there is a more practical way. What should I do instead?

jscs
  • 63,694
  • 13
  • 151
  • 195
Jacksonkr
  • 31,583
  • 39
  • 180
  • 284

5 Answers5

62

Something like this?

NSMutableArray *array = [NSMutableArray arrayWithArray:wants];
[array removeObjectsInArray:needs];
Kirby Todd
  • 11,254
  • 3
  • 32
  • 60
  • 1
    Wont that crash if needs contains objects that dont exist in wants? – TompaLompa May 06 '12 at 17:58
  • 4
    @TompaLompa No, it doesn't crash. From the NSMutableArray docs: `If the receiving array does not contain objects in otherArray, the method has no effect (although it does incur the overhead of searching the contents).` – highlycaffeinated May 06 '12 at 18:02
  • This is NOT an XOR, as if needs contains an object that wants doesn't, the result will not contain that object. – Richard J. Ross III May 06 '12 at 18:09
  • 1
    Fortunately the shoe fit on this one as well and it's a very simple/slick solution. I knew there had to be a shorter way than what I was going to do.. – Jacksonkr May 09 '12 at 00:37
9

How about using predicates?

NSPredicate *predicate = [NSPredicate predicateWithFormat:@"NOT (SELF IN %@)", needs];
NSArray *wants_needs = [wants filteredArrayUsingPredicate:predicate];
Gyfis
  • 1,174
  • 1
  • 14
  • 35
8

Kirby's answer is fine, but: if you don't care about the order of the elements in the arrays, you should be using sets instead. If the order is important, you might consider NSOrderedSet. You can use the -minusSet: or, for the latter, -minusOrderedSet: methods.

Ken Thomases
  • 88,520
  • 7
  • 116
  • 154
  • This is definitely the way to go. I'm imagining order isn't important in this example, but even if it is, sets are highly optimized for this kind of behavior. – Mike Z May 06 '12 at 18:17
  • 1
    Note that `NSOrderedSet` is only available in OSX ≥ 10.7 and iOS ≥ 5.0. – jscs May 06 '12 at 18:18
2

Given two assumptions: that the order is unimportant (or can be restored -- e.g., if the arrays are sorted now)* and that no item appears more than once in either array (although you can use a counted set for this), a set might be a good choice.

The XOR (strictly, the symmetric difference) of two sets is the union minus the intersection:

NSMutableSet * unioned = [NSMutableSet setWithArray:wants];
[unioned unionSet:[NSSet setWithArray:needs]];
NSMutableSet * intersection = [NSMutableSet setWithArray:needs];
[intersection intersectSet:[NSSet setWithArray:wants]];

[unioned minusSet:intersection];

*If order is important, you can use NSOrderedSet.

jscs
  • 63,694
  • 13
  • 151
  • 195
0

Try This:

NSArray *NSArray_XOR(NSArray *arr1, NSArray *arr2)
{
    NSMutableArray *results = [NSMutableArray array];

    for (int i = 0; i < arr1.count; i++) {
        id obj = [arr1 objectAtIndex:i];

        if (![arr2 containsObject:obj])
            [results addObject:obj];
    }

    for (int i = 0; i < arr2.count; i++) {
        id obj = [arr2 objectAtIndex:i];

        if (![arr1 containsObject:obj])
            [results addObject:obj];
    }

    // make a unmutable copy of the array.
    return [NSArray arrayWithArray:results];
}
Richard J. Ross III
  • 55,009
  • 24
  • 135
  • 201