how to get the same output by destructing in python - python

I want to get the same output as in the first code with the second code but with a slight variation. I want to de-structure the list "players" and then get the same output.
Here is the first code:
import random
lottery_numbers = set(random.sample(range(22), 6))
players = [
{"name": "Rolf", "numbers": {1, 3, 5, 7, 9, 11}},
{"name": "Charlie", "numbers": {2, 7, 9, 22, 10, 5}},
{"name": "Anna", "numbers": {13, 14, 15, 16, 17, 18}},
{"name": "Jen", "numbers": {19, 20, 12, 7, 3, 5}}
]
top_player = players[0]
for player in players:
matched_numbers = len(player["numbers"].intersection(lottery_numbers))
if matched_numbers > len(
top_player["numbers"].intersection(lottery_numbers)):
top_player = player
print(top_player)
I want to de-structure the list "players" to "name" and "player" and then compare the variable "player" with the numbers that matched with lottery_numbers
Here is the second piece of code:
for name, player in players[1].items():
matched_numbers = len(player.intersection(lottery_numbers))
if matched_numbers > len(
top_player["numbers"].intersection(lottery_numbers)):
top_player = player
print(top_player)
Pycharm seems to hit me with a error like this:
in matched_numbers = len(player.intersection(lottery_numbers))
AttributeError: 'str' object has no attribute 'intersection'
PS: I am pretty new with python and don't know much about what the error evens means..

The first thing you are doing is saying players[1].items(), this would return:
{"name": "Charlie", "numbers": {2, 7, 9, 22, 10, 5}}
because that is the first index of the players list. If you want to get the list of names, you can just do something like this:
for player in players:
this will go through all the players and the lottery numbers would just be player["numbers"]
So your final code would look like:
for player in players:
matched_numbers = len(player["numbers"].intersection(lottery_numbers))
if matched_numbers > len(
top_player["numbers"].intersection(lottery_numbers)):
top_player = player
print(top_player)
If you want to split this into 2 different variables, you can use this:
names = []
numbers = []
for player in players:
names.append(player["name"])
numbers.append(player["numbers"])
finalList = zip(names, numbers)
then do:
for name, player in finalList

A few changes to make, to do with how the players list is organised, ie. it's not a dict but rather a list.
import random
lottery_numbers = set(random.sample(range(22), 6))
players = [
{"name": "Rolf", "numbers": {1, 3, 5, 7, 9, 11}},
{"name": "Charlie", "numbers": {2, 7, 9, 22, 10, 5}},
{"name": "Anna", "numbers": {13, 14, 15, 16, 17, 18}},
{"name": "Jen", "numbers": {19, 20, 12, 7, 3, 5}}
]
top_player = players[0]
for player in players:
matched_numbers = len(player["numbers"].intersection(lottery_numbers))
if matched_numbers > len(
top_player["numbers"].intersection(lottery_numbers)):
top_player = player
print(top_player)
for player in players:
name = player["name"]
numbers = player["numbers"]
matched_numbers = len(numbers.intersection(lottery_numbers))
if matched_numbers > len(
top_player["numbers"].intersection(lottery_numbers)):
top_player = player
print(top_player)

Related

How to abstract over two similar functions

