List the valid South African National IDs (person identification numbers). A South African person identification number is a 13-digit number containing only numeric characters, and no whitespace, punctuation, or alpha characters. It is defined as YYMMDDSSSSCAZ: YYMMDD – Date of birth; SSSS – Female = 0 to 4999; Male = 5000 to 9999 C – Citizen = 0; Not citizen = 1 A – Any random number Z – Checksum digit. The checksum digit is calculated using the Luhn algorithm A = the sum of the digits in the ID number in the odd positions (excluding Z) B = the number formed by the concatenation of the digits in the ID number in the even positions C = the sum of the digits in (2 * B) D = A + C Z = 10 – (D mod 10)
📌 Challenge Details and Links
ExcelBI Excel Challenge Number: 342
Challenge Difficulty: ⭐️⭐️⭐️
📥Download Sample File
📥Link to the solutions on LinkedIn
Solving the challenge of List valid South African National IDs with Power Query
Power Query solution 1 for List valid South African National IDs, proposed by Zoran Milokanović:
let
Source = Excel.CurrentWorkbook(){[Name = "Input"]}[Content],
S = Table.SelectRows(
Source,
each
let
t = Text.Select([SA IDs], {"0" .. "9"}),
x = (t, p) => Number.From(Text.ToList(t){p}),
n = List.Transform(
{0 .. Text.Length(t) - 1},
(i) =>
let
a = Text.From((if Number.IsOdd(i) then 2 else 1) * x(t, i))
in
List.Accumulate({0 .. Text.Length(a) - 1}, 0, (s, c) => s + x(a, c))
)
in
if t <> [SA IDs] then
false
else if Text.Length(t) <> 13 then
false
else if (
try
Date.FromText(Text.Middle(t, 0, 6), [Format = "yyMMdd", Culture = "en-US"])
otherwise
null
)
= null
then
false
else if n{10} > 1 then
false
else if n{12} <> 10 - Number.Mod(List.Sum(List.RemoveLastN(n)), 10) then
false
else
true
)
in
S
Power Query solution 2 for List valid South African National IDs, proposed by Alejandro Simón 🇵🇦 🇪🇸:
let
Source = Excel.CurrentWorkbook(){[Name="Table1"]}[Content],
Sol = Table.SelectRows(Source, each
let //Date
a = List.Split(Text.ToList([SA IDs]), 6),
b = List.Transform(List.Split(a{0}, 2), each Number.From(Text.Combine(_))),
c = try hashtag#date(2000+b{0}, b{1}, b{2}) is date otherwise false,
//Male or Female
d = Number.From(Text.Combine(List.FirstN(a{1},4))) is number,
//Citizen or not
e = if a{1}{4}>"1" then false else true,
//Random number
f = Number.From(a{1}{5}) is number,
//Number length
g = Text.Length([SA IDs])=13,
//Checksum digit
z = let
A = List.Sum(List.Transform(List.Alternate(List.RemoveLastN(Text.ToList([SA IDs])), 1,1,1), Number.From)),
B = List.Alternate(List.RemoveLastN(Text.ToList([SA IDs])), 1,1),
C = List.Sum(List.Transform(B, each List.Sum(List.Transform(Text.ToList(Text.From(Number.From(_)*2)), Number.From)))),
D = A + C,
Z = try (if Number.Mod(D,10) = 0 then 0 else 10-Number.Mod(D,10)) = Number.From(a{2}{0}) otherwise false
in Z
in List.AllTrue({c,d,e,f,g,z}))
in
Sol
Solving the challenge of List valid South African National IDs with Excel
Excel solution 1 for List valid South African National IDs, proposed by Bo Rydobon 🇹🇭:
=TOCOL(MAP(A2:A10,
LAMBDA(a,
LET(m,
MID(
a,
SEQUENCE(
6,
2
),
1
)*{1,
2},
IFS((20&TEXT(
LEFT(
a,
6
),
"0-00-00"
))*(MID(
a,
11,
1
)<"2")*(MOD(
SUM(
IF(
m,
MOD(
m-1,
9
)+1
)
),
-10
)=-RIGHT(
a
)),
a)))),
3)
Excel solution 2 for List valid South African National IDs, proposed by John V.:
=TOCOL(MAP(A2:A10,
LAMBDA(x,
LET(i,
{1,
2}*MID(
x,
SEQUENCE(
6,
2
),
1
),
IFS((RIGHT(10-RIGHT(SUM(i-9*(i>9))))=MID(
x,
13,
1
))*(19&TEXT(
LEFT(
x,
6
),
"0-00-00"
))*(--MID(
x,
11,
1
)<2),
x)))),
2)
Excel solution 3 for List valid South African National IDs, proposed by محمد حلمي:
=TOCOL(MAP(A2:A10,LAMBDA(a,LET(s,SEQUENCE(6)*2,
a/AND(MOD(SUM(-MID(a,s-1,1),-("0"&MID(MID(a,s,1)*2,{1,2},1))),10)=--RIGHT(a),--MID(a,{3,5},2)<{13,32},
OR(MID(a,11,1)={0,1}&""))))),2)
Excel solution 4 for List valid South African National IDs, proposed by Kris Jaganah:
=TEXT(TOCOL(A2:A10/MAP(A2:A10,
LAMBDA(x,
LET(a,
SEQUENCE(
12
),
b,
MID(
x,
a,
1
)*(MOD(
a-1,
2
)+1),
(RIGHT(
x
)=RIGHT(
10-MOD(
SUM(
IF(
b>9,
1+RIGHT(
b
),
b
)
),
10
)
))*(LEN(
x
)=13)*(--MID(
x,
3,
2
)<13)*(--MID(
x,
5,
2
)<31)*(--MID(
x,
11,
1
)<2)))),
3),
"#")
Excel solution 5 for List valid South African National IDs, proposed by Julian Poeltl:
=FILTER(A2:A10,
IFERROR(MAP(A2:A10,
LAMBDA(N,
LET(SQ,
SEQUENCE(
12
),
C,
MID(
N,
SQ,
1
)*(MOD(
SQ-1,
2
)+1),
S,
MID(
N,
SQ,
1
),
R,
(--MID(
N,
3,
2
)<13)*(--MID(
N,
5,
2
)<32)*(--MID(
N,
11,
1
)<2)*(RIGHT(
N
)=RIGHT(
10-MOD(
SUM(
IF(
C>9,
1+RIGHT(
C
),
C
)
),
10
)
)),
R))),
0))
Excel solution 6 for List valid South African National IDs, proposed by Timothée BLIOT:
=TOCOL(MAP(A2:A10,
LAMBDA(z,
LET(Y,
--MID(
z,
1,
2
),
M,
--MID(
z,
3,
2
),
D,
--MID(
z,
5,
2
),
T,
DATE(
--Y,
M,
D
),
A,
AND(
MONTH(
T
)=M,
DAY(
T
)=D
),
B,
SUM(--((--MID(
z,
11,
1
))={1,
0}))=1,
E,
--WRAPROWS(
MID(
z,
SEQUENCE(
LEN(
z
)-1,
1
),
1
),
2
),
F,
CONCAT(
TAKE(
E,
,
-1
)
),
G,
--RIGHT(
10-MOD(
SUM(
TAKE(
E,
,
1
)
)+SUM(
--MID(
2*F,
SEQUENCE(
LEN(
2*F
)
),
1
)
),
10
)
)=--RIGHT(
z
),
z/AND(
A,
B,
LEN(
z
)=13,
G
)))),
3)
Excel solution 7 for List valid South African National IDs, proposed by Hussein SATOUR:
=LET(a, TRIM(SUBSTITUTE(A2:A10, " ", "")), b, FILTER(a, (LEN(a)=13)), c, FILTER(b, (TEXT(DATE(LEFT(b,2), MID(b,3,2), MID(b,5,2)),"yymmdd") = LEFT(b, 6)) * (--MID(b, 7, 4) <= 9999) * (--MID(b, 11, 1) < 2)), FILTER(c, MAP(c, LAMBDA(x, RIGHT(10 - MOD(SUM(--MID(x, SEQUENCE(6,,1,2),1)) + SUM(IFERROR(MID(CONCAT(MID(x, SEQUENCE(6,,2,2),1))*2, SEQUENCE(15), 1)+0,0)), 10)) = RIGHT(x)))))
Excel solution 8 for List valid South African National IDs, proposed by Sunny Baggu:
=FILTER(
A2:A10,
MAP(
A2:A10,
LAMBDA(
a,
LET(
_c1,
--MID(
a,
11,
1
) <= 1,
_c2,
ISNUMBER(
DAY(
20 & TEXT(
LEFT(
a,
6
),
"0-00-00"
)
)
),
_c3,
LET(
_n,
CONCAT(
MID(
a,
SEQUENCE(
6,
2
),
1
) * {1,
2}
),
_nsum,
SUM(
--MID(
_n,
SEQUENCE(
LEN(
_n
)
),
1
)
),
IFERROR(
10 - IF(
MOD(
_nsum,
10
) = 0,
10,
MOD(
_nsum,
10
)
) = RIGHT(
a
) + 0,
FALSE
)
),
AND(
_c1,
_c2,
_c3
)
)
)
)
)
Excel solution 9 for List valid South African National IDs, proposed by Stefan Olsson:
=FILTER(
A2:A10,
BYROW(
A2:A10,
LAMBDA(
_id,
LET(
_dc,
ISDATE(
REGEXREPLACE(
_id&"",
"^(dd)(dd)(dd).*",
"19$1-$2-$3"
)
),
_A,
SUM(
--REGEXEXTRACT(
_id&"",
REPT(
"(.).",
6
)
)
),
_B,
TEXTJOIN(
,
1,
REGEXEXTRACT(
_id&"",
REPT(
".(.)",
6
)
)
)*2,
_C,
SUM(
--REGEXEXTRACT(
_B&"",
REPT(
"(.)",
LEN(
_B
)
)
)
),
_D,
_A+_C,
_Z,
10-MOD(
_D,
10
),
IF(
_dc,
--RIGHT(
_id,
1
)=_Z,
FALSE
)
)
)
)
)
Excel solution 10 for List valid South African National IDs, proposed by Abdelrahman Omer, MBA, PMP:
=FILTER(A2:A10,0
Solving the challenge of List valid South African National IDs with Python in Excel
Python in Excel solution 1 for List valid South African National IDs, proposed by Alejandro Campos:
To check it, can use a Luhn calculator, Like https://es.planetcalc.com/2461/
import re
ids = xl("A1:A10", headers=True)
ids = ids['SA IDs'].tolist()
def is_valid_sa_id(id_number):
if not re.match(r'^d{13}$', id_number):
return False
birth_date = id_number[:6]
gender_digits = int(id_number[6:10])
citizenship_digit = int(id_number[10])
checksum_digit = int(id_number[12])
if gender_digits < 0 or gender_digits > 9999:
return False
if citizenship_digit not in [0, 1]:
return False
A = sum(int(id_number[i]) for i in range(0, 12, 2))
B = int(''.join(id_number[i] for i in range(1, 12, 2)))
C = sum(int(digit) for digit in str(2 * B))
D = A + C
Z = (10 - (D % 10)) % 10
return Z == checksum_digit
valid_ids = [id_number for id_number in ids if is_valid_sa_id(id_number)]
valid_ids
Suma de verificación de Luhn
Calcula la suma de verificación utilizando el algoritmo de Luhn. El algoritmo de Luhn es popular en la validación de números de tarjetas de crédito.
Solving the challenge of List valid South African National IDs with R
R solution 1 for List valid South African National IDs, proposed by Konrad Gryczan, PhD:
library(tidyverse)
library(readxl)
input = read_excel("South Africa National ID Validation.xlsx", range = "A1:A10") %>% janitor::clean_names()
test = read_excel("South Africa National ID Validation.xlsx", range = "B1:B4") %>% janitor::clean_names()
validate_id = function(number) {
digits = strsplit(as.character(number), "")[[1]]
odd_digits = digits[seq(1, 12, 2)]
even_digits = digits[seq(2, 12, 2)]
dob = str_sub(number, 1&, 6) %>% as.Date(format = "%y%m%d")
A = sum(as.numeric(odd_digits))
B = str_c(even_digits, collapse = "") %>% as.numeric()
B2 = B * 2
C = strsplit(as.character(B2), "")[[1]] %>% as.numeric() %>% sum()
D = A + C
Dmod = D %% 10
Z13 = str_sub(number, 13, 13) %>% as.numeric()
Z = ifelse(Dmod == 0, 0, 10 - D %% 10)
is_Z_valid = Z == Z13
is_status_valid = str_sub(number, 11, 11) %>% as.numeric() %>% between(0, 1)
is_valid_length = length(digits) == 13
is_date_valid = !is.na(dob)
final_check = is_Z_valid & is_date_valid & is_valid_length & is_status_valid
return(final_check)
}
result = input %>%
mutate(answer_expected = map(sa_i_ds, validate_id)) %>%
filter(answer_expected == TRUE)
&&
