2

I am creating a function to add the marks of each array(homework, quiz, test) inside an object(student1). There definitely arises errors as the code tries to add "Lloyd". I want to check the data-type of value of "name" so as to carry the arithmetic operation only if the value of the key is number. Please Suggest.

student1 = {
    "name": "Lloyd",
    "homework": [90, 97, 75, 92],
    "quiz": [88, 40, 94],
    "test": [75, 90]
}    

def eachSubjAverage(std):             
    for item in std:                        
        total = sum(std[item])   #totalling each marks
        std[item].append(total)     

        average_no = total/(len(std[item])-1) #averaging the marks
        std[item].append(average_no) 

eachSubjAverage(student1)
madhur
  • 101
  • 2
  • 7
  • Note that dictionaries are not ordered, so appending in order will not be the order you expect. http://stackoverflow.com/questions/15479928/why-is-the-order-in-python-dictionaries-and-sets-arbitrary – Tim Sep 02 '15 at 12:41
  • @Tim : the code is appending to lists, not dicts - FWIW, dicts dont have an "append" method. It's still a bad idea to append average and total to the marks, but for a different reason... – bruno desthuilliers Sep 02 '15 at 12:54

5 Answers5

7

An obvious case of a XY problem...

Your real problem is a wrong data structure that stores heterogenous informations (the student's name and it's marks) the same way. The RightSolution(tm) is to use a better data structure, ie:

student1 = {
    "personal_infos" : {
        "name": "Lloyd",
    },
    "marks": {
        "homework": [90, 97, 75, 92],
        "quiz": [88, 40, 94],
        "test": [75, 90]
    },
    "totals": {}
    "averages": {}
  }

}

Once you have this, you don't have to test whether you have a string or num as value:

def eachSubjAverage(student):             
    for subject, marks in student["marks"].items():                        
        total = sum(marks)   #totalling each marks
        student["totals"][subject] = total     
        average = total / (len(marks))
        student["averages"][subject] = average

Note that you could layout your data differently, ie per subject:

student1 = {
    "personal_infos" : {
        "name": "Lloyd",
    },
    "subjects": {
        "homework": {
            "marks" : [90, 97, 75, 92],
            "total" : None,
            "average" : None
            }, 
        "quiz": {
            "marks" : [88, 40, 94],
            "total" : None,
            "average" : None
            }, 
        "test": {
            "marks" : [75, 90],
            "total" : None,
            "average" : None
            }, 
        },
 }


def eachSubjAverage(student):             
    for subject, data in student["subjects"].items():                        
        total = sum(data["marks"])   #totalling each marks
        data["total"] = total     
        average = total / (len(data["marks"]))
        data["average"] = average

Note that if you don't have the option to fix the data structure (external data or else), you still don't want to rely on type-checking (which is brittle at best) - you want to test the key itself, either by whitelisting the subjects names or blacklisting the "non-subjects" names, ie:

# blacklist non-subjects
NON_SUBJECTS = ("name",)

def your_func(student):
    for key, value in student.items():
        if key in NON_SUBJECTS:
            continue
        compute_stuff_here()

Oh and yes: adding the total and average in the marks list is also a good way to shoot yourself in the foot - once it's done, you can't tell wether the last two "marks" are marks or (total, average).

Community
  • 1
  • 1
bruno desthuilliers
  • 75,974
  • 6
  • 88
  • 118
  • Thank you Bruno for providing me the fundamentally proper approach on managing (heterogeneous) informations inside an object. I am now aware of the xy nature of my question. I will be following this approach as it seems the most cogent technique. You provided the solution for the situation where I don't have the option to fix the data structure. I couldn't understand the for and if statements. Please pardon my basic queries. Could you provide me the solution in reference to my question? Thanks for your kind support! – madhur Sep 03 '15 at 06:17
  • "I couldn't understand the for and if statements." Well this is fairlay basic Python stuff - what is it that you don't understand exactly ? – bruno desthuilliers Sep 03 '15 at 08:12
  • Sorry to bother you. I do understand it now. – madhur Sep 03 '15 at 08:27
2
for item in std:
    if isinstance(std[item],list):
        total = sum(std[item])

if you want to make sure that the elements in the list is of type int, then

for item in std:
    if isinstance(std[item],list) and all(isinstance(e,int) for e in student1[item]):
        total = sum(std[item])

learn about isinstance() method

taesu
  • 4,482
  • 4
  • 23
  • 41
  • And then someday the dict grows a list of ints that are not marks, and they are treated as such by the code. – bruno desthuilliers Sep 02 '15 at 12:55
  • @brunodesthuilliers that's not my problem is it? I'm simply trying to answer the question. Am I suppose to predict the future? – taesu Sep 02 '15 at 13:02
1

There are a few methods to check the value of the data. try/except construction looks bulky, but there are more shorter ways.

First of all you can use function type, which returns you type of the object:

>>> type(student1['name']) == str
True
>>> type(student1['test']) == list
True

Or, to use a very simple trick. Multiplying any object to zero returns an empty object or 0 if int:

>>> student1['name']*0 == ""
True
>>> student1['test']*0 == []
True
>>> student1['test'][1]*0 == 0
True
Sdwdaw
  • 1,037
  • 7
  • 14
  • 1
    `isinstance(obj, cls)` is usually a better choice than testing type equality. Oh and yes also, `type` is actually a class, not a function. – bruno desthuilliers Sep 02 '15 at 14:47
  • @Sdwdaw: Thanks for the solution. I learned that this isn't the best approach. Though, it have some caveats, it solved my problem. – madhur Sep 03 '15 at 05:54
-2

You can check whether the value is iterable:

hasattr(std[item], '__iter__')

and then check that each member is a Number:

all( map ( lambda x: isinstance(x,numbers.Number), std[item] ) )
Community
  • 1
  • 1
igon
  • 3,016
  • 1
  • 22
  • 37
-2

You could use a try except:

for item in std:
    try:
        total = sum(std[item])
    except TypeError:
        pass
Tim
  • 2,563
  • 1
  • 23
  • 31