Validation of the last 2 digits of CPF in a single loop for

Asked

Viewed 723 times

-1

I checking the last two digits for CPF validation.

I made the code, but it’s just calculating the first digit. I’m not getting, include in the same loop to calculate the second digit:


    while True:

    #CPF deve ser inserido sem formatação Ex.16695835009
    cpf = input("Informe o seu CPF: ")

    if len(cpf) != 11 or not cpf.isnumeric():
        print("Dados inseridos incorretamente.\nDigite novamente.")
        continue

    novo_cpf = cpf[:-2]
    soma_digitos = 0
    lista_digitos = []

    for key, index in enumerate(novo_cpf):
        key_reverso = len(novo_cpf) + 1 - key
        lista_digitos.append(key_reverso * int(index))
        soma_digitos += lista_digitos[key]

    d = 11 - (soma_digitos % 11)

    if(d > 9):
        d = 11 - d

    '''
    Os prints abaixo é só para verificação da saída do código.
    '''
    print(lista_digitos)
    print(soma_digitos)
    print(d)
  • 1

    Just out of curiosity, because you want to include in the same loop?

  • @Bacco, in vdd the exercise asks to be made the CPF check informed by the user. In no time, asks to be done in the same loop. I am a beginner in the Python language and I thought that if I included the second digit check in the same loop, it would be less code and thus save the loop repetition for again.

1 answer

1


You can even do everything with a single bow, I just don’t know if it does so much difference like this. But before we try to do with two loops, then we compare with the version "one-loop-only".


Before the loops themselves, you could already convert the string into a list of numbers, in which each element of this list is a digit. For that we can use map and int:

def validar_cpf(cpf):
    if len(cpf) != 11:
        return False

    try:
        digitos = list(map(int, cpf))
    except ValueError:
        return False

    # restante da validação...

Thus, if any character of the string cannot be converted to number, it falls into the block except and the function already returns False. I did so because later I will need to convert these characters to number, to be able to do the calculations of the check digit. At the same time, I’ve already validated whether the string actually contains digits.

I also chose this way because isnumeric() can return True for several error-giving characters when converted to number with int(), such as the ½ (VULGAR FRACTION ONE HALF) - see here the complete list - so I find this check better than simply using isnumeric() (with the advantage that already creates the list with the characters converted to numbers, as I will need those numbers later). As the user is typing the data, there is the possibility of typing some of these characters, so we already do this check right at the beginning.


Now yes, to the calculations. First let’s make the version with 2 loops (the algorithm can be easily searched on the internet, I used this site as a source):

def validar_cpf(cpf):
    if len(cpf) != 11:
        return False

    try:
        digitos = list(map(int, cpf))
    except ValueError:
        return False

    def calcula_digito(multiplicador):
        total = 0
        for d in digitos:
            if multiplicador >= 2:
                total += d * multiplicador
                multiplicador -= 1
            else: break
        resto = total % 11
        if resto < 2:
            return 0
        else:
            return 11 - resto

    # primeiro dígito não bate, CPF inválido
    if digitos[9] != calcula_digito(10):
        return False

    # segundo dígito não bate, CPF inválido
    if digitos[10] != calcula_digito(11):
        return False

    return True

I created the Inner Function calcula_digito, containing the loop to calculate the check digit. For the first digit checker, we start with 10, which is multiplied by the first digit of the CPF, then multiply the second digit by 9, the third by 8, etc (stopping at 2, so the if multiplicador >= 2, and a break to interrupt the loop if the multiplier is less than 2).

In the second digit checker, I do the same algorithm, but starting to multiply by 11. The logic of the loop is the same, and so I took advantage of the internal function code calcula_digito. But as this function is called twice, so I’m making two loops.


Notice that what changes from one loop for another is only the initial value of the multiplier (10 for the first, 11 for the second), and the stop condition is the same (I go until the value of the multiplier is 2). So it would be possible to do everything in one loop:

