Iterate dictionary (within dictionary) to sum up ranking - python

I got the following list that contains 4 tuples with the scores of each player over 7 games:
players_score = [ ('Joe', 100, 34, 38, 90, 67, 3, 10),
('Bob', 90, 38, 4, 100, 60, 4, 11),
('May', 80, 36, 40, 91, 70, 2, 12),
('Anna', 95, 32, 36, 92, 68, 8, 13) ]
Since I'm trying to learn more about dictionaries, I asked how I could convert the above to a dictionary. I got the following:
# convert player's scores to dictionary
games = {}
for (player_name, *scores) in players_score:
for game_no, score in enumerate(scores, 1):
games.setdefault(game_no, {}).setdefault(player_name, {})
games[game_no][player_name] = score
# the dictionary "games" will look like:
# {1: {'Anna': 95, 'Bob': 90, 'Joe': 100, 'May': 80},
# 2: {'Anna': 32, 'Bob': 38, 'Joe': 34, 'May': 36},
# 3: {'Anna': 36, 'Bob': 4, 'Joe': 38, 'May': 40},
# 4: {'Anna': 92, 'Bob': 100, 'Joe': 90, 'May': 91},
# 5: {'Anna': 68, 'Bob': 60, 'Joe': 67, 'May': 70},
# 6: {'Anna': 8, 'Bob': 4, 'Joe': 3, 'May': 2},
# 7: {'Anna': 13, 'Bob': 11, 'Joe': 10, 'May': 12}}
Now I'm trying out different ways to rank each player's score of each game and then add up those scores to return something like this:
{'Anna': 23, 'May': 18, 'Bob': 16, 'Joe': 15}
The score above is generated when the sum of the ranking of each game is returned. I let the best player, with the highest score of each game earn 4 points, the second one 3, etc.
Of course, it isn't very hard to just sort the outcomes of game 1 like this:
score_list = []
for p in players_score:
score_list.append(p[1])
score_list.sort(reverse=True)
print(score_list)
Returns: [100, 95, 90, 80]
Would it be any good to search in the list/dictionary for these values a then add the points (4/3/2/1) to the name and finally sum them up?

Try this :
from itertools import zip_longest as zilo
tt = list(sorted(i, reverse=True) for i in zilo(*[v for _, *v in players_score]))
# tt = [[100, 95, 90, 80], [38, 36, 34, 32], [40, 38, 36, 4], [100, 92, 91, 90], [70, 68, 67, 60], [8, 4, 3, 2], [13, 12, 11, 10]]
score_dict = {k : sum(4-j.index(i) for i,j in zip(v, tt)) for k, *v in players_score}
Output :
{'Joe': 15, 'Bob': 17, 'May': 18, 'Anna': 20}
Explanation :
First of all, we get the scores of each players for a game in a tuple with zip_longest from itertools with this :
zipped = list(zilo(*[v for _, *v in players_score]))
Now, zipped is :
[(100, 90, 80, 95), (34, 38, 36, 32), (38, 4, 40, 36), (90, 100, 91, 92), (67, 60, 70, 68), (3, 4, 2, 8), (10, 11, 12, 13)]
Check first item (tuple) is the score of four players for the first game and so on. Now we sort each tuple, in the descending order to this :
tt = list(sorted(i, reverse=True) for i in zipped)
So, tt now becomes :
[[100, 95, 90, 80], [38, 36, 34, 32], [40, 38, 36, 4], [100, 92, 91, 90], [70, 68, 67, 60], [8, 4, 3, 2], [13, 12, 11, 10]]
Now, we need to assign scores for each player. We can break down the one-liner dict-comprehension like this :
score_dict = {}
for k, *v in players_score:
tmp = []
for i,j in zip(v, tt):
tmp.append(4-j.index(i)) #Change your score assigning logic here [5,3,1,0] in stead of [4,3,2,1]
#print(tmp) #Check here for scores in each game for a player
tot_score = sum(tmp)
score_dict[k] = tot_score
#print(score_dict)
We iterate over the players_score, we take the first item of each tuple k in it as player name, the rest as another tuple v (the scores of one player in all games), we then zip this tuple v and previous tt, to assign values for rank in each particular game. Scores assigned are 4-the_sorted_one_game_scores_tuple.index(particular_players_score_in_that_game) and all these scores are being appended to a temporary list for each players. After we get the scores for all the games, we sum them up and assign this summation value tot_score as the value to the key k for the score_dict dictionary.
Update :
You can use 5-2*j.index(i) if 5-2*j.index(i) > 0 else 0 in place of 4-j.index(i). Please note that for that case you must have less than 5 games as more than 5 games would assign the last guy and the second last guy both zero. If you want to generalize this logic to assign 0,1,3,5,7,9,... for more number of persons; then you have use a function 2*x-1 if 2*x-1 > 0 else 0 to generate this number sequence. in that case, change inside the second for loop, where we are appending the scores in a temporary list, like this :
# To get 0,1,3,5,7,9,... type score
score_func = lambda x: 2*x-1 if 2*x-1>0 else 0
score_dict = {}
for k, *v in players_score:
tmp = []
for i,j in zip(v, tt):
tmp.append(score_func(j.index(i)))
tot_score = sum(tmp)
score_dict[k] = tot_score
print(score_dict)

