views:

155

answers:

5

(Here's a sort of hypothetical situation for everybody. I'm more looking for directions rather than straight processes, but if you can provide them, awesome!)

So let's say we have a list of athletes, I'm going to use figure skaters since I'm knee deep in the Winter Olympics right now. (I'm throwing it in a dictionary since that's my first instinct, doesn't have to be this way.)

after_short_program = {
    '1': 'Evgeni Plushenko',
    '2': 'Evan Lysacek',
    '3': 'Daisuke Takahashi',
    '4': 'Nobunari Oda',
    '5': 'Stephane Lambiel'
}

So after the free skate (which hasn't happened as I ask this), let's say these are the standings.

after_free_skate = {
    '1': 'Evan Lysacek',
    '2': 'Daisuke Takahashi',
    '3': 'Evgeni Plushenko',
    '4': 'Stephane Lambiel',
    '5': 'Nobunari Oda',
}

So, the questions:

How would one go about comparing the two sets of data? Evan Lysacek moved up one space to win the gold, Daisuke moved up one place to win the silver and Evgeni moved down two spaces to win the bronze. Off the top of my head, if I were to render this information, I'd want to say, "Evan (+1 or moved up one), Evgeni (-2 or moved down two), etc."

Is there a way in Python to extract that sort of data from comparisons?

+7  A: 

I would use the athlet name as key in your dicts. Then you can look for their position more easily. Something like:

diff = {}
for (a, pos2) in after_free_skate.items():
     pos1 = after_short_program[a]
     diff[a] = pos2 - pos1

I hope it helps

luc
+3  A: 

This solution prints the results in the same order as the final placings.
If the place has not changed (+0) is printed.
If you wish to filter those out instead, simply put an if diff: before the print

>>> after_short_program = [
...     'Evgeni Plushenko',
...     'Evan Lysacek',
...     'Daisuke Takahashi',
...     'Nobunari Oda',
...     'Stephane Lambiel',
... ]
>>> 
>>> after_free_skate = [
...     'Evan Lysacek',
...     'Daisuke Takahashi',
...     'Evgeni Plushenko',
...     'Stephane Lambiel',
...     'Nobunari Oda',
... ]
>>> 
>>> for i,item in enumerate(after_free_skate):
...     diff = after_short_program.index(item)-i
...     print "%s (%+d)"%(item,diff)
...     
... 
Evan Lysacek (+1)
Daisuke Takahashi (+1)
Evgeni Plushenko (-2)
Stephane Lambiel (+1)
Nobunari Oda (-1)

As pwdyson points out, if your stopwatches aren't good enough, you might get a tie. So this modification uses dicts instead of lists. The order of the placings is still preserved

>>> from operator import itemgetter
>>> 
>>> after_short_program = {
...     'Evgeni Plushenko':1,
...     'Evan Lysacek':2,
...     'Daisuke Takahashi':3,
...     'Stephane Lambiel':4,
...     'Nobunari Oda':5,
... }
>>> 
>>> after_free_skate = {
...     'Evan Lysacek':1,
...     'Daisuke Takahashi':2,
...     'Evgeni Plushenko':3,
...     'Stephane Lambiel':4,   # These are tied
...     'Nobunari Oda':4,       # at 4th place
... }
>>> 
>>> for k,v in sorted(after_free_skate.items(),key=itemgetter(1)):
...     diff = after_short_program[k]-v
...     print "%s (%+d)"%(k,diff)
...     
... 
Evan Lysacek (+1)
Daisuke Takahashi (+1)
Evgeni Plushenko (-2)
Nobunari Oda (+1)
Stephane Lambiel (+0)
>>> 

If there is a possibility of keys in the second dict that are not in the first you can do something like this

for k,v in sorted(after_free_skate.items(),key=itemgetter(1)):
    try:
        diff = after_short_program[k]-v
        print "%s (%+d)"%(k,diff)
    except KeyError:
        print "%s (new)"%k
gnibbler
can't deal with ties :-)
pwdyson
It prints (+0) for ties. What's wrong with that?
gnibbler
Sorry, I was unclear. I meant it can't deal with two people being tied in their standing after an event. This is not mentioned in the question, though.
pwdyson
@pwdyson, ok I added a version using dicts for the placings
gnibbler
In the end I preferred this way of going about things. Thank you so much. :) If you do see this, I'm wondering if you could expand on your answer, showing how you'd handle new items in the 2nd list.
Bryan Veloso
@Bryan Veloso, sure I added one way to do it to the bottom of my answer
gnibbler
+1  A: 

