Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can't figure out how to use expressions to validate a Canadian postal code in Python

I'm trying to make a program that takes a postal code input from the user and checks to see if it's valid. So far I have:

postalCode = input("Postal code: ")

postalCode = postalCode.replace(" ", "")

postalCode = postalCode.lower()

letters = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"]
numbers = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]

valid = True

for i in range(0, len(postalCode), 2):
  if postalCode[i] not in letters or postalCode[i+1] not in numbers:
  valid = False
  break

if(valid):
  print("Valid postal code.")
else:
  print("Not a valid postal code.")

The code runs fine, but I know using expressions would be much more viable but I haven't been able to figure out how they work.

The Canadian postal code format is: L/N/L N/L/N

Thanks

like image 265
Zach Avatar asked Jan 30 '23 00:01

Zach


1 Answers

No regex Solution:

Get your facts straight - a-z is wrong, some letters are omitted due to similarity:

A Neufundland               B Nova Scotia           C Prince Edward Island
E New Brunswick             G Québec-Ost            H Montréal und Laval
J Québec-West               K Ontario-Ost           L Ontario-Mitte
M Groß-Toronto              N Ontario-Südwest       P Ontario-Nord
R Manitoba                  S Saskatchewan          T Alberta
V British Columbia          X NW-Territ. Nunavut    Y Yukon

Code:

def CheckCanadianPostalcodes(p, strictCapitalization=False, fixSpace=True):
    '''returns a Tuple of (boolean, string):
    - (True, postalCode) or 
    - (False, error message) 
    By default lower and upper case characters are allowed,  
    a missing middle space will be substituted.'''

    pc = p.strip()                   # copy p, strip whitespaces front/end
    if fixSpace and len(pc) == 6:
        pc = pc[0:3] + " " + pc[3:]    # if allowed / needed insert missing space

    nums = "0123456789"              # allowed numbers
    alph = "ABCEGHJKLMNPRSTVWXYZ"    # allowed characters (WZ handled below)
    mustBeNums = [1,4,6]             # index of number
    mustBeAlph = [0,2,5]             # index of character (WZ handled below)

    illegalCharacters = [x for x in pc 
                         if x not in (nums + alph.lower() + alph + " ")]

    if strictCapitalization:
        illegalCharacters = [x for x in pc
                             if x not in (alph + nums + " ")]

    if illegalCharacters:
        return(False, "Illegal characters detected: " + str(illegalCharacters))

    postalCode = [x.upper() for x in pc]          # copy to uppercase list

    if len(postalCode) != 7:                      # length-validation
        return (False, "Length not 7")

    for idx in range(0,len(postalCode)):          # loop over all indexes
        ch = postalCode[idx]
        if ch in nums and idx not in mustBeNums:  # is is number, check index
            return (False, "Format not 'ADA DAD'")     
        elif ch in alph and idx not in mustBeAlph: # id is character, check index
            return (False, "Format not 'ADA DAD'") # alpha / digit
        elif ch == " " and idx != 3:               # is space in between
            return (False, "Format not 'ADA DAD'")

    if postalCode[0] in "WZ":                      # no W or Z first char
        return (False, "Cant start with W or Z")

    return (True,"".join(postalCode))    # yep - all good

Testing:

testCases = [(True,"A9A 9A9"), (True,"a9a 9a9"), (True,"A9A9A9"),
             (True,"a9a9a9"), (False,"w9A 9A9"), (False,"z9a 9a9"), 
             (False,"a9a 9!9")]

for t in testCases:
    pc = CheckCanadianPostalcodes(t[1])    # output differs, see func description
    assert pc[0] == t[0], "Error in assertion: " + str(t) + " became " + str(pc)
    print(t[1], " => ", pc)

pp = input("Postal code: ") 
print(CheckCanadianPostalcodes(pp))    # output differs, see func description  

Output:

A9A 9A9  =>  (True, 'A9A 9A9')
a9a 9a9  =>  (True, 'A9A 9A9')
A9A9A9  =>  (True, 'A9A 9A9')
a9a9a9  =>  (True, 'A9A 9A9')
w9A 9A9  =>  (False, 'Cant start with W or Z')
z9a 9a9  =>  (False, 'Cant start with W or Z')
a9a 9!9  =>  (False, "Illegal characters detected: ['!']")
Postal code: b2c3d4
(False, "Illegal characters detected: ['d']")

This answer with regex (not accepted) delivers the correct regex.

Number of possible postal codes (from wikipedia)

Postal codes do not include the letters D, F, I, O, Q or U, and the first position also does not make use of the letters W or Z. [...] As the Canada Post reserves some FSAs for special functions, such as for test or promotional purposes, (e.g. the H0H 0H0 for Santa Claus, see below) as well as for sorting mail bound for destinations outside Canada. [...]

which leaves you with ABCEGHJKLMNPRSTVXY without WZ as 1st char.


Edit: Incoperated change suggestion by jl-peyret

  • allow missing space
  • and make clearer when upper/lowercase is ok
like image 157
Patrick Artner Avatar answered Jan 31 '23 21:01

Patrick Artner