🎉 Exercism Research is now launched. Help Exercism, help science and have some fun at research.exercism.io 🎉
Avatar of ingemar

ingemar's solution

to Luhn in the Objective-C Track

Published at Jul 24 2019 · 1 comment
Instructions
Test suite
Solution

Given a number determine whether or not it is valid per the Luhn formula.

The Luhn algorithm is a simple checksum formula used to validate a variety of identification numbers, such as credit card numbers and Canadian Social Insurance Numbers.

The task is to check if a given string is valid.

Validating a Number

Strings of length 1 or less are not valid. Spaces are allowed in the input, but they should be stripped before checking. All other non-digit characters are disallowed.

Example 1: valid credit card number

4539 1488 0343 6467

The first step of the Luhn algorithm is to double every second digit, starting from the right. We will be doubling

4_3_ 1_8_ 0_4_ 6_6_

If doubling the number results in a number greater than 9 then subtract 9 from the product. The results of our doubling:

8569 2478 0383 3437

Then sum all of the digits:

8+5+6+9+2+4+7+8+0+3+8+3+3+4+3+7 = 80

If the sum is evenly divisible by 10, then the number is valid. This number is valid!

Example 2: invalid credit card number

8273 1232 7352 0569

Double the second digits, starting from the right

7253 2262 5312 0539

Sum the digits

7+2+5+3+2+2+6+2+5+3+1+2+0+5+3+9 = 57

57 is not evenly divisible by 10, so this number is not valid.

Setup

There are two different methods of getting set up to run the tests with Objective-C:

  • Create an Xcode project with a test target which will run the tests.
  • Use the ruby gem objc as a test runner utility.

Both are described in more detail here: http://exercism.io/languages/objective-c

Submitting Exercises

When submitting an exercise, make sure your solution file is in the same directory as the test code.

The submit command will look something like:

exercism submit <path-to-exercism-workspace>/objective-c/luhn/Luhn.m

You can find the Exercism workspace by running exercism debug and looking for the line beginning with Workspace.

Source

The Luhn Algorithm on Wikipedia http://en.wikipedia.org/wiki/Luhn_algorithm

Submitting Incomplete Solutions

It's possible to submit an incomplete solution so you can see how others have completed the exercise.

LuhnTest.m

#import <XCTest/XCTest.h>

#if __has_include("LuhnExample.h")
# import "LuhnExample.h"
# else
# import "Luhn.h"
#endif

@interface LuhnTest : XCTestCase

@end

@implementation LuhnTest

- (void)testSingleDigitStringNotValid {
    Luhn *luhn = [[Luhn alloc] initWithString:@"1"];
    XCTAssertFalse(luhn.isValid);
}

- (void)testSingleZeroInvalid {
    Luhn *luhn = [[Luhn alloc] initWithString:@"0"];
    XCTAssertFalse(luhn.isValid);
}

- (void)testSimpleValidSINRemainsValidIfReversed {
    Luhn *luhn = [[Luhn alloc] initWithString:@"059"];
    XCTAssertTrue(luhn.isValid);
}

- (void)testSimpleValidSINInvalidIfReversed {
    Luhn *luhn = [[Luhn alloc] initWithString:@"59"];
    XCTAssertTrue(luhn.isValid);
}

- (void)testValidCanadianSIN {
    Luhn *luhn = [[Luhn alloc] initWithString:@"055 444 285"];
    XCTAssertTrue(luhn.isValid);
}

- (void)testInvalidCanadianSIN {
    Luhn *luhn = [[Luhn alloc] initWithString:@"055 444 286"];
    XCTAssertFalse(luhn.isValid);
}

- (void)testInvalidCreditCard {
    Luhn *luhn = [[Luhn alloc] initWithString:@"8273 1232 7352 0569"];
    XCTAssertFalse(luhn.isValid);
}

- (void)testValidStringsWithANonDigitInvalid {
    Luhn *luhn = [[Luhn alloc] initWithString:@"055a 444 285"];
    XCTAssertFalse(luhn.isValid);
}

