Build a Budget App - Build a Budget App

Tell us what’s happening:

test case 19, 20, 21 seems to me covered but not really passing by test case checker!!

where it needs some changes for completing this test cases…

thanks for looking into this much appreciated

Your code so far

import math

class Category:
    def __init__(self, name):
        self.ledger = []
        self.name = name

    def get_ledger(self):
        return self.ledger

    def get_name(self):
        return self.name

    def deposit(self, amount, descr=''):
        self.ledger.append({'amount': amount, 'description': descr})

    def withdraw(self, amount, descr=''):
        if self.check_funds(amount):
            self.ledger.append({'amount': -amount, 'description': descr})
            return True
        else:
            return False
    
    def get_balance(self):
        s = 0
        for item in self.ledger:
            s += item['amount']
        
        return s

    def check_funds(self, amount):
        return amount <= self.get_balance()

    def transfer(self, amount, dest_category):
        if self.check_funds(amount):
                withdraws = self.withdraw(amount, f"Transfer to {dest_category.name}")
                deposits = dest_category.deposit(amount, f"Transfer from {self.name}")
                return True
        else:
            return False
    
    def decorate_category(self):
        left_pad = (30 - len(self.name)) // 2
        right_pad = (30 - len(self.name)) - left_pad
        dec_tit = "*" * left_pad + self.name + "*" * right_pad
        # return(dec_tit +"\n")
        return(dec_tit)

    def print_ledger(self):
        line = ''
        for item in self.ledger:
            a, d = item
            amt = item[a]
            des = item[d]
            
            chk_left = 23 - len(des)
            
            amt_ra = "{:.2f}".format(amt).rjust(7)
            des_la = ""
            
            if chk_left < 0:
                des_la = des[0:23]
            else:
                des_la = des.ljust(23)

            # print(des_la + amt_ra)
            line += (des_la + amt_ra) + "\n"
        
        return line
    
    def print_ledger_total(self):
        sum = 0
        for item in self.ledger:
            a,d = item
            sum += item[a]
        
        return(f"Total: {sum}")

    def __str__(self):
        # Build the string representation
        return f"{self.decorate_category()}\n{self.print_ledger()}{self.print_ledger_total()}"

