如何在C#中检查加拿大社会保险号码的有效性?

时间:2010-11-24 04:15:07

标签: c# algorithm

我被赋予了在C#编写算法的任务,该算法检查加拿大社会保险号(SIN)的有效性。以下是验证SIN的步骤。

给出一个示例编号:123 456 782

  1. 删除校验位(最后一位):12345678 2
  2. 提取偶数(2,4,6,8th digith):1 2 3 5 6 7 8
  3. 加倍:
        2  4  6  8
        |  |  |  |
        v  v  v  v
        4  8  12 16 
    
  4. 将数字加在一起:
    4+8+1+2+1+6 = 22
  5. 添加奇数位数:
        1+3+5+7 = 16
                               
          Total : 38

有效性算法

  1. 如果总数是10的倍数,则校验位应为零。
  2. 否则,从下一个最高倍数10(本例中为40)
  3. 中减去总计
  4. 此SIN的校验位必须等于之前的数字和总数的差异(在这种情况下,40-38 = 2;校验位为2,因此数字有效)
  5. 我迷失了如何在C#中实现这一点,我该怎么做?

8 个答案:

答案 0 :(得分:4)

这是一个很好的问题需要解决。这应该比转换为字符串和解析回整数更有效。此解决方案适用于.NET 3.5及更高版本。

    public static IEnumerable<int> ToDigitEnumerable(this int number)
    {
        IList<int> digits = new List<int>();

        while(number > 0)
        {
            digits.Add(number%10);
            number = number/10;
        }

        //digits are currently backwards, reverse the order
        return digits.Reverse();
    }

    public static bool IsCanadianSocialInsuranceNumber(int number)
    {
        var digits = number.ToDigitEnumerable();

        if (digits.Count() != 9) return false;

        //The left side of the addition is adding all even indexes (except the last digit).
        //We are adding even indexes since .NET uses base 0 for indexes

        //The right side of the addition, multiplies the odd index's value by 2, then breaks each result into
        //individual digits, then adds them together
        var total = digits.Where((value, index) => index%2 == 0 && index != 8).Sum()
                    + digits.Where((value, index) => index%2 != 0).Select(v => v*2)
                          .SelectMany(v => v.ToDigitEnumerable()).Sum();

        //The final modulous 10 operator is to handle the scenarios where the total
        //is divisble by 10, in those cases, the check sum should be 0, not 10
        var checkDigit = (10 - (total%10)) % 10;

        return digits.Last() == checkDigit;
    }

此解决方案的一个问题是它假设以整数表示的数字是9位数(不能以0开头)。如果数字可以以0开头,那么它必须表示为字符串(或转换为字符串并用零填充)。测试的逻辑基本上保持不变,但假定整数的部分需要用字符串换出,然后你就必须进行解析。

答案 1 :(得分:2)

我不知道C#,但这是Python的解决方案。也许你可以从中学习如何在C#中实现它的方法。

def check(SIN):
    SIN = ''.join(SIN.split(' '))
    if len(SIN) != 9:
        raise ValueError("A Canadian SIN must be 9 digits long")
    check_digit = int(SIN[-1])
    even_digits = [int(SIN[i]) for i in range(1,8,2)]
    odd_digits  = [int(SIN[i]) for i in range(0,8,2)]

    total = sum(i/10 + i%10 for i in map(lambda x: 2*x, even_digits)) + sum(odd_digits)

    if total%10 == 0:
        return check_digit == 0
    else:
        return ((total/10)+1)*10 - total == check_digit

if __name__ == '__main__':
    for SIN in ['123 456 782',
                '123 456 789',
                '046 454 286']:
        print '%s is %sa valid Canadian SIN' % (SIN, '' if check(SIN) else 'NOT ')

输出:

123 456 782 is a valid Canadian SIN
123 456 789 is NOT a valid Canadian SIN
046 454 286 is a valid Canadian SIN

