Any more pythonic way to solve the problem below?

Asked

Viewed 153 times

3

Create a function that returns the expression value: 2/3 + 3/5 + 4/7 + 5/9 + ... + n/m, to a user-defined value of n. Check that the user-defined n value is positive and, if not, request another value until a positive value is provided.

How I solved:

def expressao():

    expr =0
    n = -1

    while n <0:


        n = int(input("Digite um valor para n: "))
        for i in range(n -1):
              expr += n/(2*n -1)
              n-=1

        return expr

Some more elegant/pythonic way to solve the above problem?

1 answer

11


Yeah, it’s got to be more idiomatic. By the statement we can identify two quite different actions: read the user’s number and calculate the sequence sum. Doing this in the same function breaks the principle of single responsibility, has great potential to infect the DRY, generates an element that is not a code unit and consequently breaks the orthogonality.

In short, you have a function that does a lot of things, that depends on a lot of things and affects a lot of things (it would only be worse if you had one print result). And we haven’t even started analyzing Python yet.

To calculate the sum of your sequence, try to use the native function sum together with a generating expression, for it is much easier to understand the purpose of the expression, that is, it leaves the code readable. When you have a loop of repetition, the first thing you interpret is that the code will need all the elements of the sequence individually, and we’re actually only interested in the sum.

Thus, for a sequence of n elements, just make:

def sum_of_sequence(n):
  return sum(i/(2*i-1) for i in range(2, n+1))

See that fixed the loop for range(2, n+1), because as it was in the question, n/(2*n-1), and varying n decrementally, causes the sum to be calculated backwards, which is counterintuitive and without justification.

Note 1: would be the responsibility of the function also validate its entry. Since the sequence only exists for n 2, any value below this could be expected to be an error or null return, depending on the requirements of the application. For the sake of simplification I chose not to add the validation.

Note 2: depending on the objective, it would not be the responsibility of a function that calculates the sum to generate the sequence itself; if there were other demands such as having to access the nth term there would be code duplication. For this situation it would probably be better to have a function responsible only for generating the sequence and another only to add (a sum Python would already play that role).

Since it is an isolated function and has only one responsibility, we can test it without depending on or affecting any other part of the code (it is orthogonal):

import math

assert math.isclose(sum_of_sequence(2), 2/3)
assert math.isclose(sum_of_sequence(3), 2/3 + 3/5)
assert math.isclose(sum_of_sequence(4), 2/3 + 3/5 + 4/7)
assert math.isclose(sum_of_sequence(5), 2/3 + 3/5 + 4/7 + 5/9)

Already for the reading of n on the part of the user, just read an input value until it is a positive number (it is worth remembering that zero is not positive).

def read_positive_number():
  while True:
    try:
      number = int(input('Informe um número positivo: '))
      if number > 0:
        return number
    except ValueError:
      continue

Another function that only does one thing. Remember the orthogonality? Yeah, let’s test it too without affecting the rest of the system or relying on other functions. In this case, as we depend on a user action, we should create a mock which will simulate the action of the.

from unittest.mock import patch

# Simula o usuário informando '1'
with patch('builtins.input', side_effect=['1']):
    assert read_positive_number() == 1

# Simula o usuário informando '0' e '2' respectivamente
with patch('builtins.input', side_effect=['0', '2']):
    assert read_positive_number() == 2

# Simula o usuário informando 'a', '-1' e '3' respectivamente
with patch('builtins.input', side_effect=['a', '-1', '3']):
    assert read_positive_number() == 3

In this way it will be possible to write unit tests to test code that are, in fact, units and thus allow to identify and maintain punctually any problem in the system. If a function stops working, just correct it without analyzing the rest of the application. Much more convenient and easy for the developer and much cheaper for the company.

So the code would be:

n = read_positive_number()

print(sum_of_sequence(n))

About your code:

def expressao():

    expr =0
    n = -1

    while n <0:


        n = int(input("Digite um valor para n: "))
        for i in range(n -1):
              expr += n/(2*n -1)
              n-=1

        return expr

Some points I would raise:

  1. The name of the function, expressao, does not identify what the function does;
  2. Initial values "random" in variables; if I do not know what is the purpose of the function, I will see the n = -1 and spend time trying to understand why it starts with -1 if there is a condition n < 0; in some languages this may even happen because it is an easy solution, but in Python we have this as a false expectation;
  3. The reading of n, but cases where the entry is not numerical;
  4. The message itself "Digite um valor para n: " does not tell the user what type of value he should enter: positive number;
  5. If the user enters any value less than or equal to 0 the return will be 0, which is not expected;
  6. If you already have i ranging from 0 to n, why not use it instead of decreasing the value of n? Changing the value of the parameter will result in what we call loss of information, because if you want to, for example, follow the execution by a debug tool, the value of the parameter will be changed and you make yourself remember what it was;
  7. There are unnecessary gaps in the code, which impairs the reading of the code;
  8. Avoid naming variables that will not be used, such as i in your tie; this also generates a false expectation in the reader that will make him analyze who he is i and how it behaves... for nothing; in Python we have as convensão to name the unused control variables as _, getting for _ in range();
  • There’s an error in the answer: n is not the number of elements in the sequence.

  • Woss, corrected. Thank you.

Browser other questions tagged

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