How to summarise integers in a while loop in a range

Viewed 57

guys!

I am trying to do assessment task. It requires me to input number of branches for certain subjects ( Chemistry, Computers, Biology, Physics, Arts). Then it asks me to put number of books for these subjects per branch. Then it asks me to calculate the number of books as total and calculate the average. I am almost done with it but there is one last obstacle for me - how to calculate sum of books in these branches and subjects. I would really appreciate if you explain it to me.

Here is the code:

#!/usr/bin/env python3

branches = 0
while branches < 1:
  try:
    branches = int(input("Enter branches: "))
  except (ValueError, TypeError):
    print("Number of branches must be an integer")
  else:
    if branches < 1:
      print("Number of branches must be at least 1")



for i in range(branches):
  for category in ["Computer", "Physics", "Chemistry", "Biology", "Arts"]:
    books = -1
    while books < 0:
      try:
        string_branches = str(i+1)
        books = int(input("Enter the number of books for " + category +  " of branch " + string_branches + " :" ))
      except (ValueError, TypeError):
        print("Number of books must be an integer")
      else:
        if books < 0:
          print("Number of books must be at least 1")
        books_total = 0
        books_total = books + books 
books_per_category = books_total  / branches
print("Following the information of all the branches of the main library: ")
print("Total number of books: ",books_total )
print("Average numbers of books per category: ",  books_per_category)

The million dollar question is - how to make books add up to total? Believe me i ve tried like bunch of different methods, but they seem not to work.

Maybe i did not define function of something.

4 Answers

I wanted to give you a more detailed answer on the CLI, how you could clean the code by splitting it up in functions, how you could define your own exceptions, and how you could remove one of your two main loops. Moreover, instead of summing the number as we go, I chose to store every input. For instance, an output could be:

{'Computer': [1, 3], # 2 branches with 1 and 3 books
'Physics': [4], # 1 branch with 4 books
 'Chemistry': [2, 3, 2],
 'Biology': [2],
 'Arts': [2]}

The code is divided in 3 parts:

  1. Exceptions
  2. Inputs
  3. Main loop

You can define your own exception. You can read e.g. this article. Moreover, I chose to limit the number of possible retries for a user input to 5. This way, the program always has an end. I defined RETRIES in capital letters I usually I would consider this as an imported global variable.

#%% Define custom exceptions
RETRIES = 5

class ConsecutiveWrongAnswers(Exception):
    """Exception raised when the users input too many times a wrong answer
    to a query. 

    Attributes:
        retries -- Number of retries
    """

    def __init__(self, retries):
        self.retries = retries
        self.message = f'The input was invalid more than {self.retries} times!'
        super().__init__(self.message)
        
class InvalidBranchNumber(Exception):
    """Exception raised for errors in the branch number input.

    Attributes:
        branch -- invalid branch number.
        message -- explanation of the error.
    """

    def __init__(self, branch, 
                 message="Branch number input should be a positive integer."):
        self.branch = branch
        self.message = message
        super().__init__(self.message)

    def __str__(self):
        return f'{self.branch} -> {self.message}'
    
class InvalidBooksNumber(Exception):
    """Exception raised for errors in the branch number input.

    Attributes:
        books -- invalid books number.
        message -- explanation of the error.
    """

    def __init__(self, branch, 
                 message="Books number input should be a positive integer."):
        self.books = books
        self.message = message
        super().__init__(self.message)

    def __str__(self):
        return f'{self.books} -> {self.message}'

Then, I define input functions which will let the user try to enter a valid input up to RETRIES times. The input is checked before it is returned.

#%% Input functions
def input_branch_number(category, retries=RETRIES):
    
    def check_branch(branch):
        if branch <= 0:
            raise InvalidBranchnNumber(branch)
    
    attempt = 0
    while True:
        attempt += 1
        if attempt > retries:
            raise ConsecutiveWrongAnswers(retries)
        
        try:
            branch = int(input(f'[IN] Branch number for category {category}: '))
            check_branch(branch)
            break
        except (InvalidBranchNumber, ValueError, TypeError):
            print (f"Invalid ID: branch number should be a positive integer.)")
        except:
            raise
    
    return branch