def validar_cpf(cpf):
    if len(cpf) != 11:
        return False

    try:
        digitos = list(map(int, cpf))
    except ValueError:
        return False

    def calcula_resto(resto):
        if resto < 2:
            return 0
        else:
            return 11 - resto

    total1 = total2 = 0
    multiplicador = 11
    for d in digitos:
        if multiplicador >= 2:
            if multiplicador >= 3:
                total1 += d * (multiplicador - 1)
            total2 += d * multiplicador
            multiplicador -= 1
        else: break

    # primeiro dígito não bate, CPF inválido
    if digitos[9] != calcula_resto(total1 % 11):
        return False

    # segundo dígito não bate, CPF inválido
    if digitos[10] != calcula_resto(total2 % 11):
        return False

    return True

I enjoy the same loop to calculate the sum of both check digits (both starting with 11 and 10), and then calculate the respective digits using the rest of the division according to the rules of the algorithm.


In this comment you said you wanted to do everything in one loop, That would be "less code". Well, look again at the above functions and see if the gain was really significant (in my opinion, it wasn’t, it’s pretty much the same amount of code).

But the point is not "write less code". A shorter code is not necessarily "better", just as a longer code is not necessarily "worse". First try to write a clear and correct code, and then you think about these optimizations.

If you’re worried about performance, honestly, those loops are not so expensive and for the vast majority of cases the difference will be irrelevant. In real systems you do performance tests to see where the bottlenecks are and do not worry about these micro-optimizations (even because in real systems, most of the time, probably the neck won’t be on those loops underage).

Also think about code maintenance: in the future it may be that someone else (or even you) has to tamper with the code, so it is best to make it clear what is done. Of course it may be a matter of opinion whether the algorithm gets better expressed with a loop or two, but you shouldn’t write a code that gets confusing and difficult to understand and maintain, just because became smaller (the size of the code should not be an objective itself, there are other points to consider).


In accordance with commented by Bacco, would optimize the second loop, because the first element is multiplied by 11, and in the end we take the rest of the division by 11, so this element does not influence the final result. Therefore, the second loop could start from the second digit onwards:

def validar_cpf(cpf):
    if len(cpf) != 11:
        return False

    try:
        digitos = list(map(int, cpf))
    except ValueError:
        return False

    # incluir o índice no qual começa o loop
    def calcula_digito(multiplicador, inicio = 0):
        total = 0
        for d in digitos[inicio:]:
            if multiplicador >= 2:
                total += d * multiplicador
                multiplicador -= 1
            else: break
        resto = total % 11
        if resto < 2:
            return 0
        else:
            return 11 - resto

    # primeiro dígito não bate, CPF inválido
    if digitos[9] != calcula_digito(10):
        return False

    # segundo dígito não bate, CPF inválido
    if digitos[10] != calcula_digito(10, 1): # começa o loop do segundo elemento
        return False

    return True

Now the function calcula_digito may receive the index in which the loop will start (or zero if none is indicated - remembering that lists start at zero).

digitos[inicio:] builds a sub-list from the index inicio. Of course I could make one if inicio == 0: do the for in digitos, and if inicio is a different value, then I create the sub-list (so I avoid creating a sub-list for nothing, when the index is zero).

Another alternative is to use a while:

def calcula_digito(multiplicador, inicio = 0):
    total = 0
    indice = inicio
    while True:
        if multiplicador >= 2:
            total += digitos[indice] * multiplicador
            multiplicador -= 1
            indice += 1
        else: break
    resto = total % 11
    if resto < 2:
        return 0
    else:
        return 11 - resto

So I avoid creating sub-lists. Anyway, note that the amendments are more in the sense of creating new structures for no reason, whether it makes more sense or not according to the context, etc., and what matters here is whether the code will get smaller.

  • 1

    Of curiosity, with 2 loops you can still skip the house * 11 of the second loop (pq between us, multiplying by eleven to get the rest of the split by eleven is kind of meaningless).

  • 1

    @Bacco You’re right, I updated the reply, thank you!

  • 1

    @hkotsubo This is not an answer, but a lesson on how to correctly write a code, thinking on all sides. It was very enlightening and motivating. I haven’t seen in the Python course that I’m doing some things like map and how to treat exceptions, to speak the truth, I entered the chapter of functions for now. So I still have a long way to go in language.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.