59

This should be easy, but I'm having a hard time finding the easiest solution.

I need an NSString that is equal to another string concatenated with itself a given number of times.

For a better explanation, consider the following python example:

>> original = "abc"
"abc"
>> times = 2
2
>> result = original * times
"abcabc"

Any hints?


EDIT:

I was going to post a solution similar to the one by Mike McMaster's answer, after looking at this implementation from the OmniFrameworks:

// returns a string consisting of 'aLenght' spaces
+ (NSString *)spacesOfLength:(unsigned int)aLength;
{
static NSMutableString *spaces = nil;
static NSLock *spacesLock;
static unsigned int spacesLength;

if (!spaces) {
spaces = [@"                " mutableCopy];
spacesLength = [spaces length];
    spacesLock = [[NSLock alloc] init];
}
if (spacesLength < aLength) {
    [spacesLock lock];
    while (spacesLength < aLength) {
        [spaces appendString:spaces];
        spacesLength += spacesLength;
    }
    [spacesLock unlock];
}
return [spaces substringToIndex:aLength];
}

Code reproduced from the file:

Frameworks/OmniFoundation/OpenStepExtensions.subproj/NSString-OFExtensions.m

on the OpenExtensions framework from the Omni Frameworks by The Omni Group.

Community
  • 1
  • 1
Sergio Acosta
  • 11,418
  • 12
  • 62
  • 91

5 Answers5

167

There is a method called stringByPaddingToLength:withString:startingAtIndex::

[@"" stringByPaddingToLength:100 withString: @"abc" startingAtIndex:0]

Note that if you want 3 abc's, than use 9 (3 * [@"abc" length]) or create category like this:

@interface NSString (Repeat)

- (NSString *)repeatTimes:(NSUInteger)times;

@end

@implementation NSString (Repeat)

- (NSString *)repeatTimes:(NSUInteger)times {
  return [@"" stringByPaddingToLength:times * [self length] withString:self startingAtIndex:0];
}

@end
tig
  • 25,841
  • 10
  • 64
  • 96
  • 4
    +1 for doing it all in a single call to the standard library. And here's how I used it to create a comma-separated list of question marks (for feeding to SQLite) -- `[@"" stringByPaddingToLength:[fields count]*2-1 withString:@"?," startingAtIndex:0]`. – noamtm Aug 07 '12 at 18:00
7
NSString *original = @"abc";
int times = 2;

// Capacity does not limit the length, it's just an initial capacity
NSMutableString *result = [NSMutableString stringWithCapacity:[original length] * times]; 

int i;
for (i = 0; i < times; i++)
    [result appendString:original];

NSLog(@"result: %@", result); // prints "abcabc"
Mike McMaster
  • 7,573
  • 8
  • 37
  • 42
  • I was trying to come up with something simpler, but I think this is the shortest way. – Sergio Acosta Nov 04 '08 at 05:21
  • I made it a little more efficient since the acceptance by specifiying times first and having the capacity = [original length] * times – Mike McMaster Nov 04 '08 at 05:21
  • 1
    Just for completeness, if for some reason you want to use large numbers, you can improve this by reusing power-of-two concatenations (e.g. foo * 10 = foo * 2 + foo * 8; foo * 8 = foo * 4 + foo * 4; foo * 4 = foo * 2 + foo * 2 -> 3 concat operations instead of 9). – Jens Ayton Nov 04 '08 at 09:14
  • This solution is not as efficient as top answer using `stringByPaddingToLength:withString:startingAtIndex:` – Cœur Jun 27 '16 at 06:43
4

For performance, you could drop into C with something like this:

+ (NSString*)stringWithRepeatCharacter:(char)character times:(unsigned int)repetitions;
{
    char repeatString[repetitions + 1];
    memset(repeatString, character, repetitions);

    // Set terminating null
    repeatString[repetitions] = 0;

    return [NSString stringWithCString:repeatString];
}

This could be written as a category extension on the NSString class. There are probably some checks that should be thrown in there, but this is the straight forward gist of it.

2

The first method above is for a single character. This one is for a string of characters. It could be used for a single character too but has more overhead.

+ (NSString*)stringWithRepeatString:(char*)characters times:(unsigned int)repetitions;
{
    unsigned int stringLength = strlen(characters);
    unsigned int repeatStringLength = stringLength * repetitions + 1;

    char repeatString[repeatStringLength];

    for (unsigned int i = 0; i < repetitions; i++) {
        unsigned int pointerPosition = i * repetitions;
        memcpy(repeatString + pointerPosition, characters, stringLength);       
    }

    // Set terminating null
    repeatString[repeatStringLength - 1] = 0;

    return [NSString stringWithCString:repeatString];
}
  • There's an error, I think, inside the for cycle: `unsigned int pointerPosition = i * repetitions;` should be `unsigned int pointerPosition = i * stringLength;` where **i * repetitions** becomes **i * stringLength**. – mginius May 11 '16 at 17:14
1

If you're using Cocoa in Python, then you can just do that, as PyObjC imbues NSString with all of the Python unicode class's abilities.

Otherwise, there are two ways.

One is to create an array with the same string in it n times, and use componentsJoinedByString:. Something like this:

NSMutableArray *repetitions = [NSMutableArray arrayWithCapacity:n];
for (NSUInteger i = 0UL; i < n; ++i)
    [repetitions addObject:inputString];
outputString = [repetitions componentsJoinedByString:@""];

The other way would be to start with an empty NSMutableString and append the string to it n times, like this:

NSMutableString *temp = [NSMutableString stringWithCapacity:[inputString length] * n];
for (NSUInteger i = 0UL; i < n; ++i)
    [temp appendString:inputString];
outputString = [NSString stringWithString:temp];

You may be able to cut out the stringWithString: call if it's OK for you to return a mutable string here. Otherwise, you probably should return an immutable string, and the stringWithString: message here means you have two copies of the string in memory.

Therefore, I recommend the componentsJoinedByString: solution.

[Edit: Borrowed idea to use …WithCapacity: methods from Mike McMaster's answer.]

Community
  • 1
  • 1
Peter Hosey
  • 95,783
  • 15
  • 211
  • 370