I have the following data definition about a football game:
Game = namedtuple('Game', ['Date', 'Home', 'Away', 'HomeShots', 'AwayShots',
'HomeBT', 'AwayBT', 'HomeCrosses', 'AwayCrosses',
'HomeCorners', 'AwayCorners', 'HomeGoals',
'AwayGoals', 'HomeXG', 'AwayXG'])
Here are some exmaples:
[Game(Date=datetime.date(2018, 10, 21), Home='Everton', Away='Crystal Palace', HomeShots='21', AwayShots='6', HomeBT='22', AwayBT='13', HomeCrosses='21', AwayCrosses='14', HomeCorners='10', AwayCorners='5', HomeGoals='2', AwayGoals='0', HomeXG='1.93', AwayXG='1.5'),
Game(Date=datetime.date(2019, 2, 27), Home='Man City', Away='West Ham', HomeShots='20', AwayShots='2', HomeBT='51', AwayBT='6', HomeCrosses='34', AwayCrosses='5', HomeCorners='12', AwayCorners='2', HomeGoals='1', AwayGoals='0', HomeXG='3.68', AwayXG='0.4'),
Game(Date=datetime.date(2019, 2, 9), Home='Fulham', Away='Man Utd', HomeShots='12', AwayShots='15', HomeBT='19', AwayBT='38', HomeCrosses='20', AwayCrosses='12', HomeCorners='5', AwayCorners='4', HomeGoals='0', AwayGoals='3', HomeXG='2.19', AwayXG='2.13'),
Game(Date=datetime.date(2019, 3, 9), Home='Southampton', Away='Tottenham', HomeShots='12', AwayShots='15', HomeBT='13', AwayBT='17', HomeCrosses='15', AwayCrosses='15', HomeCorners='1', AwayCorners='10', HomeGoals='2', AwayGoals='1', HomeXG='2.08', AwayXG='1.27'),
Game(Date=datetime.date(2018, 9, 22), Home='Man Utd', Away='Wolverhampton', HomeShots='16', AwayShots='11', HomeBT='17', AwayBT='17', HomeCrosses='26', AwayCrosses='13', HomeCorners='5', AwayCorners='4', HomeGoals='1', AwayGoals='1', HomeXG='0.62', AwayXG='1.12')]
And two almost identical functions calculating home and away statistics for a given team.
def calculate_home_stats(team, games):
"""
Calculates home stats for the given team.
"""
home_stats = defaultdict(float)
home_stats['HomeShotsFor'] = sum(int(game.HomeShots) for game in games if game.Home == team)
home_stats['HomeShotsAgainst'] = sum(int(game.AwayShots) for game in games if game.Home == team)
home_stats['HomeBoxTouchesFor'] = sum(int(game.HomeBT) for game in games if game.Home == team)
home_stats['HomeBoxTouchesAgainst'] = sum(int(game.AwayBT) for game in games if game.Home == team)
home_stats['HomeCrossesFor'] = sum(int(game.HomeCrosses) for game in games if game.Home == team)
home_stats['HomeCrossesAgainst'] = sum(int(game.AwayCrosses) for game in games if game.Home == team)
home_stats['HomeCornersFor'] = sum(int(game.HomeCorners) for game in games if game.Home == team)
home_stats['HomeCornersAgainst'] = sum(int(game.AwayCorners) for game in games if game.Home == team)
home_stats['HomeGoalsFor'] = sum(int(game.HomeGoals) for game in games if game.Home == team)
home_stats['HomeGoalsAgainst'] = sum(int(game.AwayGoals) for game in games if game.Home == team)
home_stats['HomeXGoalsFor'] = sum(float(game.HomeXG) for game in games if game.Home == team)
home_stats['HomeXGoalsAgainst'] = sum(float(game.AwayXG) for game in games if game.Home == team)
home_stats['HomeGames'] = sum(1 for game in games if game.Home == team)
return home_stats
def calculate_away_stats(team, games):
"""
Calculates away stats for the given team.
"""
away_stats = defaultdict(float)
away_stats['AwayShotsFor'] = sum(int(game.AwayShots) for game in games if game.Away == team)
away_stats['AwayShotsAgainst'] = sum(int(game.HomeShots) for game in games if game.Away == team)
away_stats['AwayBoxTouchesFor'] = sum(int(game.AwayBT) for game in games if game.Away == team)
away_stats['AwayBoxTouchesAgainst'] = sum(int(game.HomeBT) for game in games if game.Away == team)
away_stats['AwayCrossesFor'] = sum(int(game.AwayCrosses) for game in games if game.Away == team)
away_stats['AwayCrossesAgainst'] = sum(int(game.HomeCrosses) for game in games if game.Away == team)
away_stats['AwayCornersFor'] = sum(int(game.AwayCorners) for game in games if game.Away == team)
away_stats['AwayCornersAgainst'] = sum(int(game.HomeCorners) for game in games if game.Away == team)
away_stats['AwayGoalsFor'] = sum(int(game.AwayGoals) for game in games if game.Away == team)
away_stats['AwayGoalsAgainst'] = sum(int(game.HomeGoals) for game in games if game.Away == team)
away_stats['AwayXGoalsFor'] = sum(float(game.AwayXG) for game in games if game.Away == team)
away_stats['AwayXGoalsAgainst'] = sum(float(game.HomeXG) for game in games if game.Away == team)
away_stats['AwayGames'] = sum(1 for game in games if game.Away == team)
return away_stats
I'm wondering if there is a way to abstract over these two functions and merge them into one without creating a wall of if/else statements to determine whether the team plays at home or away from home and which fields should be counted.
Having cleaner data structure allow for writing simpler code.
In that case, your data already contains duplication
(eg, you have both HomeShots and AwayShots).
There are many possible answers to how you could structure data here.
I'll just go over a solution that doesn't change too much from
your original structure.
Statistics = namedtuple('Statistics', ['shots', 'BT', 'crosses', 'corners', 'goals', 'XG'])
Game = namedtuple('Game', ['home', 'away', 'date', 'home_stats', 'away_stats'])
You could use this like this (I haven't included all stats here, just a few to give an example):
def calculate_stats(games, team_name, home_stats_only=False, away_stats_only=False):
home_stats = [g.home_stats._asdict() for g in games if g.home == team_name]
away_stats = [g.away_stats._asdict() for g in games if g.away == team_name]
if away_stats_only:
input_stats = away_stats
elif home_stats_only:
input_stats = home_stats
else:
input_stats = home_stats + away_stats
def sum_on_field(field_name):
return sum(stats[field_name] for stats in input_stats)
return {f:sum_on_field(f) for f in Statistics._fields}
Which can then be used to get both away/home stats:
example_game_1 = Game(
home='Burnley',
away='Arsenal',
date=datetime.now(),
home_stats=Statistics(shots=12, BT=26, crosses=21, corners=4, goals=1, XG=1.73),
away_stats=Statistics(shots=17, BT=26, crosses=22, corners=5, goals=3, XG=2.87),
)
example_game_2 = Game(
home='Arsenal',
away='Pessac',
date=datetime.now(),
home_stats=Statistics(shots=1, BT=1, crosses=1, corners=1, goals=1, XG=1),
away_stats=Statistics(shots=2, BT=2, crosses=2, corners=2, goals=2, XG=2),
)
print(calculate_stats([example_game_1, example_game_2], 'Arsenal'))
print(calculate_stats([example_game_1, example_game_2], 'Arsenal', home_stats_only=True))
print(calculate_stats([example_game_1, example_game_2], 'Arsenal', away_stats_only=True))
Which prints:
{'shots': 18, 'BT': 27, 'crosses': 23, 'corners': 6, 'goals': 4, 'XG': 3.87}
{'shots': 1, 'BT': 1, 'crosses': 1, 'corners': 1, 'goals': 1, 'XG': 1}
{'shots': 17, 'BT': 26, 'crosses': 22, 'corners': 5, 'goals': 3, 'XG': 2.87}
When dealing with this kind of data, it's usually a good idea to use specialised tools like, for example, pandas. It could also be very convenient to use interactive tools, like JupyterLab.
I recommend not using a named tuple but a simple tuple with a dictionary, for example:
game=(datetime.date(2019, 5, 12), 'Burnley', 'Arsenal', '12', '17', '26', '26', '21', '22', '4', '5', '1', '3', '1.73', '2.87')
And a mapping dictionary:
numtostr={0: 'Date', 1: 'Home', 2: 'Away', 3: 'HomeShots', 4: 'AwayShots', 5: 'HomeBT', 6: 'AwayBT', 7: 'HomeCrosses', 8: 'AwayCrosses', 9: 'HomeCorners', 10: 'AwayCorners', 11: 'HomeGoals', 12: 'AwayGoals', 13: 'HomeXG'}
strtonum={'Date': 0, 'Home': 1, 'Away': 2, 'HomeShots': 3, 'AwayShots': 4, 'HomeBT': 5, 'AwayBT': 6, 'HomeCrosses': 7, 'AwayCrosses': 8, 'HomeCorners': 9, 'AwayCorners': 10, 'HomeGoals': 11, 'AwayGoals': 12, 'HomeXG': 13}
Make the mapping dictionaries for homestats and awaystats ({0: 'HomeShotsFor', 1: 'HomeShotsAgainst' etc} for home_stats). To explain how mapping dictionaries work, for example, if you want to get the HomeCrosses of a game, you can have
game[7]
or
game[strtonum['HomeCrosses']]
Then the functions:
def calculate_home_stats(team, games):
home_stats=[0]*13
for game in games:
if game[1]=team:
for index in range(12):
home_stats[index]+=game[index+3] #because you just put the sum of everything except date, home, and away which are the first 3 indices. see how this cleans everything up?
home_stats[12]+=1
def calculate_away_stats(team, games):
away_stats=[0]*13
for game in games:
if game[2]=team:
for index in range(12):
away_stats[index]+=game[index+3]
away_stats[12]+=1
If you really want to merge both functions into one you can do this:
def calculate_stats(team, games, homeaway):
stats=[0]*13
for game in games:
if game[{'Home': 1, 'Away': 2}[homeaway]]=team:
for index in range(12):
stats[index]+=game[index+3]
stats[12]+=1
As with my function the only thing you have to change is the index to check for home or away, instead of the redundant if else statements which require a lot of change.

Can I use the .format feature when using screen.blit in Pygame?

Heys, I recently couldnt figure out how to blit lists onto my pygame screen using certain x and y values, allowing the text to blit say x += 20, moving every other string in my list over 20 units everytime it blits. I recently did some .format stuff with printing just in the console, is there a feature like this for screen.blit so I can use the same formatting in a pygame window? Included my code underneath. Thanks in advance :D
import pygame
NAMES = ['Deanerys T.', 'Jon S.', 'Gregor C.', 'Khal D.', 'Cersei L.', 'Jamie L.',
'Tyrion L.', 'Sansa S.', 'Ayra S.', 'Ned S.']
DATE_OF_BIRTH = ['6/10/1996', '6/12/1984', '3/12/1980', '8/4/1986', '7/2/1970',
'7/2/1975', '12/24/1980', '11/30/1993', '5/18/1999', '6/27/1984']
AGE = [22, 34, 38, 32, 48, 43, 38, 25, 19, 34]
MARITAL_STATUS = ['Not Married', 'Not Married', 'Not Married', 'Not Married',
'Married', 'Not Married', 'Married', 'Married', 'Not Married', 'Married']
NUM_OF_CHILDREN = [3, 0, 0, 4, 2, 0, 1, 1, 0, 5]
for i in range(10):
print("{:>12} was born {:>10}, is age {:>2}. They have {:>1} children, and are {:>4}".format(NAMES[i], DATE_OF_BIRTH[i], AGE[i], NUM_OF_CHILDREN[i], MARITAL_STATUS[i]))
print("\n")
for i in range(10):
print("NAME: {:>12} DATE OF BIRTH: {}".format(NAMES[i], DATE_OF_BIRTH[i], ))
print("\n")
for i in range(10):
print("NAME: {:>12} AGE: {}".format(NAMES[i], AGE[i], ))
print("\n")
for i in range(10):
print("NAME: {:>12} MARRIAGE STATUS: {}".format(NAMES[i], MARITAL_STATUS[i], ))
print("\n")
for i in range(10):
print("NAME: {:>12} NUMBER OF CHILDREN: {}".format(NAMES[i], NUM_OF_CHILDREN[i], ))
print("\n")
The format is an operator on the string, so it's no problem to use it with pygame.
For Example:
hp = 56
player_hp_text = "Hit Points: {:>3}".format( hp )
player_hp_bitmap = myfont.render( player_hp_text, 16, (255, 255,0) )
screen.blit( player_hp_bitmap, ( 10, 10 ) )

reassigning elements of a dict which uses both a string and a variable

I'm trying to reassign elements of my dict but coming up with a problem:
name = "none"
scripts = "blank"
stats = {"hp" : 0, "sp" :0}
atk = {"normal": 0, "crit" : 0, "crit chance": 0}
sword = {"attack": atk, "scripts" : scripts}
actions = {"melee" : sword}
blank = {"name" : "blank", "stats" : stats, "action" : actions}
attacker = blank
scripts = {"normal 1" : "%s attacks with %s
(attacker["name"],attacker["action"]["melee"]["attack"]["normal"])}
stats = {"hp" : 20, "sp" :5}
atk = {"normal": 12, "crit" : 15, "crit chance": 30}
sword = {"attack": atk, "scripts" : scripts}
actions = {"melee" : sword}
character = {"name" : "Jabe", "stats" : stats, "action" : actions}
attacker = character
print (attacker["action"]["melee"]["scripts"]["normal 1"])
print (attacker["action"]["melee"]["attack"])
in my turn based game I have a loop where first the player attacks then the enemy attacks, so I don't have to write the same code again, initially the attacker = the player's character and the enemy = attacked, but after its run through the players combat the player then becomes the attacked then the enemy becomes the attacker so the contents of the dict needs to change, which works mostly, until you get to the "script" bit.
for my game to work I want the above code to output the script from "character" using its values for the dict elements it calls from, like this:
Jabe attacks with 12
but instead it outputs:
blank attacks with 0
There are various way you can attach some code to data. One was is to make a lambda and call it to build the string you want to print:
Code:
scripts = {"normal 1": lambda x: "{} attacks with {}".format(
x["name"], x["action"]["melee"]["attack"]["normal"])}
and then:
print(attacker["action"]["melee"]["scripts"]["normal 1"](attacker))
An aside: The below works, but a better way to do this sort of thing is to use objects.
All of it:
name = "none"
scripts = "blank"
stats = {"hp": 0, "sp": 0}
atk = {"normal": 0, "crit": 0, "crit chance": 0}
sword = {"attack": atk, "scripts": scripts}
actions = {"melee": sword}
blank = {"name": "blank", "stats": stats, "action": actions}
attacker = blank
scripts = {"normal 1": lambda x: "{} attacks with {}".format(
x["name"], x["action"]["melee"]["attack"]["normal"])}
stats = {"hp": 20, "sp": 5}
atk = {"normal": 12, "crit": 15, "crit chance": 30}
sword = {"attack": atk, "scripts": scripts}
actions = {"melee": sword}
character = {"name": "Jabe", "stats": stats, "action": actions}
attacker = character
print(attacker["action"]["melee"]["scripts"]["normal 1"](attacker))
print(attacker["action"]["melee"]["attack"])
Results:
Jabe attacks with 12
{'crit': 15, 'crit chance': 30, 'normal': 12}

Python counter not recognizing dictionary

I have a list and a dictionary and I want to ultimately find a sum of the values in the two. For example, I want the code below to return :
{gold coin : 45, rope : 1, dagger : 6, ruby : 1}
First I right a function to turn the dragonLoot list into a dictionary and then I run a Counter to add the two dictionaries together. However, when I run the code I get the following:
{'ruby': 1, 'gold coin': 3, 'dagger': 1}
Counter({'gold coin': 42, 'dagger': 5, 'rope': 1})
For some reason it looks like the Counter is not recognizing the dictionary that I create from dragonLoot. Does anyone have any suggestions on what I am doing wrong? Thanks!
inv = {'gold coin' : 42, 'rope' : 1, 'dagger' : 5}
dragonLoot = ['gold coin','dagger','gold coin','gold coin','ruby']
def inventory(item):
count = {}
for x in range(len(item)):
count.setdefault(item[x],0)
count[item[x]] = count[item[x]] + 1
print(count)
inv2 = inventory(dragonLoot)
from collections import Counter
dicts = [inv,inv2]
c = Counter()
for d in dicts:
c.update(d)
print(c)
You don't need the inventory function: Counter will count the iterable for you. You can also use + with Counter. Combine these, and you can do quite simply
inv = Counter({'gold coin' : 42, 'rope' : 1, 'dagger' : 5})
dragonLoot = ['gold coin','dagger','gold coin','gold coin','ruby']
inv += Counter(dragonLoot)
After this is run, inv will be Counter({'gold coin': 45, 'dagger': 6, 'rope': 1, 'ruby': 1}), as desired.
You are not returning the count in your inventory method:
def inventory(item):
count = {}
for x in range(len(item)):
count.setdefault(item[x],0)
count[item[x]] = count[item[x]] + 1
print(count)
You are simply printing your inventory calculation. Change that print to a return, or add a return line after the print:
def inventory(item):
count = {}
for x in range(len(item)):
count.setdefault(item[x],0)
count[item[x]] = count[item[x]] + 1
print(count)
return count
Adding that to your code and running it, gives this output:
Counter({'gold coin': 45, 'dagger': 6, 'rope': 1, 'ruby': 1})
Alternatively, the implementation provided by #nneonneo is optimal.
Here is an other way to do it without the Counter:
dragonLoot = ['gold coin','dagger','gold coin','gold coin','ruby']
inv = {'gold coin' : 42, 'rope' : 1, 'dagger' : 5}
for i in dragonLoot:
inv[i] = inv.get(i, 0) +1
print (inv)
Output:
{'gold coin': 45, 'rope': 1, 'dagger': 6, 'ruby': 1}

loop tuple list - TypeError: unhashable type: 'list'

this works:
shopping_list = ["banana", "orange", "apple"]
stock = {
"banana": 6,
"apple": 0,
"orange": 32,
"pear": 15
}
prices = {
"banana": 4,
"apple": 2,
"orange": 1.5,
"pear": 3
}
def compute_bill(food):
total = 0
# food = tuple(food)
for food in food:
total += prices[food]
return total
print compute_bill(shopping_list)
But if I change food to anything else in the loop, for example X - for x in food - then python gives me below error (it only works with for food in food.)
Traceback (most recent call last):
File "compute-shopping.py", line 25, in <module>
print compute_bill(shopping_list)
File "compute-shopping.py", line 21, in compute_bill
total += prices[food]
TypeError: unhashable type: 'list'
This is not related to using tuple or list as key for dictionary ... or is it ?!
Assuming food is a list, you just need to change the for loop to:
for food_type in food:
total += prices[food_type]

Categories

Resources