On 10/03/2021 09.16, Victor Dib wrote: > Olá, comunidade do Python!
Olá Victor! At which point we should switch to English, because this is an English-language list. (there may be Portuguese lists - I haven't looked) Please do not be afraid to write in English. There are many here who are quite aware of how difficult it can be to use a second, third, ... language, and thus no-one will embarrass you - but, few here understand Portuguese and so writing in English means that more people will consider the question and attempt to help you... > Meu nome é Victor Dib, e sou um estudante brasileiro de programação. We wish you well in your studies. > Já entrei em contato com vocês hoje, e vocês solicitaram que eu me > inscrevesse na lista de e-mails de vocês primeiro. Bom, isso já foi feito, > então espero que agora vocês possam dar atenção ao meu caso. The list-community is a group of people who volunteer their time, and are happy to help if they can (and if they want to do so). > Já sou um usuário de nível iniciante pra médio da linguagem Python. Mas > recentemente tive um problema no uso da linguagem. > Ao construir uma função que encontra números perfeitos, percebi que a > linguagem não estava realizando corretamente a inserção de novos dados > inteiros à uma lista. Encaminharei imagens para demonstrar o meu problema. Unfortunately, the list-server (software) removes graphics. Please copy-paste code-snippets (as you did) and (all of) any error-messages, sample outputs, etc. > Tive problemas com a versão 3.9.2 da linguagem. Mas também usei a versão > 3.8.7 (por ser marcada como estável no site da linguagem), e igualmente tive > problemas. The changes and improvements between the two versions of Python are unlikely to affect this code. > Acredito que não haja problemas em minha lógica, e por isso gostaria que > vocês desse m uma olhada para ter certeza de que não é um problema na > linguagem. Let's have a look and see if there is an error in the code-provided, or if it's in the Python language itself... > Odiaria que uma linguagem tão incrível como o Python não funcionasse como > deveria. Por isso, por favor, verifiquem meu caso! We would not like to think that there is a fault in our favorite language! > Obrigado! You are welcome. Thanks for joining our community, and as time goes by, we look forward to seeing you help others... > O programa que escrevi para encontrar números perfeitos: Let's start with the definition of a "Perfect Number": <<< In number theory, a perfect number is a positive integer that is equal to the sum of its positive divisors, excluding the number itself. For instance, 6 has divisors 1, 2 and 3 (excluding itself), and 1 + 2 + 3 = 6, so 6 is a perfect number. The sum of divisors of a number, excluding the number itself, is called its aliquot sum, so a perfect number is one that is equal to its aliquot sum... The first few perfect numbers are 6, 28, 496 and 8128 >>> (https://en.wikipedia.org/wiki/Perfect_number) > def num_perf_inf(n): > divisors = [] > perfects = [] > limit = n - 1 > for i in range(1, limit): > dividend = i + 1 > for j in range(i): > divisor = j + 1 > if dividend % divisor == 0: > divisors.append(divisor) > print(f'Divisors of {i + 1}: {divisors}') > > divisors.pop() > if sum(divisors) == dividend: > perfects.append(i) > divisors.clear() > print(perfects) > > > num_perf_inf(28) > > E o resultado da execução desse código, em Python 3.9.2 e em Python 3.8.7 > (como já mencionado, testei as duas versões da linguagem: > > > Divisors of 2: [1] > Divisors of 3: [1] > Divisors of 4: [1, 2] > Divisors of 5: [1] > Divisors of 6: [1, 2, 3] ... > Divisors of 27: [1, 3, 9] > [23] My thinking is different to yours, but it is how I solve problems. Secondly, there is much in this code which is not idiomatic Python. I want to make changes to make the code easier to understand... The first thing I want to change is the function call. Human-thinking says: give me all the "perfect numbers" up to 28. The code uses Python-thinking, eg range( 28 ) is integers >0 and <28 (not <=28) The second thing is that the first "perfect number" is six, so let's start by making sure that it is correctly-reported. num_perf_inf( 6 ) which requires another code-change: def num_perf_inf( last_number_to_be_considered ): Note: in Python "style" spaces before "(" are 'definitely-wrong', spaces inside "(" and ")" are considered either 'wrong' or 'unnecessary'. I use them to help my aged-eyes read the line of code. The parameter "n" is only used in one place - to calculate "limit", and "limit" is only used in one place. So, let's get rid of "limit" and change the outer for-loop to: for i in range( 1, last_number_to_be_considered + 1 ): For our first test, "i" will cover the range: 1, 2, 3, 4, 5, and 6. Are we happy? My mind is better with words. I prefer to call you "Victor"! Plus, I've already changed the function's parameter, so let me continue to help my understanding by changing "i" to a name which relates to the parameter's name and its purpose: for number_to_be_considered in range( 1, last_number_to_be_considered + 1 ): Unfortunately, long identifiers start to exceed the line-length, particularly in my email client. Apologies! The 'message' is: use meaningful names! Let's have a look at the loop now. What is it doing? It performs two functions: 1 find the divisors from number_to_be_considered, and 2 asks if the number_to_be_considered is a "perfect number" Let's separate those two tasks into separate sub-routines (functions), so that each has only one responsibility - and can be tested separately (and thus, more easily) and in great detail (with different source data), etc, etc. To get started, the function might now appear as: for number_to_be_considered in range( 1, last_number_to_be_considered + 1 ): divisors = find_divisors( number_to_be_considered ) is_perfect_number( number_to_be_considered, divisors ) Note that there is no longer any need to define "divisors" before the loop! Yes, we could go one step further and combine these into a single line with two function-calls. However, might that be unnecessarily complicated? Let's start detailing the second function: "is_perfect_number()". Note the choice of name appears to be a question. That's because it's result should be a boolean condition - either the number is "perfect" or it is not! Remember what I said about "testing"? We can test this function, on its own, eg print( "Should be True :", is_perfect_number( 6, [ 1, 2, 3 ] ) ) print( "Should be False :", is_perfect_number( 4, [ 1, 2 ] ) ) (etc) Let's copy the relevant lines of code into the new function. Note moving the "perfects" list definition and replacing the "i"! Also, added a debug-print so that we can see what's happening, and a final line to return a boolean result: def is_perfect_number( candidate_number:int, divisors:list[ int ] )->bool: perfects = [] divisors.pop () if sum (divisors) == candidate_number: perfects.append ( candidate_number ) divisors.clear () print( candidate_number, perfects ) return bool( perfects ) Run the tests and this is the result: 6 [] Should be True : False 4 [] Should be False : False Uh-oh! Why is there a call to the pop() list-method? Let's jump into the Python REPL (a terminal running Python): >>> divisors = [ 1, 2, 3 ] >>> divisors.pop() 3 >>> divisors [1, 2] Is this what we want? So, after the .pop() when the sum() is calculated the list is missing the 3 - yet the "perfect number" formula is: 1 + 2 + 3 = 6 Let's get rid of the pop() line and re-run the tests: 6 [6] Should be True : True 4 [] Should be False : False Additionally, as a matter of style, see the two parameters to the function, and then how they are used in the if-statement? My mind wants to see them used in the same order. Thus: def is_perfect_number( candidate_number:int, divisors:list[ int ] )->bool: ... if candidate_number == sum( divisors ): ... Note: this "consistency" was less apparent before I added the idea of a separate function. Thinking a bit more, why do we need the if-statement at all? Could we turn the if's condition-clause into a return statement and remove everything else from the function? (and now some people will discuss: if the function is essentially only a return-statement, why have the function at all? An answer is "testing"!) Happy with that? Let's turn our attention to the first function. There's not much to be done here apart from copying the original code. However, I've changed "j" into "candidate_divisor" because (again) that is a more descriptive name. Please refer to my comment (above) about whether or not to include zero in a range. Does it 'hurt' (the extra execution time is negligible) or create an incorrect result? Another thought: is the maximum value of a divisor the "number_to_be_considered" or could you halve the number of times that loop is run? The finished code has moved all of the adjustments to a range() function to the range() call, rather than having "intermediate-variables". I must admit, because the ranges all start from one, I might replace range() with a Python generator which returns "positive integers" up to a specified maxima, because that call will read more smoothly than range( 1, ... ). However, if you don't know about generators, that's an unnecessary complication. Similarly, the code within find_divisors() could be reduced to a single list-comprehension - but only if we are all comfortable with that advanced-Python technique! My version now consists of: def num_perf_inf( last_number_to_be_considered:int ): for number_to_be_considered in range( 1, last_number_to_be_considered + 1 ): divisors = find_divisors( number_to_be_considered ) if is_perfect_number( number_to_be_considered, divisors ): print( number_to_be_considered ) find_divisors() with its explicit loop, is about five lines long is_perfect_number() is one line, as mentioned the perfects list has been dropped because we may as well print each "perfect number" as it is 'found' rather than saving them and printing them all at the end. (according to the spec[ification] as-presented) Thus: num_perf_inf (28) results: 6 28 I haven't given you a copy of all of my code. You will only learn if you do it yourself! So, to wrap-up: Before considering if Python has made a mistake, it is worth breaking down the larger problem into smaller units of code ("subroutines"), and checking that each of them works (correctly). Alternately, I follow a practice called "TDD" (Test-Driven Development), whereby each large problem is broken-down into subroutines, and then each subroutine is individually created and tested. Further tests will ensure that as the smaller-units are combined, they work-together correctly. Finally, when all are combined, we expect that the entire system will work. The original code appeared to work, but one small part (ie do these divisors reveal that the number is "perfect"?) was not correct. Accordingly, the whole program[me] failed. The (great) people who create the Python language and its interpreter did not let us down! -- Regards, =dn -- https://mail.python.org/mailman/listinfo/python-list