# Building adjacency matrix from set data

I have two sets: games and players. Players pick their games. As such I will have data say g1 = {p1, p3, p5}, g2 = {p2, p4}, g3 = {p2, p3, p5}. My interest though is to build the connections among players because of the common games they have played. For instance, since p1 played with p3 and p5 in game 1, then they are connected. I can then build an adjacency matrix for the players: {{0,0,1,0,1},{0,0,1,1,1},{1,1,0,0,1},{0,1,0,0,0},{1,1,1,0,0}}.I'll be doing this for hundreds of games and thousands of players. Question is: how do I efficiently get the adjacency matrix given the data? Thanks for the inputs.

• In your example you have a unique symbol for each player and game. When you have thousands of players do you intend to use thousands of symbols? Does the actual data consist of hundreds of expressions like g1 = {p1, p3, p5} ? May 20 '14 at 9:05
• See here for some routines that will give you the "who played whom" data, which can then be trivially mapped to an adjacency matrix form.
– ciao
May 20 '14 at 9:06
• Yes Simon. I'll have hundreds of those expressions. May 20 '14 at 13:49

Here's an example using one of the routines in the post referenced in my comment:

(* your example *)
g1 = {p1, p3, p5};
g2 = {p2, p4};
g3 = {p2, p3, p5};

(* format to use routine and get all player ID *)
games = {g1, g2, g3};
players = Union @@ games;

(* use routine to get "who played whom"... map to ints for later*)

(* get max to dim adj matrix later *)

(* put all together to produce adj matrix *)

(*

{{0, 0, 1, 0, 1}, {0, 0, 1, 1, 1}, {1, 1, 0, 0, 1}, {0, 1, 0, 0, 0}, {1, 1, 1, 0, 0}}

*)


A quick and dirty benchmark comparing graph function based vs getNeighbors based solution. I used games = DeleteDuplicates /@ RandomInteger[size, {20, 20}] for this, simulating a 20 games X up to 20 players per game, with size being the size of player pool. The test was kept quite small for obvious time reasons, but illustrates the trend. It appears the size parameter may be the least sensitive for the graph function based solution (that is, increasing other parameters like number of games / game size exhibits even worse behavior).

While elegant (it's one of the methods I attempted when pondering the "neighbors" question), the setup costs to get to the graph function ready state kills it for larger problems.

N.B.: tested on loungebook while cigaring, so both should be 5-10X+ faster on real machines.

• Thanks rasher. I'd suppose the routines would come in handy. May 20 '14 at 14:40
• @newbiepy: If performance matters, yes (which you seemed to imply with the use of "efficiency" in your OP) - the accepted answer will be hideously slow on large sets - in a trivial couple of hundred player test it's over 2500X slower, and it gets dramatically worse as problem size grows. Did you actually test it on problems of the size you allude to in your OP?
– ciao
May 20 '14 at 22:51
• Thanks rasher for this inputs. No I haven't tested yet...but the data set would be become bigger. So thanks for the foresight and will work on understanding your code. May 21 '14 at 0:45
• @newbiepy: Added bmark example, ask away if you need clarification on code (it's basically bit-mask trickery to encode set membership which makes lookups efficient).
– ciao
May 21 '14 at 1:17
• Thanks again! The benchmark graph says it all. Thanks also for opening your lines if I need further assistance. a May 21 '14 at 11:56

Using the setup in @rasher's answer

g1 = {p1, p3, p5};
g2 = {p2, p4};
g3 = {p2, p3, p5};
games = {g1, g2, g3};
players = Union @@ games;

edges = UndirectedEdge @@@ Subsets[#, {2}] & /@ games // Flatten // DeleteDuplicates;
Normal@am

(* {{0, 0, 1, 0, 1}, {0, 0, 1, 1, 1}, {1, 1, 0, 0, 1},
{0, 1, 0, 0, 0}, {1, 1, 1, 0, 0}} *)


Or, two variations of the same idea:

amF = AdjacencyMatrix[GraphUnion @@ (Graph[#,
DeleteDuplicates[Flatten[UndirectedEdge @@@ Subsets[#, {2}]]]] & /@ #)] &;
ConstantArray[1, {Length@#, Length@#}] -IdentityMatrix[Length@#]] & /@ #)] &;

am == amF@games == amF2@games;
(* True *)


Update: The following is rasher's getNeighbors slightly modified for the current use case:

amR[listarg_] := Block[{uniques, map, digits, lu, la, im},
la = DeleteDuplicates /@ listarg;
uniques = Union @@ la;
lu = Length[uniques];
im = IdentityMatrix[lu];
map = Total[la /. Dispatch[Thread[uniques -> 2^Range[0, lu - 1]]], {2}];
digits = IntegerDigits[map, 2, lu] // Transpose;
(IntegerDigits[#, 2, lu] //
Reverse) & /@ (BitOr @@ Pick[map, #, 1] & /@ (digits) //Reverse) - im];

am == amR@games
(* True *)

• Thanks kguler. This was simple enough for me to follow! May 20 '14 at 14:34
• Very, very slow on anything but toy-sized problems. But +1 nonetheless.
– ciao
May 20 '14 at 22:41
• @rasher, thanks for the vote. I did not expect much from this approach performance-wise, esp in comparison to the method in your answer. @newbie, thank you for the accept but, if I were you, I would go for rasher's answer. Besides huge performance benefits, you will enjoy the cleverness of the approach if you spend some effort to understand getNeighbors.
– kglr
May 20 '14 at 23:56
• Thanks kguler for the inputs and for referring me to rasher. Appreciate that. May 21 '14 at 1:05
• @kguler: You are a gentleman of the first order.
– ciao
May 21 '14 at 8:22