def input_books_number(category, branch_id, retries=RETRIES):
    
    def check_books(books):
        if books <= 0:
            raise InvalidBooksNumber(books)
    
    attempt = 0
    while True:
        attempt += 1
        if attempt > retries:
            raise ConsecutiveWrongAnswers(retries)
        
        try:
            books = int(input(f'[IN] Books number for (category, branch n°) - ({category}, {branch_id}): '))
            check_books(books)
            break
        except (InvalidBooksNumber, ValueError, TypeError):
            print (f"Invalid ID: branch number should be a positive integer.)")
        except:
            raise
    
    return books

And finally, the main loop is now a lot simpler to read:

#%% Main loop

# Initialize the data structure which will contain the number of books per category and per branch. 
books_logs = dict()

for category in ["Computer", "Physics", "Chemistry", "Biology", "Arts"]:
    # Input the number of branch per category
    branch = input_branch_number(category)
    
    # For each category, initialize an empty list. The list will be filled by the number of books per branch
    books_logs[category] = list()
    for branch_id in range(branch):
        # Input the books for each (category, branch_id)
        books = input_books_number(category, branch_id)
        books_logs[category].append(books)

You can now work on the books_logs dictionary to sum per branch, per category, or to get the total.

N.B: For string formatting, instead of summing str objects as you did, I suggest you use the python formatting flag which improves readability:

f'This is a formatting string with {n_characters} characters and {n_words}.'

The element in between {} can be anything, it will be evaluated/executed by python. You could have formulas, e.g.

k = 2
f'I want to format with leading zeros integer {k} to get {str(k).zfill(3)}'

Finally, I chose a very simple data structure, but you can replace the dictionary with another one. Especially, as suggested by Ruthger Righart you could use a pandas dataframe.

What you have done wrong is that you are assigning 0 to books_total for every iteration in category. You will need to declare it before reading the input and add the input to it in every iteration.

books_total = 0 # init here
for i in range(branches):
  for category in ["Computer", "Physics", "Chemistry", "Biology", "Arts"]:
    books = -1
    while books < 0:
      try:
        string_branches = str(i+1)
        books = int(input("Enter the number of books for " + category +  " of branch " + string_branches + " :" ))
      except (ValueError, TypeError):
        print("Number of books must be an integer")
      else:
        if books < 0:
          print("Number of books must be at least 1")
    books_total += books # add the input to books_total

Firstly this statement doubles your no of books, which makes it a wrong calculation

books_total = books + books 

you can do:

books_total += books 

and you have to declare total_books outside of loop and every time you enter a new book, you add that number to total books.

You may want to put your data in a Pandas DataFrame.

#!/usr/bin/env python3

import pandas as pd

branches = 0
while branches < 1:
  try:
    branches = int(input("Enter branches: "))
  except (ValueError, TypeError):
    print("Number of branches must be an integer")
  else:
    if branches < 1:
      print("Number of branches must be at least 1")


df = pd.DataFrame({})

for i in range(branches):
  for category in ["Computer", "Physics", "Chemistry", "Biology", "Arts"]:  
    books = -1
    while books < 0:
      try:
        string_branches = str(i+1)
        books = int(input("Enter the number of books for " + category +  " of branch " + string_branches + " :" ))
        df[category+string_branches] = pd.Series(books)

      except (ValueError, TypeError):
        print("Number of books must be an integer")
      else:
        if books < 0:
          print("Number of books must be at least 1")
        books_total = 0
        books_total = books + books 
books_per_category = books_total  / branches
print("Following the information of all the branches of the main library: ")
print("Total number of books: ",books_total )
print("Average numbers of books per category: ",  books_per_category)

From there you could make different sum operations more easily.

df.apply(sum, axis=1)

Or sum it per category, for example.

df['Computer1']+df['Computer2']

If you have many branches just use the string ('Computer') to compute the sum across branches:

df.loc[:, list(df.columns[df.columns.str.contains(pat = 'Computer')])].apply(sum, axis=1)
Related