Related

Create a dictionary from a list of key and multi list of values in Python

I have a list of key:
list_date = ["MON", "TUE", "WED", "THU","FRI"]
I have many lists of values that created by codes below:
list_value = list()
for i in list(range(5, 70, 14)):
list_value.append(list(range(i, i+10, 3)))
Rules created that:
first number is 5, a list contains 4 items has value equal x = x + 3, and so on [5, 8, 11,1 4]
the first number of the second list equal: x = 5 + 14, and value inside still as above x = x +3
[[5, 8, 11, 14], [19, 22, 25, 28], [33, 36, 39, 42], [47, 50, 53, 56], [61, 64, 67, 70]]
I expect to obtain a dict like this:
collections = {"MON":[5, 8, 11, 14], "TUE" :[19, 22, 25, 28], "WED":[33, 36, 39, 42], "THU":[47, 50, 53, 56], "FRI":[61, 64, 67, 70]}
Then, I used:
zip_iterator = zip(list_date, list_value)
collections = dict(zip_iterator)
To get my expected result.
I tried another way like using lambda function
for i in list(range(5, 70, 14)):
list_value.append(list(range(i,i+10,3)))
couple_start_end[lambda x: x in list_date] = list(range(i, i + 10, 3))
And the output is:
{<function <lambda> at 0x000001BF7F0711F0>: [5, 8, 11, 14], <function <lambda> at 0x000001BF7F071310>: [19, 22, 25, 28], <function <lambda> at 0x000001BF7F071280>: [33, 36, 39, 42], <function <lambda> at 0x000001BF7F0710D0>: [47, 50, 53, 56], <function <lambda> at 0x000001BF7F0890D0>: [61, 64, 67, 70]}
I want to ask there is any better solution to create lists of values with the rules above? and create the dictionary collections without using the zip method?
Thank you so much for your attention and participation.
Sure, you can use enumerate but I wouldn't say it is in anyway better or worse than the zip based solution:
collections = {}
for idx, key in enumerate(list_keys):
collections[key] = list_value[idx]
print(collections)
Output:
{'MON': [5, 8, 11, 14], 'TUE': [19, 22, 25, 28], 'WED': [33, 36, 39, 42], 'THU': [47, 50, 53, 56], 'FRI': [61, 64, 67, 70]}
Further, you don't need to create the value list separately, you can create the dictionary as you go along:
list_keys = ["MON", "TUE", "WED", "THU","FRI"]
collections = {}
for idx, start in enumerate(range(5, 70, 14)):
collections[list_keys[idx]] = [i for i in range(start, start+10, 3)]
print(collections)

How to change or create new ndarray from list

