Validating Credit Card Numbers This is one of the puzzles I was trying to solve, where the question is to validate the credit card number with following characteristics: 


  
  It must contain exactly 16 digits. 
  It must start with a 4,5 or 6 
  It must only consist of digits (0-9). 
  It may have digits in groups of 4 , separated by one hyphen "-". 
  It must NOT use any other separator like ' ' , '_', etc. 
  It must NOT have  4 or more consecutive repeated digits.
  


I have go through some of the solutions here and here as well, but the characteristics for validating are different from what I was working on, hence posting this.

total_count = int(raw_input())
numbers_list = []
#refernece list to check the starting series
start_list = [4,5,6]

for count in range(total_count):
    numbers_list.append(raw_input())

#condition 1, validates the starting series
def val_start(num):
    if int(num[0]) in start_list:
        return True
    else:
        return False

#to check if individial elements of the list are of length=4
#4321-5555-67899-9991, splitted to ["4321","5555","67899","991"] which is invalid
def val_group(num):
    for val in num.split("-"):
        if len(val) != 4:
            return False

    return True

#condition 2, validates the length of the number
def val_len(num):
    check = num
    check2 = True
    if "-" in num:
        check = "".join(num.split("-"))
        check2 = val_group(num)

    if ((len(check) == 16) and check2):
        return True
    else:
        return False


#condition 3, validates if input consists only number
def val_isdigit(num):
    if not num.isdigit():
        for ch in num:
            if not (ch.isdigit() | (ch == "-")):
                return False
    return True

#condition 4, validates the repetition of any number for 4 consective times
def val_rep(num):
    res = "".join(num.split("-"))
    for i in range(len(res)):
        try:
            if (res[i] == res[i+1]):
                if (res[i+1] == res[i+2]):
                    if (res[i+2] == res[i+3]):
                        return False
        except IndexError:
           pass
    return True

for num in numbers_list:
    #returns all the values into a list
    result = [val_start(num), val_len(num),val_isdigit(num), val_rep(num)]
    if False in result:
        print("Invalid")
    else:
        print("Valid")


I am looking for an optimised way for the function val_rep(), which validates the repetition of any number for 4 consecutive times.
First of all, you should encapsulate your functionality in a function, unless you really want to use your code only once. At the moment you would have to duplicate your code somewhat just to check a second credit card. So instead let us write a function with a doc string:

def is_valid_card_number(sequence):
    """Returns `True' if the sequence is a valid credit card number.

    A valid credit card number
    - must contain exactly 16 digits,
    - must start with a 4, 5 or 6 
    - must only consist of digits (0-9) or hyphens '-',
    - may have digits in groups of 4, separated by one hyphen "-". 
    - must NOT use any other separator like ' ' , '_',
    - must NOT have 4 or more consecutive repeated digits.
    """

    #  do your stuff here


However, credit card numbers follow a fixed pattern, and therefore are a perfect candidate for regular expressions:

import re

PATTERN='^([456][0-9]{3})-?([0-9]{4})-?([0-9]{4})-?([0-9]{4})$'


This might seem arcane, so let us have a look at that pattern. Anything inside parentheses is considered a group. So we have four groups: ([456][0-9]{3}) and three times ([0-9]{4}). The hyphens in between have ? and are therefore optional. If they exist, they must occur once, so -- is not allowed.

The […] indicates are character class. Only characters inside that class are allowed at that point. The ^ indicates that we're describing the start of our string, not some point in the middle. So we have to start with 456. That requriement is now met. We're followed by 3 ({3}) digits. Our first group therefore consists of four digits, and so do the others. The $ makes sure that there is nothing left: our string has to stop there.

The re documentation provides more information.

We now have met most of our conditions:


we have 16 digits 
we have groups of four
we only have digits or hyphens
we start with a 4, 5 or 6


We're missing the four consecutive digits. This is now tricky, since it's not clear whether the hyphens are part of the run. If they are only invalid four 4 character groups, we can simply check whether the current character * 4 is the complete group:

import re

PATTERN='^([456][0-9]{3})-?([0-9]{4})-?([0-9]{4})-?([0-9]{4})$'

def is_valid_card_number(sequence):
    """Returns `True' if the sequence is a valid credit card number.

    A valid credit card number
    - must contain exactly 16 digits,
    - must start with a 4, 5 or 6 
    - must only consist of digits (0-9) or hyphens '-',
    - may have digits in groups of 4, separated by one hyphen "-". 
    - must NOT use any other separator like ' ' , '_',
    - must NOT have 4 or more consecutive repeated digits.
    """

    match = re.match(PATTERN,sequence)

    if match == None:
        return False

    for group in match.groups:
        if group[0] * 4 == group:
            return False
    return True


If the repeated digits may also not span hyphens/groups it gets a little bit more complicated, but not too much. This is left as an exercise, though.

That being said: make sure to handle multiple numbers, not a single one. That way you will be able to check your code easier. But that depends. If it's a single use script, you probably don't have a need for a function.