How do I perform an operation on only the int part of lists?

Viewed 141

I'm trying to complete this assignment for a class. The instructions say to:

L is a list of lists. The first item in each sublist is a student name and the rest of the items in the sublist are grade values (float or int). Return a new list of lists where each sublist has length 2 of the form [student_name (str), average grade (float)]

>>> average_grade([['Bob', 56, 80, 72, 90], ['Alice', 60, 88, 44, 70], ['Joe', 44, 100, 80, 60, 50]])
[['Bob', 74.5], ['Alice', 65.5], ['Joe', 66.8]]

The problem that I'm having with my code is that I'm trying to be able to enter in the lists, but only find the average of the integers. So far I have this:

for int in ['Bob', 'Alice', 'Joe']:
    return (map(L[sum(int)/len(int)]), map(L[sum(int)/len(int)]), map(L[sum(int)/len(int)]))

But when I enter in even only one list, I get this:

builtins.TypeError: unsupported operand type(s) for +: 'int' and 'str'

I'm not sure what to do here. Any help?

6 Answers

You need to deconstruct each of the sublists.

def average_grade(grade_db):
    averages = []
    for student_grades in grade_db:
        name = student_grades[0]
        grades = student_grades[1:]
        # proceed to update results
    return averages

results = average_grade([['Bob', 56, 80, 72, 90], ['Alice', 60, 88, 44, 70], ['Joe', 44, 100, 80, 60, 50]])

The first time through the loop on the given call, student_grades == ['Bob', 56, 80, 72, 90], the second time through student_grades == ['Alice', 60, 88, 44, 70], and so on. Thus, the first time through the loop, name == 'Bob' and grades == [56, 80, 72, 90], etc.

def average_grade(L):
    return [[el[0], float(sum(el[1:]))/len(el[1:])] for el in L]

This is the list comprehension approach.

Iterate over your list of lists L: for each element el create a new list with first element being el[0] (name), and second element being the average. The average is computed using float(sum(x))/len(x), using the sliced list el[1:]. The slicing el[1:] means take a sublist of el starting from index 1 up to the end. float() is needed here since the sum() can be int if all the elements are int.

Possible implementation below:

test = [['Bob', 56, 80, 72, 90], ['Alice', 60, 88, 44, 70], ['Joe', 44, 100, 80, 60, 50]]


def mean(numbers):
    'Takes a list of numbers as argument and returns a float as their mean'
    return float(sum(numbers)) / max(len(numbers), 1)


def average_grade(list_of_students):
    'Takes the lists of students as arguments and returns a list of lists containing the name and average grade'
    # We build an empty list to populate as we parse the list of students
    students = []
    # Parse each sublist of the list of students
    for student in list_of_students:
        # We add to the students list a new list made of:
        # element 0 is the first element of each sublist, meaning the name
        # element 1 is the result of applying the mean function over a list of floats created from each entry in the grades list
        students.append([student[0], mean([float(i) for i in student[1:]])])
    # And we return the students list
    return students

print(average_grade(test))

Possible bugs with this implementation:

What if one of your grades cannot be converted to a float? Hint -> TypeError on cast in line 18.

What if you have an empty list of grades for one of your entries?

What if you have a missing name?

What if your entry data is not a list?

What if that list is completely empty?

Possible teacher questions: Why would you instantiate an empty list inside your average function? Why not just declare it outside? Hint: take the line outside of the function then call average_grade() twice and see what happens.

Why do you cast to float before you calculate the mean? Here it's done to avoid calculating the mean of non string/int values. If one such value is in the list of grades, the script will just error out with a TypeError. You could circumvent this by surrounding the entire cast in a try/catch clause and either stop execution when you encounter an unexpected value or just discard it.

This isn't the MOST efficient way to do this in Python but I believe it will make it more clear for beginners:

final_list = []
for sublist in lists:
    new_list = sublist[0] # Student name is always in the first index
    new_list.append(sum(sublist[1:]) / float(len(sublist[1:]))) # Take an average of the rest of the sublist and append it to the new list
    final_list.append(new_list) # Append the new_list e.g. ['Bob', 74.5] to the final list

This is what the attempted solution (in the question) is doing:

for int in ['Bob', 'Alice', 'Joe']:
  return (map(L[sum(int)/len(int)]), map(L[sum(int)/len(int)]), map(L[sum(int)/len(int)]))
  1. It is iterating over names 'Bob', 'Alice' and 'Joe'
  2. At first iteration the value of the variable that is named as int (BTW int is a python keyword, so consider choosing a better name) has value 'Bob'
  3. At this point the your return statement translates to something equivalent of return (map(L[sum('Bob')/len('Bob'), map(L[sum('Bob')/len('Bob'), map(L[sum('Bob')/len('Bob'))

Notice that you are trying to sum a string object. This raises the TypeError

builtins.TypeError: unsupported operand type(s) for +: 'int' and 'str'

Now, to dive deeper, where is the 'int' type coming in, in the above error. The sum function is essentially trying to do this:

S = 0
S = S + 'B'
S = S + 'o'
S = S + 'b'
return S

As you can see, we can't add up an integer and a string. It makes no sense.

Here is an example of an approach that might work:

map(lambda x: [x[0],sum(x[1:])/(float)(len(x)-1)], L)

The above solution will iterate over each item in L. An item here is a sublist like ['Bob', 56, 80, 72, 90]. Then, the lambda function maps this sublist to ['Bob', sum([56, 80, 72, 90])/(float)(len(['Bob', 56, 80, 72, 90])-1)].

Each iteration produces a mapped list as above and that's one of the potential solutions. HTH.

You can use list comprehension which is one line solution :

with loop:

your_data=[['Bob', 56, 80, 72, 90], ['Alice', 60, 88, 44, 70], ['Joe', 44, 100, 80, 60, 50]]

print([[item[0],sum(item[1:])/len(item[1:])] for item in your_data])

output:

[['Bob', 74.5], ['Alice', 65.5], ['Joe', 66.8]]

without loop :

If you don't want loop you can try with lambda :

print(list(map(lambda x:[x[0],sum(x[1:])/len(x[1:])],your_data)))

output:

[['Bob', 74.5], ['Alice', 65.5], ['Joe', 66.8]]
Related