I have value X of type ndarray with shape: (40000, 2)
The second column of X contains list of 50 numbers
Example:
[17, [1, 2, 3, ...]],
[39, [44, 45, 45, ...]], ...
I want to convert it to ndarray of shape (40000, 51):
the first column will be the same
the every element of the list will be in it's own column.
for my example:
[17, 1, 2, 3, ....],
[39, 44, 45, 45, ...]
How can I do it ?
np.hstack((arr[:,0].reshape(-1,1), np.array(arr[:,1].tolist())))
Example:
>>> arr
array([[75, list([90, 39, 63])],
[20, list([82, 92, 22])],
[80, list([12, 6, 89])],
[79, list([11, 96, 74])],
[96, list([26, 37, 65])]], dtype=object)
>>> np.hstack((arr[:,0].reshape(-1,1),np.array(arr[:,1].tolist()))).astype(int)
array([[75, 90, 39, 63],
[20, 82, 92, 22],
[80, 12, 6, 89],
[79, 11, 96, 74],
[96, 26, 37, 65]])
You can do this for each line of your ndarray , here is an example :
# X = [39, [44, 45, 45, ...]]
newX = numpy.ndarray(shape=(1,51))
new[0] = X[0] # adding the first element
# now the rest of elements
i = 0
for e in X[1] :
newX[i] = e
i = i + 1
You can make this process as a function and apply it in this way :
newArray = numpy.ndarray(shape=(40000,51))
i = 0
for x in oldArray :
Process(newArray[i],x)
i=i+1
I defined the source array (with shorter lists in column 1) as:
X = np.array([[17, [1, 2, 3, 4]], [39, [44, 45, 45, 46]]])
To do your task, define the following function:
def myExplode(row):
tbl = [row[0]]
tbl.extend(row[1])
return tbl
Then apply it to each row:
np.apply_along_axis(myExplode, axis=1, arr=X)
The result is:
array([[17, 1, 2, 3, 4],
[39, 44, 45, 45, 46]])

Create a dictionary with 2 unequal lists

