7
\$\begingroup\$

I'm verifying South African IDs with jQuery, the code below works but Id like to know if it could be simplified in anyway or if there is a better way of doing things here?

South African IDs are verified as follows:

Using ID Number 8001015009087 as an example:
Add all the digits in the odd positions (excluding last digit).
8 + 0 + 0 + 5 + 0 + 0 = 13  .............................[1]

Move the even positions into a field and multiply the number by 2.
011098 x 2 = 22196  .....................................[2]

Add the digits of the result in [2].
2 + 2 + 1 + 9 + 6 = 20  .................................[3]

Add the answer in [3] to the answer in [1].
13 + 20 = 33  ...........................................[4]

Subtract the second digit of [4](i.e. 3) from 10. The number must tally with 
the last number in the ID Number. If the result is 2 digits, the last digit is 
used to compare against the last number in the ID Number. If the answer differs, 
the ID number is invalid.

And the code:

var me = $("#id");
var odd = new Number();
var even_string = new String();
var even_result = new Number();
var even = new Number();
var result = new Number();

// Check length
if( me.val().length == 13 )
{
    $.each(me.val(), function(p,v){
        if (p%2 == 0 & p != 12)
        {
            odd += Number(v); // --> 1. Add all odd positions except the last one.
        }
        else
        {
            if(p != 12)
            {
                even_string += String(v); // --> 2.1 Join all even positions.
            }
        }
    });

    // 2.2 Multiply even string by two.
    even_result = (even_string * 2);

    // 3. Add the digits of the new even result in two point two.
    $.each(String(even_result), function(p,v){
        even += Number(v);
    });

    // 4. Add answer in three to the answer in one.
    result = odd+even;

    // 5. Subtract the second digit from step 4. from ten.
    result = 10 - Number(String(result).substr(1,1));

    // 6. Make sure we use the very last digit on the result.
    if( (String(result)).length > 1 )
    {
        result = Number( (String(result)).charAt( (String(result)).length-1 ) );
    }

    // 6. The final check
    if( Number(result) != me.val().substr(12,1) )
    {
        // Not a valid South African ID
        alert("Your South African ID is not valid.");
    }
};
\$\endgroup\$
5
  • \$\begingroup\$ You can beautify (better indenting and spacing) or uglify (compress) it, I don't see why make changes to a fully working code which is as simple as that though. \$\endgroup\$
    – Fabrício Matté
    Commented Jun 19, 2012 at 14:13
  • \$\begingroup\$ First, read on the differences between == and ===. Second, remove all of the == and != you are using that needs no type conversion. \$\endgroup\$
    – rlemon
    Commented Jun 19, 2012 at 14:24
  • \$\begingroup\$ You also can avoid your dependance to jQuery by using document.getElementById element.value and for. \$\endgroup\$
    – dievardump
    Commented Jun 19, 2012 at 14:27
  • \$\begingroup\$ Are all ID numbers's the same length? \$\endgroup\$ Commented Jun 19, 2012 at 14:29
  • 1
    \$\begingroup\$ Also when you say sum the digits in odd positions for step 1, the index goes from the right to the left correct? So the second digit from the right would be index 0? Which is why the third digit from the right "0" is index 1 and is included as part of component 1? Correct? \$\endgroup\$ Commented Jun 19, 2012 at 14:33

4 Answers 4

4
\$\begingroup\$
  • jQuery is entirely unnecessary and will just make your solution slower
  • Don't initialize to new Class() - this is not Java. Instead, initialize to the initial value, or undefined (var x;).
  • use a function to encapsulate your verification logic, and to separate it from the DOM interface.
  • don't call me.val() more than once; cache the result
  • instead of the slow $.each, use a simpler loop
  • You already know the length of the id string, so you can get rid of the p%2 check and simply operate twice on each loop, once on the current position and once on p + 1, and increment by 2 each time (this also results in half as many iterations, speeding up the loop even further).
  • Cast using +string (resulting in a number) and '' + number (resulting in a string)
  • split the string into the first 12 and the last 1 at the beginning, so you don't have to keep excluding the last character and re-extracting it.
  • 'string'.slice(-1) is a more concise way to access the last character in a string
  • check to make sure that not only is the string 13 characters long, but also that every character is a numeral

