Without modifying the input and output formats I do not see how to avoid nested loops as you have information grouped by match and you want to extract it by player. What you could actually do is avoid the last loop by creating the Counter inside the nested loop:
from collections import defaultdict, Counter
if __name__=='__main__':
x = {'match1': [1, 2, 3], 'match2': [2, 3, 4], 'match3': [3, 4, 5]}
d = defaultdict(Counter)
for key, value in x.items():
for i in value:
d[i].update(value)
print(d)
If the input can be modified you could go for something like:
from collections import Counter
if __name__=='__main__':
x = {1: [{1, 2, 3}], 2: [{1, 2, 3}, {2, 3, 4}], 3: [{1, 2, 3}, {2, 3, 4}, {3, 4, 5}], 4: [{2, 3, 4}, {3, 4, 5}], 5: [{3, 4, 5}]}
d = {player: Counter([p for match in matches for p in match]) for player, matches in x.items()}
print(d)
Where you swap the nested loops for a dict and a list comprehension which should be more efficient. Probably players and matches are not ints and lists of ints so this could be done a bit more redable. For example:
from collections import defaultdict, Counter
def printMatrix(matrix):
print(' '.join([' |'] + list(map(str, matrix.keys()))))
print('---+-' + '-'*len(matrix)*2)
for row, values in matrix.items():
fmt = ' {row} |'
for col in matrix.keys():
fmt += ' {values[' + str(col) + ']}'
print(fmt.format(row=row, values=values))
class Entity:
def __init__(self):
self._id = None
@classmethod
def register(cls, value):
if value in cls.ids:
raise ValueError("The provided ID is already in use")
cls.ids.add(value)
@classmethod
def unregister(cls, value):
if value is not None:
cls.ids.remove(value)
@property
def id(self):
return self._id
@id.setter
def id(self, value):
if value == self.id:
return
self.register(value)
self.unregister(self.id)
self._id = value
class Player(Entity):
ids = set()
def __init__(self, pid):
super().__init__()
self.id = pid
self.__matches = set()
def __repr__(self):
return 'Player<{}>'.format(self.id)
@property
def matches(self):
return set(self.__matches)
def inscribe(self, match):
if match not in self.__matches:
self.__matches.add(match)
def delist(self, match):
self.__matches.remove(match)
class Match(Entity):
ids = set()
def __init__(self, mid, players):
super().__init__()
self.id = mid
self.__players = set()
self.players = players
for player in players:
player.inscribe(self)
def __repr__(self):
return 'Match<{}>'.format(self.id)
@property
def players(self):
return set(self.__players)
@players.setter
def players(self, value):
for player in self.__players:
player.delist(self)
self.__players = set(value)
for player in self.__players:
player.inscribe(self)
if __name__=='__main__':
players = [Player(i) for i in range(1, 6)]
matches = [Match(i, {players[i-1], players[i], players[i+1]}) for i in range(1, 4)]
for player in players:
print(player, player.matches)
for match in matches:
print(match, match.players)
d = {player.id: Counter([p.id for match in player.matches for p in match.players]) for player in players}
printMatrix(d)
The printMatrix() function is just a helper I made to pretty-print the output into the screen.
The Entity class avoids duplicate code that would be needed for both the Player and Match classes as they both have unique IDs. The constructor creates a empty _id attribute. The register() and unregister() methods handle adding and removing IDs from the class attribute ids. It also declares the id property with its getter and setter. Children classes only need to call super().__init__() at the constructor and create the ids class attribute at the level where the ID-uniqueness wants to be enforced, as both Player and Match are doing.
The Player class additionally has the matches instance read-only property that is populated and depopulated with inscribe() and delist() methods respectively. The Match class has the players property with its getter and setter methods.
First the players and matches are created with two list comprehensions (remember that lists start at position 0, so the Player with ID 1 is at players[0]) and printed with their corresponding relations (matches that they play for players and players that participate for the matches).
As both are keeping a reference to the other type, we can get all the info needed to build the dict of Counters you requested just from players.
5should be a1, shouldnt it?