答案 2 :(得分:1)

程序的关键是你需要有一些方法来迭代SIN中的每个整数。

由于最简单的方法是将整数转换为字符串以进行操作操作并返回整数以进行加法/乘法运算,因此我使用了以下方法:

该计划:

public class Program
{
    static void Main(string[] args)
    {
        int sn = 123456782;
        int[] Digits;
        int AddedResult = 0;
        string s = sn.ToString();
        string sa = s.Substring(s.Length - 1, 1);

        int checkDigit = Convert.ToInt32(sn.ToString().Substring(s.Length - 1, 1));
        //get the last digit.

        if (IsValidLength(sn))
        {

            sn = RemoveLastDigit(sn);
            Digits = ExtractEvenDigits(sn);
            Digits = DoubleDigits(Digits);
            AddedResult = AddedEvenDigits(Digits);
            AddedResult += AddOddDigits(sn);
            if (IsValidSN(AddedResult, checkDigit))
            {
                Console.WriteLine("The number is valid");
            }
            else
            {
                Console.WriteLine("The Number is not valid");
            }
        }
        else
        {
            Console.WriteLine("NotValidLength");
        }
        Console.Read();

    }

    public static bool IsValidSN(int AddedResult, int checkDigit)
    {
        return ((AddedResult % 10 == 0 && checkDigit == 0) || IsValidDifference(AddedResult, checkDigit));

    }

    public static bool IsValidDifference(int AddedResult, int checkDigit)
    {
        int nextHighestTens = AddedResult;
        while (nextHighestTens % 10 != 0)
        {
            nextHighestTens++;
        }
        return ((nextHighestTens - AddedResult) == checkDigit);
    }

    public static int AddOddDigits(int sn)
    {
        string s = sn.ToString();
        int i = 1;
        int addedResult = 0;
        foreach (char c in s)
        {
            if (i % 2 != 0)
            {
                addedResult += Convert.ToInt32(c.ToString());
            }
            i++;
        }

        return addedResult;
    }

    public static int AddedEvenDigits(int[] Digits)
    {
        int addedEvenDigits = 0;
        string s = "";
        for (int i = 0; i < Digits.Length; i++) //extract each digit. For example 12 is extracted as 1 and 2
        {
            s += Digits[i].ToString();
        }
        for (int i = 0; i < s.Length; i++) //now add all extracted digits
        {
            addedEvenDigits += Convert.ToInt32(s[i].ToString());
        }
        return addedEvenDigits;
    }

    public static int[] DoubleDigits(int[] Digits)
    {
        int[] doubledDigits = new int[Digits.Count()];
        for (int i = 0; i < Digits.Length; i++)
        {
            doubledDigits[i] = Digits[i] * 2;
        }
        return doubledDigits;
    }

    public static int[] ExtractEvenDigits(int sn)
    {
        int[] EvenDigits = new int[4];
        string s = sn.ToString(); //12345678

        int j = 0;
        for (int i = 1; i < s.Length; i += 2)
        {
            EvenDigits[j] = Convert.ToInt32(s[i].ToString());
            j++;
        }

        return EvenDigits;
    }

    public static int RemoveLastDigit(int sn)
    {
        string s = sn.ToString();
        return Convert.ToInt32(s.Substring(0, s.Count() - 1));
    }
    public static bool IsValidLength(int sn)
    {
        return (sn > 9999999 && sn < 1000000000);
    }
}

我在大约20分钟内写了这篇文章,所以上传并不值得。我计划将其作为练习进行改进,并为此编写了一些单元测试(我计划做得更好)。

[TestFixture]
public class SINTests
{
    private int SinNumber = 123456782;

    [Test]
    public void TestValidNumber()
    {
        Assert.IsTrue(Program.IsValidLength(SinNumber));
    }

    [Test]
    public void TestRemoveLastDigit()
    {
        Assert.AreEqual(12345678, Program.RemoveLastDigit(SinNumber));
    }

