-1

I'm facing issues with this function. I don't know anything about hashing, but still receive an error about it. The purpose of my function is to give the name of those who have the highest number of activity (activities are the str in the set named "Groupes", and the function return a set with the names and the number of activity. You can see that members names are given in the dictionary "membres_nom" and number are used to call them. Here is the function:

# membres_nom:dict{int:str}
membres_nom = {538:'Dimitri',802:'Elfriede',147:'Bachir', \
               125:'Amina',153:'Clovis'}




# groupes : dict[str:set[int]]
groupes = {'cinephiles':{802,125,147,153}, \
'travaux manuels':{125,802,153}, \
'cuisine':{153,147,802}, \
'sport':{153,538,802}}


def suractifs(names, group):
    """ Problème Dictionnaire Question 2
        dict{int:str} * dict[str:set[int]] -> tuple[Liste[str],int]
    """

    # nom : List[str]
    nom = []
    # nb_activites : List[int]
    nb_activites = []
    # s : List[str]
    # n : int
    # (i, j, nb_maximal) : tuple[int,int,int]

    i = 0
    nb_maximal = 0
    temp_set = set()

    # (numero, k) : tuple[int, str]
    for numero in names:
        nom.append(names[numero])
        nb_activites.append(0)
        for k in group:
            if numero in group[k]:
                nb_activites[i] += 1
        i = i + 1

    for j in range(0, len(nb_activites)):
        if nb_activites[j] > nb_maximal:
            nb_maximal = nb_activites[j]

    k = 0
    for k in range(0, len(nom)):
        if nb_activites[k] == nb_maximal:
            temp_set.add(nom[k])
    final_set = (temp_set, nb_maximal)

    return final_set
  • 2
    what is the error message? what are you expecting? please provide a [**minimal**, complete and verifiable example](https://stackoverflow.com/help/mcve) that reproduces your problem. – hiro protagonist Mar 13 '18 at 10:26

2 Answers2

1

You are trying to get a set with a set as an element. In the line final_set = {temp_set,nb_maximal}. Set elements should be hashable, but sets are not hashable.

You can return a tuple or a list instead: final_list = [temp_set, nb_maximal]. In fact, just putting return temp_set, nb_maximal will implicitly build a tuple. I think is the way to go, more explicit return type, more clean, etc. For more information about hashability, there are plenty of SO questions, for example: what-do-you-mean-by-hashable-in-python

By the way, it seems you have a bug, since 'Elfriede' and 'Clovis' in your example are involved in 4 activities, and your function is returning 'Bachir' and 'Amina' which are involved in 2 activities.

Here you have an alternative implementation of your function:

def suractifs(names, group):
    # build a list of tuples with the name in the first coordinate and 
    # the number of appearances in activities in the second coordinate
    act_list= [(n, len([a for a, s in group.iteritems() if i in s])) 
                            for i, n in names.iteritems()]
    # sort the list in decresing order of the activities
    act_list.sort(key=lambda t : -t[1])
    # get one of the names with most activities        
    a = act_list[0]
    # return only the filtered list of names that have the maximum 
    #number of activities
    return set( t[0] for t in act_list if t[1] == a[1]),  a[1]

An alternative implementation using Counter as Pitto suggested:

from collections import Counter
def suractifs3(names, group):
    names_in_acts = sum( (list(s) for s in group.itervalues()), [])
    act_list = Counter(names_in_acts).most_common()
    act_list.sort(key=lambda t : -t[1])
    a = act_list[0]
    return set(names[t[0]] for t in act_list if t[1] == a[1]), a[1]
eguaio
  • 3,754
  • 1
  • 24
  • 38
  • Two masterpieces, incredibly well done. I wish I could write such good and pythonic code one day, bravo! – Pitto Mar 13 '18 at 13:26
  • hahaha, lol. That code is not so good. I could tell you at least ten reasons why starting with the fact that is hard to read. The only good thing is that the two `for` iterations inside the list comprehension are faster than putting two true `for` iterations. But a price is paid in memory usage. – eguaio Mar 13 '18 at 14:08
  • @nolw38 don't forget to mark one of the answers if you think they solved your problem. – eguaio Mar 14 '18 at 11:42
0

I am not sure I understood well your code so I've tried to implement on my own.

This is one possible / easy / verbose solution. A more pythonic one would probably involve the use of Counter (https://pymotw.com/2/collections/counter.html)

Ask questions if you need any details.

# membres_nom:dict{int:str}
membres_nom = {'Dimitri':538,'Elfriede':802,'Bachir':147, \
               'Amina':125,'Clovis':153}

# groupes : dict[str:set[int]]
groupes = {'cinephiles':{802,125,147,153}, \
'travaux manuels':{125,802,153}, \
'cuisine':{153,147,802}, \
'sport':{153,538,802}}

def highest_activity_numbers(names, group):
    # Here I create a dictionary that will contain names / activity number
    activity_count_per_name = {}
    # Using iteritems we can access the key / value in a dictionary
    # In here I access category_name and user_ids from groupes
    for category_name, user_ids in groupes.iteritems():
        # Here I access user_name and user_id from membres_nom
        # I've clearly decided for the variables name that
        # made more sense to me but you can use anything you prefer
        for user_name, user_id in membres_nom.iteritems():
            # At this point, for example, at the 1st round of the loop I
            # access 'Dimitri' and I check if he is in the users of
            # 'cinephiles'
            if user_id in user_ids:
                # If 'Dimitri' is in the list I get his current activity
                # counter and add a unit to it
                ac = activity_count_per_name.get(user_name, 0)
                activity_count_per_name[user_name] = ac + 1
    # Time to return the loot :)
    return activity_count_per_name

print highest_activity_numbers(membres_nom,groupes)
Pitto
  • 8,229
  • 3
  • 42
  • 51
  • 1
    Just two comments: `if user_name in activity_count_per_name` is better than `activity_count_per_name.get(user_name) == None`. Also, the `if ... else ` statement can be replaced by the lines `ac activity_count_per_name.get(user_name, 0)` and `activity_count_per_name[user_name] = ac + 1`. – eguaio Mar 13 '18 at 11:36
  • Hi eguaio and thanks for the comment... I think that if user_name in activity_count_per_name will raise a keyerror in case it doesn't exist, right? Thanks, I will add it to my code :) – Pitto Mar 13 '18 at 11:43
  • 1
    `if user_name in activity_count_per_name` will never raise a keyerror, it will just return false if the key is not in the dictionary. It is the correct and recommended idiom to check that. :) – eguaio Mar 13 '18 at 12:34
  • Ooooooh you are right I should use a list. I will try it tonight and show you the result –  Mar 13 '18 at 12:56
  • 1
    Simply great, @eguaio, thanks for the learning here! I am happy to help, nolw38, just keep in mind we're using dictionaries here {} not lists []. A dictionary contains a key / value pair, a list just a list of elements. – Pitto Mar 13 '18 at 13:25
  • I changed my code :-) (See in the first message) Now it works. But there are a lot of things I can improve here. First, I will try to do some comprehension. It would be simpler. I will do it tomorrow. If you have any suggests, feel free to tell me (I would rather keep my former code though because I prefer to keep a work "from me" even if your codes are way more efficient. –  Mar 13 '18 at 18:38
  • Good day, @nolw38! I hope your coding is progressing fine. Please do not forget to upvote / choose the answer I've provided if it helped you :) – Pitto May 03 '18 at 08:21