Skip to content

Instantly share code, notes, and snippets.

@izumin5210
Created December 17, 2015 13:38
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save izumin5210/a9f765fd218ebcacf986 to your computer and use it in GitHub Desktop.
Save izumin5210/a9f765fd218ebcacf986 to your computer and use it in GitHub Desktop.
mynumber_validator_answer.md

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

from: 総務省令第八十五号 行政手続における特定の個人を識別するための番号の利用等に関する法律

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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment