How to turn an NSArray of strings into an array of unique strings, in the same order?
Asked Answered
S

5

28

If you have an NSArray of strings

{ @"ONE", @"ONE", @"ONE", "TWO", @"THREE", @"THREE" }

How would I turn that into

{ @"ONE", @"TWO", @"THREE" }

..where the array follows the same order as the original. I think that you can turn an array into an NSSet to get unique items, but if you turn it back into an array you are not guaranteed to get the same order..

Splenius answered 17/11, 2010 at 22:28 Comment(0)
R
50

My initial thought was that you could do:

NSArray * a = [NSArray arrayWithObjects:@"ONE", @"ONE", @"ONE", @"TWO", @"THREE", @"THREE", nil];
NSLog(@"%@", [a valueForKeyPath:@"@distinctUnionOfObjects.self"]);

But that does not maintain order. Therefore, you have to do it manually:

NSArray * a = [NSArray arrayWithObjects:@"ONE", @"ONE", @"ONE", @"TWO", @"THREE", @"THREE", nil];
NSMutableArray * unique = [NSMutableArray array];
NSMutableSet * processed = [NSMutableSet set];
for (NSString * string in a) {
  if ([processed containsObject:string] == NO) {
    [unique addObject:string];
    [processed addObject:string];
  }
}

I use an NSMutableSet for determining if I've already come across this entry before (as opposed to [unique containsObject:string], since a set will have O(1) lookup time, and an array has O(n) lookup time. If you're only dealing with a small number of objects, then this won't matter. However, if the source array is very large, then using the set to determine uniqueness may add a bit of a speed boost. (however, you should use Instruments to profile your code and see if it's necessary)

Rackley answered 17/11, 2010 at 22:35 Comment(2)
Really not the most intuitive thing that @distinctUnionOfObjects messes up the order (implementations just seems to do the old setWithArray: / allObjects), as it takes and returns an array.Sim
Thanks you for this post. I found it very helpful, however, I also discovered if you are just trying to load a second array from the first array with no duplicates then you do not need NSMutableSet. you can just use the for loop and the if statement to check each object and add it if necessary. Very useful. Thank youNeille
D
50

You could do like this:

NSArray * uniqueArray = [[NSOrderedSet orderedSetWithArray:duplicatesArray] array];

This way, you also preserve the order!

Dikdik answered 27/12, 2011 at 5:23 Comment(2)
that's right but it should be NSArray * uniqueArray = [[NSOrderedSet orderedSetWithArray:duplicatesArray] allObjects]Pyuria
@Pyuria You would be correct if it is NSSet, but the answer uses NSOrderedSet which does not have an allObjects property, and provides the array property instead. developer.apple.com/documentation/foundation/nsorderedset/…Claver
M
7

I Think You can Do this With that

NSArray * uniqueArray = [[Yourarray valueForKeyPath:@"@distinctUnionOfObjects.self"] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)];

i hope this would help you

Mcnary answered 27/2, 2013 at 4:58 Comment(1)
+1: This is what I was looking for. However, this doesn't fulfill the requirements of the asker as this won't keep the order of the original array... (1)[Yourarray valueForKeyPath:@"@distinctUnionOfObjects.self"] doesn't respect order and (2) sortedArrayUsingSelector:@selector(caseInsensitiveCompare:) obviously sorts the array... If you're looking for a way to "filter out duplicates and alphabetically sort the final array", this is the one for you. :DPuerperal
R
0

Hmm.. you could just use a loop ?

NSMutableArray *newarray = [[NSMutableArray alloc] init];
NSString *laststring = nil;
for (NSString *currentstring in oldarray) 
{
   if (![currentstring isEqualtoString:laststring]) [newarray addObject:currentstring];
   laststring = currentstring
}
Rodarte answered 17/11, 2010 at 22:41 Comment(2)
thanks .. it depends if the original array is really ordered that way... if something like ( "one", "one", "two", "one", "three" ) is possible as the original array it won't work.Rodarte
Um, but that only works if identical elements are always adjacent in the array, right? In this case this seems to be true, but who knows...Blouin
H
0

Here's a nice category that defines a custom operator like @distinctUnionOfObjects, except it only works on strings and it will maintain their original order. Note: It does not sort the strings for you. It leaves intact only the first instance of whatever strings are repeated.

Usage:

#import "NSArray+orderedDistinctUnionOfStrings.h"
...
// if you feed it an array that has already been ordered, it will work as expected
NSArray *myArray = @[@"ONE", @"ONE", @"ONE", @"TWO", @"THREE", @"THREE"];
NSArray *myUniqueArray = [myArray valueForKeyPath:@"@orderedDistinctUnionOfStrings.self"];

Output:

myUniqueArray = ( "ONE", "TWO", "THREE" )

.h file:

#import <Foundation/Foundation.h>

@interface NSArray (orderedDistinctUnionOfStrings)

@end

.m file:

#import "NSArray+orderedDistinctUnionOfObjects.h"

@implementation NSArray (orderedDistinctUnionOfObjects)

- (id) _orderedDistinctUnionOfStringsForKeyPath:(NSString*)keyPath {
    NSMutableIndexSet *removeIndexes = [NSMutableIndexSet indexSet];

    for (NSUInteger i = 0, n = self.count; i < n; ++i) {
        if ([removeIndexes containsIndex:i]) {
            continue;
        }
        NSString *str1 = [[self objectAtIndex:i] valueForKeyPath:keyPath];

        for (NSUInteger j = i+1; j < n; ++j) {
            if ([removeIndexes containsIndex:j]) {
                continue;
            }
            id obj = [self objectAtIndex:j];
            NSString *str2 = [obj valueForKeyPath:keyPath];
            if ([str1 isEqualToString:str2]) {
                [removeIndexes addIndex:j];
            }
        }
    }

    NSMutableArray *myMutableCopy = [self mutableCopy];
    [myMutableCopy removeObjectsAtIndexes:removeIndexes];

    return [[NSArray arrayWithArray:myMutableCopy] valueForKeyPath:[NSString stringWithFormat:@"@unionOfObjects.%@", keyPath]];
}

@end

And here is an excellent read on how to generate your own operators, and demystifies (by a little bit) how this works: http://bou.io/KVCCustomOperators.html

Herra answered 4/8, 2015 at 7:20 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.