{{ message }}

Instantly share code, notes, and snippets.

Created Dec 17, 2015

# Check Digit of MyNumber

## How to calculate the check digit of mynumber

\begin{align*} d &= 11 - \Biggl( \sum^{11}{n=1} P_n \times Q_n \Biggr) \bmod 11 \ & \text{ただし，$\Biggl( \sum^{11}{n=1} P_n \times Q_n \Biggr) \bmod 11 \leq 1$の場合は$d=0$} \ P_n &= \text{ 個人番号を構成する検査用数字以外の十一桁の番号の最下位の桁を1桁目としたときの$n$桁目の数字} \ Q_n &= \begin{cases} n + 1 && \text{if $1 \leq n \leq 6$} \ n - 5 && \text{if $7 \leq n \leq 11$} \end{cases} \end{align*}

## Naive Implementation

class MynumberValidator
def validate(num)
d = num.to_s.chars.reverse.map(&:to_i)

return false if d.size != 12

cd = d.shift

sum = 0

1.upto(11) do |i|
sum += d[i-1] * (i <= 6 ? i + 1 : i - 5)
end

sum %= 11

cd == (sum <= 1 ? 0 : 11 - sum)
end
end

require 'test/unit'
extend Test::Unit::Assertions

validator = MynumberValidator.new

assert_equal validator.validate(12345678901),     false
assert_equal validator.validate(123456789012),    false
assert_equal validator.validate(123456789018),    true
assert_equal validator.validate(123456789001),    false
assert_equal validator.validate(123456789000),    true
assert_equal validator.validate("123456789012"),  false
assert_equal validator.validate("023456789013"),  true

## Ruby-ish Implementation

class MynumberValidator
def validate(num)
d = num.to_s.chars.map(&:to_i)
return false if d.size != 12
cd = d.pop
sum = d.reverse.map.with_index(1) { |n, i| n * (i <= 6 ? i+1 : i-5) }.inject(0, :+) % 11
cd == (sum <= 1 ? 0 : 11 - sum)
end
end

require 'test/unit'
extend Test::Unit::Assertions

validator = MynumberValidator.new

assert_equal validator.validate(12345678901),     false
assert_equal validator.validate(123456789012),    false
assert_equal validator.validate(123456789018),    true
assert_equal validator.validate(123456789001),    false
assert_equal validator.validate(123456789000),    true
assert_equal validator.validate("123456789012"),  false
assert_equal validator.validate("023456789013"),  true

## OOP-like Implementation

class MyNumberValidator
MYNUMBER_LENGTH = 12

def validate(num)
@my_number = num.to_s.chars.map(&:to_i)

validate_length && validate_check_digit
end

private

def digits
@my_number[0..-2].reverse
end

def check_digit
@my_number[-1]
end

def validate_length
digits.size != MYNUMBER_LENGTH
end

def validate_check_digit
q_n = [*(2..7), *(2..6)]
sum = digits.zip(q_n).inject(0) { |sum, (p, q)| sum + p * q } % 11
check_digit == (sum <= 1 ? 0 : 11 - sum)
end
end

require 'test/unit'
extend Test::Unit::Assertions

validator = MyNumberValidator.new

assert_equal validator.validate(12345678901),     false
assert_equal validator.validate(123456789012),    false
assert_equal validator.validate(123456789018),    true
assert_equal validator.validate(123456789001),    false
assert_equal validator.validate(123456789000),    true
assert_equal validator.validate("123456789012"),  false
assert_equal validator.validate("023456789013"),  true
<main>:1: warning: already initialized constant MyNumberValidator::MYNUMBER_LENGTH
<main>:1: warning: previous definition of MYNUMBER_LENGTH was here


## More Short Implementation

class MynumberValidator
def validate(num)
d=num.to_s.chars.map(&:to_i)
d.size==12&&d.pop==(11-d.reverse.zip((2..7).cycle).inject(0){|s,(n,m)|s+n*m}%11).tap{|cd| break 0 if cd>9}
end
end

require 'test/unit'
extend Test::Unit::Assertions

validator = MynumberValidator.new

assert_equal validator.validate(12345678901),     false
assert_equal validator.validate(123456789012),    false
assert_equal validator.validate(123456789018),    true
assert_equal validator.validate(123456789001),    false
assert_equal validator.validate(123456789000),    true
assert_equal validator.validate("123456789012"),  false
assert_equal validator.validate("023456789013"),  true

### Explanations

#### $Q_n$の計算

\begin{align*} Q_n &= \begin{cases} n + 1 && \text{if $1 \leq n \leq 6$} \ n - 5 && \text{if $7 \leq n \leq 11$} \end{cases} \end{align*}

(1..11).each do |i|
puts "Q_#{"%02d" % i} = #{i <= 6 ? i + 1 : i - 5}"
end
Q_01 = 2
Q_02 = 3
Q_03 = 4
Q_04 = 5
Q_05 = 6
Q_06 = 7
Q_07 = 2
Q_08 = 3
Q_09 = 4
Q_10 = 5
Q_11 = 6

1..11


2, 3, 4, 5, 6, 7, 2, 3, ...というように循環した配列になる！

#### Enumerable#cycle()

[0, 1, 2].cycle(3) { |i| puts i }
# (0..2).cycle(3, &method(:puts))
0
1
2
0
1
2
0
1
2


#### Enumerable#zip()

p [:a, :b, :c].zip([0,1,2])
p [:a, :b, :c, :d].zip([0,1,2])
# p %i(a b c d).zip(0..2)
[[:a, 0], [:b, 1], [:c, 2]]
[[:a, 0], [:b, 1], [:c, 2], [:d, nil]]

[[:a, 0], [:b, 1], [:c, 2], [:d, nil]]


## Unit test with RSpec

RSpec.describe MynumberValidator do
let(:validator) { MynumberValidator.new }

RSpec.describe '#validate_mynumber' do
subject { validator.validate(num) }

context 'pass the number that has invalid length ' do
let(:num) { 12345678901 }
it { is_expected.to be false }
end

context 'pass the invalid number' do
let(:num) { 123456789012 }
it { is_expected.to be false }
end

context 'pass the valid number' do
let(:num) { 123456789018 }
it { is_expected.to be true }
end

context 'pass the invalid number that calculated digit is bigger' do
let(:num) { 123456789001 }
it { is_expected.to be false }
end

context 'pass the valid number that calculated digit is bigger' do
let(:num) { 123456789000 }
it { is_expected.to be true }
end

context 'pass the invalid number as string' do
let(:num) { '123456789012' }
it { is_expected.to be false }
end

context 'pass the valid number starts with 0' do
let(:num) { '023456789013' }
it { is_expected.to be true }
end
end
end