def create_spend_chart(categories):
    withdrawals = {}
    for category in categories:
        total = 0
        for transaction in category.get_ledger():
            amount, descr = transaction
            amt = transaction[amount]

            if(amt < 0):
                total += (-amt)

        tot = total

        withdrawals[category] = tot

    percentages = {}

    total_withdrawals = sum(withdrawals.values())

    def custom_round_to_nearest_10(x, threshold=0.5):
        """
        Rounds a number to the nearest 10.
        If the fractional part of (x/10) is >= threshold, it rounds up.
        Otherwise, it rounds down.
        """
        base = int(x // 10) * 10
        remainder = x % 10
        if remainder / 10 >= threshold:
            return base + 10
        else:
            return base

    for category, withdrawal in withdrawals.items():
        # 1. Calculate the percentage relative to the TOTAL withdrawals, not 100
        # (Assuming total_withdrawals is calculated beforehand)
        perc = (withdrawal / total_withdrawals) * 100

        print(perc, "perc")
        
        # 2. Force round DOWN to the nearest 10 using integer division
        calc = custom_round_to_nearest_10(perc)

        print(calc, "calc")
        
        percentages[category.get_name()] = calc

    def top_side():
        levels = [100, 90, 80, 70, 60, 50, 40, 30, 20, 10, 0]
        chart_lines = []

        for v in levels:
            # Format the single Y-axis label to align nicely (3 spaces wide)
            row = f"{v:>3}|"
            
            # Check every category at this specific height level
            for name, amt in percentages.items():
                # If the category amount is greater or equal, draw the bar segment
                if amt >= v:
                    row += "o  "
                else:
                    row += ""

            if v==0:
                # row += f" {name} "
                row += "  "
                    
            chart_lines.append(row)
            
        return "\n".join(chart_lines) + "\n"


    divs = ("---" * len(percentages.keys())).rjust(10)
    # divs = ("---" * len(percentages.keys()) + "--").rjust(12)

    cn = ''
    for category, withdrawal in withdrawals.items():
        print(category)

    return("Percentage spent by category\n" +top_side() + divs)

Your browser information:

User Agent is: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36

Challenge Information:

Build a Budget App - Build a Budget App

GitHub Link: freeCodeCamp/curriculum/challenges/english/blocks/lab-budget-app/5e44413e903586ffb414c94e.md at main · freeCodeCamp/freeCodeCamp · GitHub

I am testing like this:

food = Category('Food')
food.deposit(1000, 'initial deposit')
food.withdraw(10.15, 'groceries')
food.withdraw(15.89, 'restaurant and more food for dessert')
clothing = Category('Clothing')
food.transfer(50, clothing)

print(create_spend_chart([food, clothing]))
print(create_spend_chart([clothing, food]))

changing the order of the categories, the chart stay the same instead of changing the order of the bars

how so? as per this example it currently only showing eithdrawals for food category as clothing category has none withdrawals in it, so output is showing “Food” is taking up 100% of withdrawals and “Clothing” is taking up “0%”

am i correct with my analysis of this (your provided) usecase?

thanks and happy coding :slight_smile:

I am printing two charts, do you see the second one?
the second one shows 100% for clothing and 0% for food, which is not correct

oooohh, interesting… so category also needs to maintain its order on chart? as both cases “food” has 100% withdrawals and only thing that was different is “order of them” in paramters, is it a good enough dignose of this issue? thanks :slight_smile:

yes, the categories need to maintain the order

thanks, ill look into that and try to adjust implementation to meet this requirement, much appreciated your feedback, happy coding :slight_smile:

i brought some changes to it and now its currently maintaining order in chart as you pointed it out

def create_spend_chart(categories):
    withdrawals = {}
    for category in categories:
        total = 0
        for transaction in category.get_ledger():
            amount, descr = transaction
            amt = transaction[amount]

            if(amt < 0):
                total += (-amt)

        tot = total

        withdrawals[category] = tot

    total_withdrawals = sum(withdrawals.values())

    print(total_withdrawals)

    def custom_round_to_nearest_10(x, threshold=0.5):
        """
        Rounds a number to the nearest 10.
        If the fractional part of (x/10) is >= threshold, it rounds up.
        Otherwise, it rounds down.
        """
        base = int(x // 10) * 10
        remainder = x % 10
        if remainder / 10 >= threshold:
            return base + 10
        else:
            return base

    percentages = []

    for category, withdrawal in withdrawals.items():
        # 1. Calculate the percentage relative to the TOTAL withdrawals, not 100
        # (Assuming total_withdrawals is calculated beforehand)
        perc = (withdrawal / total_withdrawals) * 100

        print(perc, "perc")
        
        # 2. Force round DOWN to the nearest 10 
        calc = custom_round_to_nearest_10(perc)

        print(calc, "calc")
        
        percentages.append((category.get_name(), calc))

    def top_side():
        levels = [100, 90, 80, 70, 60, 50, 40, 30, 20, 10, 0]
        chart_lines = []

        for v in levels:
            # Format the single Y-axis label to align nicely (3 spaces wide)
            row = f"{v:>3}|"
            
            # Check every category at this specific height level
            for name, amt in percentages:
                # If the category amount is greater or equal, draw the bar segment
                if amt >= v:
                    row += "o  "
                    # row += name[0]
                else:
                    row += "   "

            if v==0:
                # row += f" {name} "
                row += "  "
                    
            chart_lines.append(row)
            
        return "\n".join(chart_lines) + "\n"


    divs = ("---" * len(percentages)).rjust(10)

    cn = ''
    for category, withdrawal in withdrawals.items():
        print(category)

and test cases still remains unsolved… any more pointers or feedback would be very useful to finish them and moving onto next, thanks for looking into this and much appreciate your help :slight_smile:

Hi!
Between the slash and the columns with ‘o’, a space has worked for me.

And be careful with the horizontal hyphen (-) line, it causes a lot of problems at the end.

Good luck!

i opened a new topic with updated codes to seek next feedbackand ill be closing this chosing this as a solution (if needed) to declutter my attempts. thanks and happy coding :slight_smile:

Tell us what’s happening:

Even though it seems i covered all of its usecases but test cases checkers doesnt seem to be taking them as correct solutions, any pointers on this would be fantastic, thanks

Your code so far


import math
from itertools import zip_longest

class Category:
    def __init__(self, name):
        self.ledger = []
        self.name = name

    def get_ledger(self):
        return self.ledger

    def get_name(self):
        return self.name

    def deposit(self, amount, descr=''):
        self.ledger.append({'amount': amount, 'description': descr})

    def withdraw(self, amount, descr=''):
        if self.check_funds(amount):
            self.ledger.append({'amount': -amount, 'description': descr})
            return True
        else:
            return False
    
    def get_balance(self):
        s = 0
        for item in self.ledger:
            s += item['amount']
        
        return s

    def check_funds(self, amount):
        return amount <= self.get_balance()

    def transfer(self, amount, dest_category):
        if self.check_funds(amount):
                withdraws = self.withdraw(amount, f"Transfer to {dest_category.name}")
                deposits = dest_category.deposit(amount, f"Transfer from {self.name}")
                return True
        else:
            return False
    
    def decorate_category(self):
        left_pad = (30 - len(self.name)) // 2
        right_pad = (30 - len(self.name)) - left_pad
        dec_tit = "*" * left_pad + self.name + "*" * right_pad
        # return(dec_tit +"\n")
        return(dec_tit)

    def print_ledger(self):
        line = ''
        for item in self.ledger:
            a, d = item
            amt = item[a]
            des = item[d]
            
            chk_left = 23 - len(des)
            
            amt_ra = "{:.2f}".format(amt).rjust(7)
            des_la = ""
            
            if chk_left < 0:
                des_la = des[0:23]
            else:
                des_la = des.ljust(23)

            # print(des_la + amt_ra)
            line += (des_la + amt_ra) + "\n"
        
        return line
    
    def print_ledger_total(self):
        sum = 0
        for item in self.ledger:
            a,d = item
            sum += item[a]
        
        return(f"Total: {sum}")

    def __str__(self):
        # Build the string representation
        return f"{self.decorate_category()}\n{self.print_ledger()}{self.print_ledger_total()}"

def create_spend_chart(categories):
    withdrawals = {}
    for category in categories:
        total = 0
        for transaction in category.get_ledger():
            amount, descr = transaction
            amt = transaction[amount]

            if(amt < 0):
                total += (-amt)

        tot = total

        withdrawals[category] = tot

    percentages = {}

    total_withdrawals = sum(withdrawals.values())

    def custom_round_to_nearest_10(x, threshold=0.5):
        """
        Rounds a number to the nearest 10.
        If the fractional part of (x/10) is >= threshold, it rounds up.
        Otherwise, it rounds down.
        """
        base = int(x // 10) * 10
        remainder = x % 10
        if remainder / 10 >= threshold:
            return base + 10
        else:
            return base

    for category, withdrawal in withdrawals.items():
        # 1. Calculate the percentage relative to the TOTAL withdrawals, not 100
        # (Assuming total_withdrawals is calculated beforehand)
        perc = (withdrawal / total_withdrawals) * 100

        # print(perc, "perc")
        
        # 2. Force round DOWN to the nearest 10 using integer division
        calc = custom_round_to_nearest_10(perc)

        # print(calc, "calculate: rounded percentage")
        
        percentages[category.get_name()] = calc

    def top_side():
        levels = [100, 90, 80, 70, 60, 50, 40, 30, 20, 10, 0]
        chart_lines = []

        for v in levels:
            # Format the single Y-axis label to align nicely (3 spaces wide)
            row = f"{v:>3}|"
            
            # Check every category at this specific height level
            for name, amt in percentages.items():
                # If the category amount is greater or equal, draw the bar segment
                if amt >= v:
                    row += " o"
                else:
                    row += "  "

            if v==0:
                row += "  "
                    
            chart_lines.append(row)
            
        return "\n".join(chart_lines) + "\n"


    divs = ("---" * len(percentages.keys())).rjust(10)

    def print_category_names_vertically():
        names = list(percentages.keys())

        zip_names = zip_longest(*names, fillvalue=" ")

        lines = []

        for row in zip_names:
            line_content = ' '.join(row)
            lines.append(line_content.rjust(8))

        return "\n".join(lines)
                    

    names_vertically = print_category_names_vertically()

    # return("Percentage spent by category\n" +top_side())
    return("Percentage spent by category\n" +top_side() + divs + "\n" + names_vertically)

food = Category('Food')
food.deposit(1000, 'initial deposit')
food.withdraw(10.15, 'groceries')
food.withdraw(15.89, 'restaurant and more food for dessert')
clothing = Category('Clothing')
food.transfer(50, clothing)
clothing.withdraw(10.15, 'tshirt')

print(create_spend_chart([food, clothing]))
print(create_spend_chart([clothing, food]))

Your browser information:

User Agent is: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36

Challenge Information:

Build a Budget App - Build a Budget App

GitHub Link: freeCodeCamp/curriculum/challenges/english/blocks/lab-budget-app/5e44413e903586ffb414c94e.md at main · freeCodeCamp/freeCodeCamp · GitHub


clothing is 11.8%, food is 88.2%

are your bars rounded down to the nearest 10?

yeah clothing gets rounded to “10%” and food becomes “90%”

that is not rounding down, considering that 90% is higher than 88%

ohhh i thought it was expecting it be closer to its multiple of 10… i initially started with that then moved to this implementation!!

thanks ill try this one out see how that goes, much appreciated :slight_smile:

it says specifically to round down, so if you don’t do that it’s not going to accept your code

of course, ill adjust my implementation to see how that goes, thanks and happy coding :slight_smile:

i did try to rounded down to nearest 10 but its still remains same!!

def custom_round_to_nearest_10(x):
        base = int(x // 10)
        multiple_of_10 = base * 10
        return multiple_of_10

am i missing something here too? thanks for looking into this, much appreciated :slight_smile:

it is working, see:

print(custom_round_to_nearest_10(87)) #80

please post your updated code

  • yeah you’re right but i was referring to “test case” passing thing :grin my bad i wasnt clear enough…

here is my updated code with that function, thanks again for looking into this, much appreciated :slight_smile:

import math
from itertools import zip_longest

class Category:
    def __init__(self, name):
        self.ledger = []
        self.name = name

    def get_ledger(self):
        return self.ledger

    def get_name(self):
        return self.name

    def deposit(self, amount, descr=''):
        self.ledger.append({'amount': amount, 'description': descr})

    def withdraw(self, amount, descr=''):
        if self.check_funds(amount):
            self.ledger.append({'amount': -amount, 'description': descr})
            return True
        else:
            return False
    
    def get_balance(self):
        s = 0
        for item in self.ledger:
            s += item['amount']
        
        return s

    def check_funds(self, amount):
        return amount <= self.get_balance()

    def transfer(self, amount, dest_category):
        if self.check_funds(amount):
                withdraws = self.withdraw(amount, f"Transfer to {dest_category.name}")
                deposits = dest_category.deposit(amount, f"Transfer from {self.name}")
                return True
        else:
            return False
    
    def decorate_category(self):
        left_pad = (30 - len(self.name)) // 2
        right_pad = (30 - len(self.name)) - left_pad
        dec_tit = "*" * left_pad + self.name + "*" * right_pad
        # return(dec_tit +"\n")
        return(dec_tit)

    def print_ledger(self):
        line = ''
        for item in self.ledger:
            a, d = item
            amt = item[a]
            des = item[d]
            
            chk_left = 23 - len(des)
            
            amt_ra = "{:.2f}".format(amt).rjust(7)
            des_la = ""
            
            if chk_left < 0:
                des_la = des[0:23]
            else:
                des_la = des.ljust(23)

            # print(des_la + amt_ra)
            line += (des_la + amt_ra) + "\n"
        
        return line
    
    def print_ledger_total(self):
        sum = 0
        for item in self.ledger:
            a,d = item
            sum += item[a]
        
        return(f"Total: {sum}")

    def __str__(self):
        # Build the string representation
        return f"{self.decorate_category()}\n{self.print_ledger()}{self.print_ledger_total()}"

def create_spend_chart(categories):
    withdrawals = {}
    for category in categories:
        total = 0
        for transaction in category.get_ledger():
            amount, descr = transaction
            amt = transaction[amount]

            if(amt < 0):
                total += (-amt)

        tot = total

        withdrawals[category] = tot

    percentages = {}

    total_withdrawals = sum(withdrawals.values())

    def custom_round_to_nearest_10(x):
        base = int(x // 10)
        multiple_of_10 = base * 10
        return multiple_of_10

    for category, withdrawal in withdrawals.items():
        # 1. Calculate the percentage relative to the TOTAL withdrawals, not 100
        # (Assuming total_withdrawals is calculated beforehand)
        perc = (withdrawal / total_withdrawals) * 100

        # print(perc, "perc")
        
        calc = custom_round_to_nearest_10(perc)

        # print(calc, "calculate: rounded percentage")
        
        percentages[category.get_name()] = calc

    def top_side():
        levels = [100, 90, 80, 70, 60, 50, 40, 30, 20, 10, 0]
        chart_lines = []

        for v in levels:
            # Format the single Y-axis label to align nicely (3 spaces wide)
            row = f"{v:>3}|"
            
            # Check every category at this specific height level
            for name, amt in percentages.items():
                # If the category amount is greater or equal, draw the bar segment
                if amt >= v:
                    row += " o"
                else:
                    row += "  "

            if v==0:
                row += "  "
                    
            chart_lines.append(row)
            
        return "\n".join(chart_lines) + "\n"


    divs = ("---" * len(percentages.keys())).rjust(10)

    def print_category_names_vertically():
        names = list(percentages.keys())

        zip_names = zip_longest(*names, fillvalue=" ")

        lines = []

        for row in zip_names:
            line_content = ' '.join(row)
            lines.append(line_content.rjust(8))

        return "\n".join(lines)
                    

    names_vertically = print_category_names_vertically()

    # return("Percentage spent by category\n" +top_side())
    return("Percentage spent by category\n" +top_side() + divs + "\n" + names_vertically)

food = Category('Food')
food.deposit(1000, 'initial deposit')
food.withdraw(10.15, 'groceries')
food.withdraw(15.89, 'restaurant and more food for dessert')
clothing = Category('Clothing')
food.transfer(50, clothing)
clothing.withdraw(10.15, 'tshirt')

print(create_spend_chart([food, clothing]))
print(create_spend_chart([clothing, food]))