    [Test]
    public void TestExtractEvenDigit()
    {
        int sn = 12345678;
        int[] array = new int[] { 2,4,6,8 };
        Assert.AreEqual(array, Program.ExtractEvenDigits(sn));
    }

    [Test]
    public void TestAddOddDigits()
    {
        int sn = 12345678;
        int result = 1 + 3 + 5 + 7;
        Assert.AreEqual(result, Program.AddOddDigits(sn));
    }
    [Test]
    public void TestDoubleEvenDigits()
    {
        int sn = 12345678;
        int[] original = new int[] { 2, 4, 6, 8 };
        int[] array = new int[] { 4, 8, 12, 16 };
        Assert.AreEqual(array, Program.DoubleDigits(original));
    }
    [Test]
    public void TestOddDigits()
    {
        int sn = 12345678;
        Assert.AreEqual(16, Program.AddOddDigits(sn));
    }

}

由于字符串可以解释为字符数组 1 ,因此对字符串起作用的操作也需要注意将字符转换为整数与转换字符不同的事实。字符串到整数。例如:

Char c = '2';
int cInt = Convert.ToInt32(c); // returns 50
string s = c.ToString();
int sInt = Convert.ToInt32(s) //returns 2;

1 从技术上讲,字符串不是C#中的字符数组(尽管它在C和C ++中),但是因为你可以通过索引器访问字符串的组件,所以它可以是像一串人物一样对待。

答案 3 :(得分:1)

您给出的规范使事情变得比它们需要的要复杂得多:它实际上等同且更简单地将最后一个数字添加到校验和中并确保校验和的最后一个数字为0.

新程序员常遇到的麻烦是“如何获得每个数字?”方法如下:

  • 在整数类型中,% 10将删除除号码的最后一位以外的所有内容:123 % 10 == 3/ 10将删除该号码的最后一位数:123 / 10 == 12
  • 在字符串中,str[i] - '0'会为您提供索引为i的数字。数字的字符存储为特殊数字:'0'存储为48,'9'存储为57.如果减去48,则将实际数字作为数字。当然,你并不需要记住“减去48”:如果你只是减去'0',它会做同样的事情:'8' - '0' == 8

这是两种有效的方法。一个需要int并检查SIN的校验和。一个需要string并检查格式(必须是“ddd ddd ddd”)和SIN的校验和;虽然它非常有效,但它有点难看和重复。

// Checks that the given int is a valid Canadian Social Insurance Number
//   according to both range (000 000 000 to 999 999 998) and checksum.
public static bool IsValidSIN(int sin) {
  if (sin < 0 || sin > 999999998) return false;

  int checksum = 0;
  for (int i = 4; i != 0; i--) {
    checksum += sin % 10;
    sin /= 10;

    int addend = 2*(sin % 10); if (addend >= 10) addend -= 9;
    checksum += addend;
    sin /= 10;
  }

  return (checksum + sin) % 10 == 0;
}

// Checks that the given string is a valid Canadian Social Insurance Number
//   according to both format ("ddd ddd ddd") and checksum.
// Implementation note: uses an admittedly ugly and repetitive parser.
public static bool IsValidSIN(string sin) {
  if (sin.Length != 11) return false;

  int checksum, addend;

  checksum = sin[0] - '0';
  if (checksum < 0 || checksum > 9) return false;

  addend = 2*(sin[1] - '0'); if (addend >= 10) addend -= 9;
  if (addend < 0 || addend > 9) return false;
  checksum += addend;

  addend = sin[2] - '0';
  if (addend < 0 || addend > 9) return false;
  checksum += addend;

  if (sin[3] != ' ') return false;

  addend = 2*(sin[4] - '0'); if (addend >= 10) addend -= 9;
  if (addend < 0 || addend > 9) return false;
  checksum += addend;

  addend = sin[5] - '0';
  if (addend < 0 || addend > 9) return false;
  checksum += addend;

  addend = 2*(sin[6] - '0'); if (addend >= 10) addend -= 9;
  if (addend < 0 || addend > 9) return false;
  checksum += addend;

  if (sin[7] != ' ') return false;

  addend = sin[8] - '0';
  if (addend < 0 || addend > 9) return false;
  checksum += addend;

  addend = 2*(sin[9] - '0'); if (addend >= 10) addend -= 9;
  if (addend < 0 || addend > 9) return false;
  checksum += addend;

  addend = sin[10] - '0';
  if (addend < 0 || addend > 9) return false;

  return (checksum + addend) % 10 == 0;
}

