Search code examples
iosobjective-ccocoa-touch

Base 62 conversion in Objective-C


I spent much too much time trying to find an implementation for base 62 conversion for Objective-C. I am sure this is a terrible example and there must be an elegant, super-efficient way to do this, but this works, please edit or answer to improve it! But I wanted to help people searching for this to have something that will work. There doesn't appear to be anything specific to be found for an Objective-C implementation.

@implementation Base62Converter

+(int)decode:(NSString*)string
{
    int num = 0;
    NSString * alphabet = @"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";

    for (int i = 0, len = [string length]; i < len; i++)
    {
        NSRange range = [alphabet rangeOfString:[string substringWithRange:NSMakeRange(i,1)]];
        num = num * 62 + range.location;
    }

    return num;
}

+(NSString*)encode:(int)num
{
    NSString * alphabet = @"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
    NSMutableString * precursor = [NSMutableString stringWithCapacity:3];

    while (num > 0)
    {
        [precursor appendString:[alphabet substringWithRange:NSMakeRange( num % 62, 1 )]];
        num /= 62;
    }

    // http://stackoverflow.com/questions/6720191/reverse-nsstring-text
    NSMutableString *reversedString = [NSMutableString stringWithCapacity:[precursor length]];

    [precursor enumerateSubstringsInRange:NSMakeRange(0,[precursor length])
                             options:(NSStringEnumerationReverse |NSStringEnumerationByComposedCharacterSequences)
                          usingBlock:^(NSString *substring, NSRange substringRange, NSRange enclosingRange, BOOL *stop) {
                              [reversedString appendString:substring];
                          }];
    return reversedString;
}

@end

Solution

  • Your code is fine. If anything, make it more generic. Here is a recursive version for any base (same code):

    #import <Foundation/Foundation.h>
    
    @interface BaseConversion : NSObject
    +(NSString*) formatNumber:(NSUInteger)n toBase:(NSUInteger)base;
    +(NSString*) formatNumber:(NSUInteger)n usingAlphabet:(NSString*)alphabet;
    @end
     
    @implementation BaseConversion
    
    // Uses the alphabet length as base.
    +(NSString*) formatNumber:(NSUInteger)n usingAlphabet:(NSString*)alphabet
    {
        NSUInteger base = [alphabet length];
        if (n<base){
            // direct conversion
            NSRange range = NSMakeRange(n, 1);
            return [alphabet substringWithRange:range];
        } else {
            return [NSString stringWithFormat:@"%@%@",
    
                    // Get the number minus the last digit and do a recursive call.
                    // Note that division between integer drops the decimals, eg: 769/10 = 76
                    [self formatNumber:n/base usingAlphabet:alphabet],
    
                    // Get the last digit and perform direct conversion with the result.
                    [alphabet substringWithRange:NSMakeRange(n%base, 1)]];
        }
    }
    
    +(NSString*) formatNumber:(NSUInteger)n toBase:(NSUInteger)base 
    {
        NSString *alphabet = @"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; // 62 digits
        NSAssert([alphabet length]>=base,@"Not enough characters. Use base %ld or lower.",(unsigned long)[alphabet length]);
        return [self formatNumber:n usingAlphabet:[alphabet substringWithRange:NSMakeRange (0, base)]];
    }
     
    @end
     
    int main(int argc, char *argv[]) {
        @autoreleasepool {
            NSLog(@"%@",[BaseConversion formatNumber:3735928559 toBase:16]); // deadbeef
            return EXIT_SUCCESS;
        }
    }
    

    Swift version: https://gist.github.com/janodev/9fb63a408251e66d7f1756f2537bc4b6