One way would be to flip the keys and values, then take the difference, ie:

for k, v in after_free_skate.items():
   print 'k', v - after_short_program[k]  
Dana the Sane
That should be after_free_skate.items():
Wogan
A: 

I would put the names as keys and the positions as values, with the positions as ints:

after_short_program = {
    '1': 'Evgeni Plushenko',
    '2': 'Evan Lysacek',
    '3': 'Daisuke Takahashi',
    '4': 'Nobunari Oda',
    '5': 'Stephane Lambiel'
}

after_free_skate = {
    '1': 'Evan Lysacek',
    '2': 'Daisuke Takahashi',
    '3': 'Evgeni Plushenko',
    '4': 'Stephane Lambiel',
    '5': 'Nobunari Oda',
}

after_short_program_swap = {}
for k,v in after_short_program.iteritems():
   after_short_program_swap[v]=int(k)

after_free_skate_swap = {}
for k,v in after_free_skate.iteritems():
    after_free_skate_swap[v]=int(k)

then the code is much simpler:

moved = {}
for key in after_short_program_swap:
    moved[key] = after_short_program_swap[key] - after_free_skate_swap[key]

print moved

prints:

{'Evan Lysacek': 1, 'Nobunari Oda': -1, 'Evgeni Plushenko': -2, 'Stephane Lambiel': 1, 'Daisuke Takahashi': 1}

to print out in the medal order, following @gnibbler:

from operator import itemgetter
print '\n'.join('%s (%+d)' % (k,moved[k]) for k,v in sorted(after_free_skate_swap.items(),key=itemgetter(1)))

Evan Lysacek (+1)

Daisuke Takahashi (+1)

Evgeni Plushenko (-2)

Stephane Lambiel (+1)

Nobunari Oda (-1)

pwdyson
This doesn't print the places in any particular order. I thought it should be in the order of gold,silver,bronze,4th,5th etc.
gnibbler
ah yes. Added code to sort in medal order.
pwdyson
+1  A: 

I'd personally use lists as they are naturally suited to store 'positional' information... the following is a rather functional approach employing lists:

###_* input data
after_short_program = [
    'Evgeni Plushenko',
    'Evan Lysacek',
    'Daisuke Takahashi',
    'Nobunari Oda',
    'Stephane Lambiel'
    ]
after_free_skate = [
    'Evan Lysacek',
    'Daisuke Takahashi',
    'Evgeni Plushenko',
    'Stephane Lambiel',
    'Nobunari Oda'
    ]

## combine
all_athletes = set(after_short_program + after_free_skate)

###_* import libraries, define functions
from operator import add, sub
from functools import partial

def tryit(f,*args):
    try: return f(*args)
    except: return None
def compose(f,g): ## available in functional library
    return lambda x: f(g(x))

###_* apply functions
## original and new positions for each athlete
## I usually wrap list.index() in a try-except clause
pos = [(x,{'orig':tryit(compose(partial(add,1),after_short_program.index),x),
           'new':tryit(compose(partial(add,1),after_free_skate.index),x)})
       for i,x in enumerate(all_athletes)]

## calculate the changes (now edited to sort by final position)
changes = [(x[0],tryit(sub,x[1]['orig'],x[1]['new']))
           for x in sorted(pos,key=lambda x: x[1]['new'])]

The output is as follows:

>>> changes
[('Evan Lysacek', 1), ('Daisuke Takahashi', 1), ('Evgeni Plushenko', -2), ('Stephane Lambiel', 1), ('Nobunari Oda', -1)]
Stephen
This solution guards against having a different elements in the two sets....
Stephen
While I like how this guards against having different elements, it doesn't sort them in "medal order" like the rest of the solutions.
Bryan Veloso
Thank you. But sorting by place order is not difficult - please see application of sorted() function above!
Stephen