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

8
我被分配编写一个C#算法,以检查加拿大社会保险号码(SIN)的有效性。以下是验证SIN的步骤。
给定一个示例号码:123 456 782
  1. 删除校验位(最后一位):123456782
  2. 提取偶数位数字(第2、4、6、8位):12345678
  3. 将它们乘以2:
        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
    总和:38
有效性算法
  1. 如果总和是10的倍数,则校验位应为零。
  2. 否则,从比总和大的下一个10的倍数中减去总和(在本例中为40)。
  3. 此SIN的校验位必须等于数字与先前总和之差(在本例中,40-38 = 2;校验位为2,因此该数字有效)。
我不知道如何在C#中实现这个算法,请问怎么做?

5
你是在让我们帮你做作业吗? - George Stocker
7
好的,谢谢你告诉我们了,现在有什么问题吗? - Adriaan Stander
美国社会安全号码没有校验位。这是哪个国家的? - Joel Coehoorn
没关系,找到了:它是加拿大。 - Joel Coehoorn
2
就像是“危险辞海”游戏一样,他告诉我们答案,然后我们尝试提出问题。“什么是......验证社会安全号码的正确方式?” - jb.
显示剩余3条评论
9个回答

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开头,则必须表示为字符串(或转换为字符串并填充零)。测试逻辑将基本保持不变,但假定整数的部分将需要替换为字符串,然后您将需要进行解析。


2

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


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

供您参考,虽然上一个加拿大社会保险号(SIN)在技术上仍然有效,但以0开头的所有数字都未被加拿大政府使用... - Mike Verrier
确实,这是一个虚构但有效的社会保险号码。这就是我在这里用它进行演示目的的原因(它是在维基百科上使用的相同社会保险号码)。 - BioGeek

2
您收到的规范让事情变得比必要的更加复杂:实际上,只需将最后一位数字添加到校验和中,并确保校验和的最后一位为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并检查SIN的格式(必须为“ddd ddd ddd”)和校验和;虽然它相当有效,但有点丑陋和重复。
// 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;
}

1

迄今为止我尝试过的最快方法。没有LINQ,没有if / else,没有奇偶检查,只有一个循环从字符串获取整数数组。

注意:没有保护措施-输入被认为是9个数字的字符串。

public static bool IsValidSin(string input)
{
    int[] luhnMap = { 0, 2, 4, 6, 8, 1, 3, 5, 7, 9 };
    int[] ints = new int[9];
    for (int i = 0; i < 9; i++)
    {
        ints[i] = int.Parse(input[i].ToString());
    }
    int check = ints[0] + luhnMap[ints[1]] + ints[2] + luhnMap[ints[3]] + ints[4] + luhnMap[ints[5]] + ints[6] + luhnMap[ints[7]] + ints[8];
    return (check % 10) == 0;
}

无法为此 046454286 工作。 - Waqas Ahmed
@WaqasAhmed 这在我的电脑上可以运行。 - Vince

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++中是),但由于可以通过索引器访问字符串的组件,因此它可以像字符数组一样处理。


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;
    }

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;
        }
    }
}      

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;

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接