答案 4 :(得分:1)

在互联网上搜索“Luhn算法”。你会发现很多例子。

答案 5 :(得分:1)

我最近还将其编码为一个应用程序。在调用之前,字符串sSIN已经通过正则表达式检查为9位数字。

public static bool IsCanadianSocialInsuranceNumber(string sSIN)
    {
        int iChecksum = 0;
        int iDigit = 0;

        for (int i = 0; i < sSIN.Length; i++)
        {
            // even number else odd
            if (((i+1) % 2) == 0)
            {
                iDigit = int.Parse(sSIN.Substring(i, 1))*2;
                iChecksum += (iDigit < 10) ? iDigit : iDigit - 9;
            }
            else
            {
                iChecksum += int.Parse(sSIN.Substring(i, 1));
            }
        }

        return ((iChecksum % 10) == 0) ? true : false;
    }

答案 6 :(得分:0)

public bool ValidateSIN(string sin)
{        
    if ((int)Char.GetNumericValue(sin[0]) == 0)
    {
        return false;
    }
    else
    {
        string evenString = "";
        int totalOfEvens = 0;
        int totalOfOdds = 0;
        int total, nextMultipleOfTen, remainder;
        int checkDigit = (int)Char.GetNumericValue(sin[8]);

        // multiply each even number of the input string by 2
        // get the resulting numbers into a string so the chars 
        // can be manipulated as individual digits
        for (int i = 1; i <= 7; i += 2)
        {
            evenString += (Char.GetNumericValue(sin[i]) * 2);
        }

        // add the individual digits of the products from the above loop
        foreach (char c in evenString)
        {
            totalOfEvens += (int)Char.GetNumericValue(c);
        }

        // get the odd numbers of the input string, minus the last number,
        // and add them together
        for (int i = 0; i <= 6; i += 2)
        {
            totalOfOdds += (int)Char.GetNumericValue(sin[i]);
        }

        total = totalOfEvens + totalOfOdds;

        // take the quotient of total divided by 10 and add 1 to get the next multiple of ten
        nextMultipleOfTen = (Math.DivRem(total, 10, out remainder) + 1) * 10;

        if ((total % 10 == 0 && checkDigit == 0) || (checkDigit == nextMultipleOfTen - total))
        {
            return true;
        }
        else
        {
            return false;
        }
    }
}      

答案 7 :(得分:0)

这是一个非常简单的方法:

int test = 123456782;
if(test > 100000000 && test < 999999999)
{
    int check = test % 10;
    string temp = "";
    foreach(char c in test.ToString().Substring(0, 8))
    {
 //The character codes for digits follow the same odd/even pattern as the digits.
 //This code puts each digit or its value times 2, into a string and sums the digits
 //after instead of keeping 2 separate totals
        if(c % 2 == 1)
        {
            temp += c;
        }
        else
        {
            temp += (int.Parse(c.ToString()) * 2).ToString();
        }
    }
    int temp2 = temp.Sum((x => int.Parse(x.ToString())));
//no need to compare the sum to the next 10, the modulus of 10 will work for this
    int temp2mod = temp2 % 10;
    if((temp2mod == 0 && temp2mod == check) || (10 - temp2mod == check))
        return true;
}
return false;