Heuristic
Heuristic

Reputation: 5341

ObjC: count of each string in an array?

Say I have an NSArray:

@[@"Hello", @"World", @"Hello"]

Since there are two 'Hello's, I need to convert the array into:

@[@"Hello(1)", @"World", @"Hello(2)"]

So 'Hello' has number appended, but 'World' won't have (1) appended as there is only one.

I know I can create a dictionary with the words as keys and iterate through the array, increment the count as their values, but is there a better way?

edit:

Yes I do need the result array to have the same order for words as the input array, the reason for this is that I'm making a vocabulary app for non-English speaking students, one of the feature is to have a translation(in their own language) of a sentence and a list of shuffled words for the sentence, they can then choose the words in the correct order from the list. So you may see the translation of 'How do you do?' in another language and the list of words: 'do' 'you' 'how' 'do', and the students need to select the words in the order of 'How do you do?', that's why I need to distinguish the order of same words.

Upvotes: 1

Views: 167

Answers (2)

Dave DeLong
Dave DeLong

Reputation: 243156

NSCountedSet is definitely the way to go. You can do it in a single pass if you use two of them: one to know how many there are total, and one to track how many you've seen so far:

NSArray *a = @[@"Hello", @"World", @"Hello"];
NSCountedSet *counts = [NSCountedSet setWithArray:a];

NSMutableArray *final = [NSMutableArray array];
NSCountedSet *countsSoFar = [NSCountedSet set];

for (NSString *str in a) {
    if ([counts countForObject:str] > 1) {
        // this object appears more than once.  append the number of times it's appeared so far
        [countsSoFar addObject:str];
        NSUInteger countSoFar = [countsSoFar countForObject:str];
        str = [str stringByAppendingFormat:@"(%ld)", countSoFar];
    }
    [final addObject:str];
}

NSLog(@"%@", final);

This logs:

(
  "Hello(1)",
  World,
  "Hello(2)"
)

Upvotes: 1

nielsbot
nielsbot

Reputation: 16032

Maybe use an NSCountedSet (which is equivalent of a what you might implement with NSDictionary)

-(NSArray*)renameStrings:(NSArray*)strings
{
    NSCountedSet * set = [ [ NSCountedSet alloc ] initWithArray:strings ];

    NSMutableArray * result = [ NSMutableArray arrayWithCapacity:[ strings count ] ] ;

    for( id object in set )
    {
        NSUInteger count = [ set countForObject:object ] ;
        if ( count == 1 )
        {
            [ result addObject:object ] ;
        }
        else
        {
            NSUInteger index= 0 ;
            while( index < count )
            {
                ++index ;
                [ result addObject:[ NSString stringWithFormat:@"%@(%lu)", object, index ] ] ;
            }
        }
    }

    return result ;
}

(The result may be ordered differently than the input)

Addendum:

Here's a one-pass solution that also maintains the order of the input (just for "fun"):

struct Tree
{
    struct Tree * left ;
    struct Tree * right ;
    NSUInteger count ;
    NSUInteger firstIndex ;
    CFStringRef value ;
};

void TreeInsert( struct Tree * tree, NSMutableArray * resultArray, NSString * stringToInsert, NSUInteger stringIndex )
{
    switch( CFStringCompare( (__bridge CFStringRef)stringToInsert , tree->value, 0 ) )
    {
        case NSOrderedAscending:
        {
            if ( tree->right )
            {
                TreeInsert( tree->right, resultArray, stringToInsert ,stringIndex ) ;
            }
            else
            {
                tree->right = malloc( sizeof( struct Tree ), 1 ) ;
                *tree->right = (struct Tree){ NULL, NULL, 1, stringIndex, CFBridgingRetain( stringToInsert ) } ;
                [ resultArray addObject:stringToInsert ] ;
            }
            break ;
        }
        case NSOrderedDescending:
        {
            if ( tree->left )
            {
                TreeInsert( tree->left, resultArray, stringToInsert ,stringIndex ) ;
            }
            else
            {
                tree->left = malloc( sizeof( struct Tree ), 1 ) ;
                *tree->left = (struct Tree){ NULL, NULL, 1, stringIndex, CFBridgingRetain( stringToInsert ) } ;
                [ resultArray addObject:stringToInsert ] ;
            }
            break ;
        }
        default:
        {
            ++tree->count ;
            if ( tree->firstIndex != NSNotFound )
            {
                NSString * string = [ NSString stringWithFormat:@"%@(1)", [ resultArray objectAtIndex:tree->firstIndex ] ] ;
                [ resultArray replaceObjectAtIndex:tree->firstIndex withObject:string ] ;
                tree->firstIndex = NSNotFound ;
            }
            [ resultArray addObject:[ NSString stringWithFormat:@"%@(%lu)", stringToInsert, tree->count ] ] ;
            break ;
        }
    }
}

void DisposeTree( struct Tree * tree )
{
    if ( tree->left ) { DisposeTree( tree->left ) ; }
    if ( tree->right ) { DisposeTree( tree->right ) ; }
    if ( tree->value ) { CFRelease( tree->value ) ; }

    free( tree ) ;
}

int main(int argc, const char * argv[])
{

    @autoreleasepool {

        NSArray * array = @[ @"Hello", @"Goodbye", @"Hello", @"Something else" ] ;
        NSMutableArray * result = [ NSMutableArray arrayWithCapacity:array.count ] ;

        struct Tree * tree = NULL ;

        NSEnumerator * enumerator = [ array objectEnumerator ] ;

        {
            NSString * firstString = [ enumerator nextObject ] ;
            tree = malloc( sizeof( struct Tree ), 1 ) ;
            *tree = (struct Tree){ NULL, NULL, 1, 0, CFBridgingRetain( firstString ) } ;
            [ result addObject:firstString ] ;
        }

        NSUInteger index = 1 ;
        for( NSString * string in enumerator )
        {
            TreeInsert( tree, result, string, index ) ;
            ++index ;
        }

        NSLog(@"result=%@\n", result ) ;
    }
}

Upvotes: 2

Related Questions