I like to form a dictionary with two lists, header as the key and score as the values:
#data
header= ["math","science","english"]
score = [80,95,75,81,22,90,20,55,99]
#my attempt
d={}
for i in header:
print(i)
for j in score:
print(j)
Desired output is
{("math":80, 81, 20),("science":95,22,55),("english":75,90,99)}
>>> {k: score[i::3] for i, k in enumerate(header)}
{'math': [80, 81, 20], 'science': [95, 22, 55], 'english': [75, 90, 99]}
We can make use of zip in this scenario.
groups = [score[i::3] for i in range(0, len(header))]
dict(zip(header, groups))
{'english': [75, 90, 99], 'math': [80, 81, 20], 'science': [95, 22, 55]}
Assuming you want your output to be a valid dictionary. Then try this.
dict(zip(header,zip(*[score[i:i+3] for i in range(0,len(score),3)])))
# {'math': (80, 81, 20), 'science': (95, 22, 55), 'english': (75, 90, 99)}
You have:
>>> header= ["math","science","english"]
>>> score = [80,95,75,81,22,90,20,55,99]
The first idea is to use zip, but zip stops when the shortest of the two lists is exhausted:
>>> dict(zip(header, score))
{'math': 80, 'science': 95, 'english': 75}
You have to use a second zip to group the scores:
>>> n = len(header)
>>> L = list(zip(*(score[i*n:(i+1)*n] for i in range(n))))
>>> L
[(80, 81, 20), (95, 22, 55), (75, 90, 99)]
And then to zip the header and the grouped scores:
>>> dict(zip(header, L))
{'math': (80, 81, 20), 'science': (95, 22, 55), 'english': (75, 90, 99)}
You should try to divide your problem into multiple smaller problems. Here are two links to related questions and their answers here on Stack Overflow:
How to Split Python list every Nth element
Choose one of the multiple possible solutions for a slice_per function there, so you'll get:
>>> header = ["math", "science", "english"]
>>> score = [80, 95, 75, 81, 22, 90, 20, 55, 99]
>>> def slice_per(source, step):
... return [source[i::step] for i in range(step)]
...
>>> slice_per(score, len(header))
[[80, 81, 20], [95, 22, 55], [75, 90, 99]]
Convert two lists into a dictionary
Combine the slices with your headers:
>>> dict(zip(header, slice_per(score, len(header))))
{'math': [80, 81, 20], 'science': [95, 22, 55], 'english': [75, 90, 99]}
you could use a dictionary comprehension and the buil-in function and enumerate:
step = len(header)
{k : score[i::step] for i, k in enumerate(header)}
output:
{'math': [80, 81, 20], 'science': [95, 22, 55], 'english': [75, 90, 99]}
or you can use numpy:
import numpy as np
dict(zip(header, np.array(score).reshape(3,3).T.tolist()))
output:
{'math': [80, 81, 20], 'science': [95, 22, 55], 'english': [75, 90, 99]}
if you want to use for loops:
result = {}
for i in range(len(score) // len(header)):
for j, h in enumerate(header):
result.setdefault(h, []).append(score[i * len(header) + j])
result
output:
{'math': [80, 81, 20], 'science': [95, 22, 55], 'english': [75, 90, 99]}

Comparing lists with their indices and content in Python

I have a list of numbers as
N = [13, 14, 15, 25, 27, 31, 35, 36, 43]
After some calculations, for each element in N, I get the following list as the answers.
ndlist = [4, 30, 0, 42, 48, 4, 3, 42, 3]
That is, for the first index in N (which is 13), my answer is 4 in ndlist.
For some indices in N, I get the same answer in ndlist. For example, when N= 13 and 31, the answer is 4 in ndlist.
I need to find the numbers in N (13 and 31 in my example) such that they have the same answer in ndlist.
Can someone help me to that?
You can use a defaultdict and put those into a list keyed by the answer like:
Code:
N = [13, 14, 15, 25, 27, 31, 35, 36, 43]
ndlist = [4, 30, 0, 42, 48, 4, 3, 42, 3]
from collections import defaultdict
answers = defaultdict(list)
for n, answer in zip(N, ndlist):
answers[answer].append(n)
print(answers)
print([v for v in answers.values() if len(v) > 1])
Results:
defaultdict(<class 'list'>, {4: [13, 31], 30: [14],
0: [15], 42: [25, 36], 48: [27], 3: [35, 43]})
[[13, 31], [25, 36], [35, 43]]
Here is a way using only a nested list comprehension:
[N[idx] for idx, nd in enumerate(ndlist) if nd in [i for i in ndlist if ndlist.count(i)>1]]
#[13, 25, 31, 35, 36, 43]
To explain: the inner list comprehension ([i for i in ndlist if ndlist.count(i)>1]) gets all duplicate values in ndlist, and the rest of the list comprehension extracts the corresponding values in N where those values are found in ndlist

Python: Renumerate elements in multiple lists

Suppose I have a dictionary with lists as follows:
{0: [31, 32, 58, 59], 1: [31, 32, 12, 13, 37, 38], 2: [12, 13]}
I am trying to obtain the following one from it:
{0: [1, 2, 3, 4], 1: [1, 2, 5, 6, 7, 8], 2: [5, 6]}
So I renumerate all the entries in order of occurence but skipping those that were already renumerated.
What I have now is a bunch of for loops going back and forth which works, but doesn't look good at all, could anyone please tell me the way it should be done in Python 2.7?
Thank you
import operator
data = {0: [31, 32, 58, 59], 1: [31, 32, 12, 13, 37, 38], 2: [12, 13]}
# the accumulator is the new dict with renumbered values combined with a list of renumbered numbers so far
# item is a (key, value) element out of the original dict
def reductor(acc, item):
(out, renumbered) = acc
(key, values) = item
def remapper(v):
try:
x = renumbered.index(v)
except ValueError:
x = len(renumbered)
renumbered.append(v)
return x
# transform current values to renumbered values
out[key] = map(remapper, values)
# return output and updated list of renumbered values
return (out, renumbered)
# now reduce the original data
print reduce(reductor, sorted(data.iteritems(), key=operator.itemgetter(0)), ({}, []))
If you're not worried about memory or speed you can use an intermediate dictionary to map the new values:
a = {0: [31, 32, 58, 59], 1: [31, 32, 12, 13, 37, 38], 2: [12, 13]}
b = {}
c = {}
for key in sorted(a.keys()):
c[key] = [b.setdefault(val, len(b)+1) for val in a[key]]
Just use a function like this:
def renumerate(data):
ids = {}
def getid(val):
if val not in ids:
ids[val] = len(ids) + 1
return ids[val]
return {k : map(getid, data[k]) for k in sorted(data.keys())}
Example
>>> data = {0: [31, 32, 58, 59], 1: [31, 32, 12, 13, 37, 38], 2: [12, 13]}
>>> print renumerate(data)
{0: [1, 2, 3, 4], 1: [1, 2, 5, 6, 7, 8], 2: [5, 6]}
data = {0: [31, 32, 58, 59], 1: [31, 32, 12, 13, 37, 38], 2: [12, 13]}
from collections import defaultdict
numbered = defaultdict(lambda: len(numbered)+1)
result = {key: [numbered[v] for v in val] for key, val in sorted(data.iteritems(), key=lambda item: item[0])}
print result

Categories

Resources