0

I'm trying to create a adjacency matrix for a set of team players. I've stored the match details like this-

 x={'match1':[1,2,3],'match2':[2,3,4],'match3':[3,4,5]}

Here each key word has a list of values that contains the team players that played for that match.

I'm trying to create an adjacency matrix that shows how many matches each team player played with another team member

The output should look like this

  1 2 3 4 5
1 1 1 1 0 0
2 1 2 2 1 0
3 1 2 3 2 1
4 0 1 2 2 1 
5 0 0 1 1 1

(i,i) element in the matrix is the total number of matches played by that team member. I've been able to calculate the values correctly using Counter.

from collections import defaultdict, Counter

if __name__=='__main__':
    x = {'match1': [1, 2, 3], 'match2': [2, 3, 4], 'match3': [3, 4, 5]}
    d = defaultdict(list)
    col_count = dict()
    for key, value in x.items():
        for i in value:
            d[i] += value
    for key, value in d.items():
        col_count[key] = Counter(d[key])
    print(col_count)

The output is:

{1: Counter({1: 1, 2: 1, 3: 1}), 2: Counter({2: 2, 3: 2, 1: 1, 4: 1}), 3: Counter({3: 3, 2: 2, 4: 2, 1: 1, 5: 1}), 4: Counter({3: 2, 4: 2, 2: 1, 5: 1}), 5: Counter({3: 1, 4: 1, 5: 1})}

Given that x dictionary will contain a large number of keys and each key will have a list with many elements, I wish to avoid the use of nested for loops.

Is it possible to store the match details as any other data type so that subsequent computation will not require nested loops?

If a dictionary is the best way, is it possible to calculate the matrix some other way?

5
  • I think there is a typo in the matrix, that 5 should be a 1, shouldnt it? Commented Oct 23, 2017 at 8:14
  • The error has been corrected. Commented Oct 23, 2017 at 8:18
  • what dose it mean that row=2 path=2 result=2 ? Commented Oct 23, 2017 at 9:03
  • Player 2 played 2 matches. Commented Oct 23, 2017 at 9:26
  • I don't think you can. You can't do better than process every pair of players at least once, which you are already doing. Maybe you don't need to compute the adjacency matrix at all, depending on how you use it? Commented Oct 23, 2017 at 9:58

1 Answer 1

1

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.

Sign up to request clarification or add additional context in comments.

5 Comments

How can I modify the input? I'm unable to figure out how else I could store the list elements. I could store it as a list of list but then again I will require nested loops.
@Misha I edited my answer to include how would I structure the data by having two classes whose instances reference eachother. You will probably include many more attributes like match results or player stats.
I'm unable to understand some parts of printMatrix function. Could you direct me to some documentation or provide some help so that I could understand fmt = ' {row} |' and fmt += ' {values[' + str(col) + ']}'. I tried searching for help but haven't been able to find anything that helps me understand these lines.
Sure, here you have it. I'm first building the string with {} as placeholders for the variables that will latter take their place. Basically it will end up being a sting like " {row} | {values[0]} {values[1]} {values[2]} {values[3]} {values[4]}" and then you call .format() function with two keyword parameters, row and values, and the formatter will insert the row and the elements of values into the places where the curly brackets are.
@Misha It's an auxiliary function I made to print the matrixes the same way you did in the OP without reliying on the size. This function works for any size.

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.