11

I have a list of people:

[
    {'name' : 'John', 'wins' : 10 },
    {'name' : 'Sally', 'wins' : 0 },
    {'name' : 'Fred', 'wins' : 3 },
    {'name' : 'Mary', 'wins' : 6 }
]

I am adding wins using a list of names (['Fred', 'Mary', 'Sally']). I don't know if the name is in the list of people already, and I need to insert a new record if not. Currently I'm doing the following:

name = 'John'
person = None
pidx = None
for p in people_list:
    if p['name'] == name:
        person = p
        pidx = people_list.index(p)
        break
if person is None:
    person = {'name' : name, 'wins' : 0}
person['wins'] += 1
if pidx is None:
    people_list.append(person)
else
    people_list[pidx] = person

Is there a better way to do this with a list? Given that I'm saving this to MongoDB I can't use a dict as it will save as an object and I want to use native array functions for sorting and mapping that aren't available for objects.

0

5 Answers 5

14

I'm assuming here that you don't want to use any structure other than the list. Your code should work, although you unnecessarily write the dictionary back to the list after updating it. Dictionaries are copied by reference, so once you update it, it stays updated in the list. After a little housekeeping, your code could look like this:

def add_win(people_list, name):
    person = find_person(people_list, name)
    person['wins'] += 1

def find_person(people_list, name):
    for person in people_list:
        if person['name'] == name:
            return person
    person = {'name': name, 'wins': 0}
    people_list.append(person)
    return person
Sign up to request clarification or add additional context in comments.

Comments

9

Yes, use a dict.

wins = {}
for name in winners:
    wins.setdefault(name, 0)
    wins[name] += 1

edit:

index = {}
for name in wins:
    person = index.setdefault(name, { 'name' : name, 'wins': 0 })
    if person['wins'] == 0:
        person_list.append(person)
    person['wins'] += 1

4 Comments

I don't want to use a dict. I want to use a list. Saying "use a dict" is useless because I want it to be a list. I'm saving this to MongoDB, using a dict makes it an object and I want to use some native array functions.
@Josh K: see my answer about using a list and a set
@MAK: What's not clear? I know that using a dict would provide a cleaner access pattern but I can't.
@Josh: the answer is still "use a dict". See my edit for how you can do both.
7

If you don't want a dict permanently use one temporarily.

people = [
    {'name' : 'John', 'wins' : 10 },
    {'name' : 'Sally', 'wins' : 0 },
    {'name' : 'Fred', 'wins' : 3 },
    {'name' : 'Mary', 'wins' : 6 }
]

wins = ['Fred', 'Mary', 'Sally']

people_dict = dict((p["name"], p) for p in people)

for winner in wins:
    people_dict[winner].setdefault("wins", 0)
    people_dict[winner]["wins"] += 1

people = people_dict.values()

4 Comments

Can you convert from a list to a dict? I'm pulling a list out of MongoDB.
that is precisely what is happening in the example code on the line where people_dict is defined. The key is the name, the value is the dict you have.
What's the processing overhead for converting to and from the list / dict?
Less than the processing overhead of searching a list every time you want to increment a number within it.
4

Your access pattern dictates the use of a different data structure (or at least another helper data structure). Scanning the list as you're doing is in fact the right thing to do if you're using a list, but you shouldn't be using a list (if you want it to be efficient, anyhow).

If the order of the list doesn't matter, you should use a Dictionary (python dict). If it does, you should use an OrderedDict from the collections module.

You could also use two separate data structures - the list you already have, and additionally a set containing just the names in the list so you have quick access to test inclusion or not. However, the set doesn't help you access the actual name data quickly (you'd still have to do a linear search in the list for that), so it would only be a helpful pattern if you merely were testing inclusion, but otherwise always walking the list as it was inserted.

Edit: it seems like what you might actually want is a list and a dict, where the dictionary is a mapping between the name and the index in the list. Alternatively you could still use a dict or OrderedDict, but insert them into Mongo as an array by using dict.iteritems() to create an array (or what would look like an array to Mongo) on insertion. You could use various mutators from zip to things in itertools to dynamically build up the objects you need in your resultant array.

1 Comment

Note that collections.OrderedDict is new in Python 2.7, but there's an equivalent recipe for OrderedDict for earlier versions. See the documentation for details.
0

This specific case is implemented by the collections.Counter type. Along with array generators, this is one expression:

[{'name':name, 'wins':wins}
 for name, wins in Counter(names).items()]

If you want a specific order, sorted() is the easiest way (this also uses a plain generator (), rather than an array generator [], since it's temporary):

sorted(({'name':name, 'wins':wins} for name, wins in Counter(names).items()),
       key=lambda item: item['name'])

Where item['name'] could be item['wins'] or any other comparable expression.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.