- (void)testValidStringsWithPunctuationInvalid {
    Luhn *luhn = [[Luhn alloc] initWithString:@"055-444-285"];
    XCTAssertFalse(luhn.isValid);
}

- (void)testValidStringWithSymbolsInvalid {
    Luhn *luhn = [[Luhn alloc] initWithString:@"055£ 444$ 285"];
    XCTAssertFalse(luhn.isValid);
}

- (void)testSingleZeroWithSpaceInvalid {
    Luhn *luhn = [[Luhn alloc] initWithString:@" 0"];
    XCTAssertFalse(luhn.isValid);
}

- (void)testMoreThanOneZeroValid {
    Luhn *luhn = [[Luhn alloc] initWithString:@"0000 0"];
    XCTAssertTrue(luhn.isValid);
}

- (void)testInputDigit9CorrectConverted {
    Luhn *luhn = [[Luhn alloc] initWithString:@"091"];
    XCTAssertTrue(luhn.isValid);
}

@end

luhn-tests/Luhn.h

//
//  Luhn.h
//  luhn-tests
//
//  Created by Ingemar Pertl on 23.07.19.
//  Copyright © 2019 Ingemar Pertl. All rights reserved.
//

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface Luhn : NSObject

@property (nonatomic) BOOL isValid;

- (instancetype)initWithString:(NSString *)number;

@end

NS_ASSUME_NONNULL_END

luhn-tests/Luhn.m

//
//  Luhn.m
//  luhn-tests
//
//  Created by Ingemar Pertl on 23.07.19.
//  Copyright © 2019 Ingemar Pertl. All rights reserved.
//

#import "Luhn.h"

@interface Luhn ()

@property (nonatomic, copy) NSString *numberAsString;

- (void)checkLuhn;

- (NSNumber *)doubleByLuhn:(int)number;

@end

@implementation Luhn

- (instancetype)initWithString:(NSString *)input {
    self = [super init];
    self.numberAsString = input;
    
    [self checkLuhn];

    return self;
}

- (void)checkLuhn {
    // remove all spaces
    NSString *cleanedInput = [[self.numberAsString componentsSeparatedByCharactersInSet:
                               [NSCharacterSet whitespaceCharacterSet]] componentsJoinedByString:@""];
    
    if(cleanedInput.length <= 1){
        self.isValid = false;
        return;
    }
    
    NSMutableCharacterSet *characterSet = [NSMutableCharacterSet letterCharacterSet];
    [characterSet formUnionWithCharacterSet:[NSMutableCharacterSet punctuationCharacterSet]];
    if([cleanedInput rangeOfCharacterFromSet:characterSet].location != NSNotFound) {
        self.isValid = false;
        return;
    }
    
    // double every second digit
    for (int i = (int)cleanedInput.length - 2; i >= 0; i -= 2) {
        NSNumber *doubled = [self doubleByLuhn:[cleanedInput characterAtIndex:i] - '0'];
        cleanedInput = [cleanedInput stringByReplacingCharactersInRange:NSMakeRange(i, 1) withString:[NSString stringWithFormat:@"%@",doubled]];
    }
    
    // sum all of the digits
    int sum = 0;
    for(int i = 0; i < (int)cleanedInput.length; i++) {
        sum += [cleanedInput characterAtIndex:i] - '0';
    }
    
    self.isValid = sum % 10 == 0;
}

- (NSNumber *)doubleByLuhn:(int)number {
    int doubled = number * 2;
    if(doubled > 9)
        doubled -= 9;
    
    return [NSNumber numberWithInt:doubled];
}

@end

Community comments

Find this solution interesting? Ask the author a question to learn more.
Avatar of ingemar

forget my question in the reflection panel ;-) I took a look at the community solutions and found some nice input! And one zero is invalid because one digit itself is invalid!

ingemar's Reflection

I've a problem with some test cases:
testSingleZeroWithSpaceInvalid and testMoreThanOneZeroValid

I don't understand why one zero is invalid and multiple are valid? The sum in the end is zero for both, right?