With all that and a few stylistic preferences, here's my take on your problem:

function isValidSAID(id) {
    var i, c,
        even = '',
        sum = 0,
        check = id.slice(-1);

    if (id.length != 13 || id.match(/\D/)) {
        return false;
    }
    id = id.substr(0, id.length - 1);
    for (i = 0; c = id.charAt(i); i += 2) {
        sum += +c;
        even += id.charAt(i + 1);
    }
    even = '' + even * 2;
    for (i = 0; c = even.charAt(i); i++) {
        sum += +c;
    }
    sum = 10 - ('' + sum).charAt(1);
    return ('' + sum).slice(-1) == check;
}

if (!isValidSAID(document.getElementById('id').innerHTML)) {
    alert('Your South African ID is not valid.');
}
\$\endgroup\$
2
  • \$\begingroup\$ IE bug, use: check = id.slice(-1); \$\endgroup\$
    – CodeChap
    Commented Sep 10, 2012 at 11:48
  • \$\begingroup\$ @Derrick thanks. You'd think I would know to always check IE by now. Answer fixed. \$\endgroup\$
    – delete me
    Commented Sep 12, 2012 at 2:46
3
\$\begingroup\$

Keep in mind that the first 6 digits of a South African ID are the birthdate of the person, so it might be helpful if you check that these are a valid date as well. In your example here, the birthdate would be 1 January 1980 (YYMMDD).

\$\endgroup\$
1
\$\begingroup\$

I like that you keep your algorithm simple, rather than condensing it.
Here is how I would refactor it:

// Wrapped in a self-executing function, not to polute the global scope.
(function (isNan, $, console) {
    "use strict";
    function validateId(id) {
        var odd_checksum = 0,
            even_digits = "",
            even_intermediate,
            even_checksum = 0,
            result = 0,
            lastDigit;

        // Check length
        if (id.length !== 13) {
            return false;
        }

        lastDigit = +id.charAt(12);
        if (isNan(lastDigit)) {
            return false;
        }

        $.each(id, function (i, digit) {
            if (i === 12) {
                return false; // end
            }
            if (i % 2 === 0) {
                odd_checksum += +digit; // --> 1. Add all odd positions except the last one.
            } else {
                even_digits += digit.toString(); // --> 2.1 Join all even positions.
            }
        });

        // 2.2 Multiply even string by two.
        even_intermediate = 2 * +even_digits;

        // 3. Add the digits of the new even result in two point two.
        $.each(even_intermediate.toString(), function (i, digit) {
            even_checksum += +digit;
        });

        // 4. Add answer in three to the answer in one.
        result = odd_checksum + even_checksum;
        // If there was any char not a digit, we have NaN here. ( i + NaN === NaN)
        if (isNaN(result)) {
            return false;
        }

        // 5. Subtract the second digit from step 4. from ten.
        result = result.toString();
        if (result.length > 1) {
            result = +result.charAt(1);
        } else {
            result = 0;
        }
        result = 10 - result;

        // 6. Make sure we use the very last digit on the result.
        if (result === 10) {
            result = 0;
        }

        // 6. The final check
        if (result !== lastDigit) {
            return false;
        }

        return true; // success!
    }

    // Testing.
    var test_ids = [
            "8001015009087",
            "8001015009080"
        ],
        test;
    // Seems to work.
    for (test in test_ids) {
        test = test_ids[test];
        if (validateId(test)) {
            console.log("Your South African ID is valid:" + test);
        } else {
            console.log("Your South African ID is NOT valid:" + test);
        }
    }
} (isNaN, $, window.console || { log: function (msg) { alert(msg); } }));
\$\endgroup\$
0
\$\begingroup\$

I don't know if it's simpler or more readable, it's kind of in the eye of the beholder. the immediate algorithm is more succinct. However it's still a little convoluted.

You can decide for yourself, but here is a different way of writing it.

http://jsfiddle.net/BJhSp/1/

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.