-
Notifications
You must be signed in to change notification settings - Fork 0
/
skillrank.go
149 lines (118 loc) · 3.28 KB
/
skillrank.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
package skillrank
import (
"fmt"
"math"
)
type node struct {
weight float64
outbound float64
}
// Graph holds node and edge data.
type Graph struct {
edges map[uint32](map[uint32]float64)
nodes map[uint32]*node
}
// NewGraph initializes and returns a new graph.
func NewGraph() *Graph {
return &Graph{
edges: make(map[uint32](map[uint32]float64)),
nodes: make(map[uint32]*node),
}
}
// Link creates a weighted edge between a source-target node pair.
func (self *Graph) Link(source, target uint32, level string) {
// weight is equal to the level of the skill, "basic" = 1.0, "intermediate" = 2.0, "advanced" = 4.0
weight := 0.0
switch level {
case "basic":
weight = 1.0
case "intermediate":
weight = 2.0
case "advanced":
weight = 4.0
}
if _, ok := self.nodes[source]; ok == false {
self.nodes[source] = &node{
weight: 0,
outbound: 0,
}
}
// If the edge already exists, we need to subtract the old weight
if _, ok := self.edges[source]; ok == true {
if _, ok := self.edges[source][target]; ok == true {
self.nodes[source].outbound -= self.edges[source][target]
}
}
// Add the new weight
self.nodes[source].outbound += weight
if _, ok := self.nodes[target]; ok == false {
self.nodes[target] = &node{
weight: 0,
outbound: 0,
}
}
if _, ok := self.edges[source]; ok == false {
self.edges[source] = map[uint32]float64{}
}
self.edges[source][target] = weight
}
// Rank computes the PageRank of every node in the directed graph.
// alpha is the damping factor, usually set to 0.85.
// epsilon is the convergence criteria, usually set to a tiny value.
//
// This method will run as many iterations as needed, until the graph converges.
func (self *Graph) Rank(alpha, epsilon float64, callback func(id uint32, rank float64)) {
delta := float64(1.0)
inverse := 1 / float64(len(self.nodes))
// Normalize all the edge weights so that their sum amounts to 1.
for source := range self.edges {
if self.nodes[source].outbound > 0 {
for target := range self.edges[source] {
self.edges[source][target] /= self.nodes[source].outbound
}
}
}
for key := range self.nodes {
self.nodes[key].weight = inverse
}
for delta > epsilon {
leak := float64(0)
nodes := map[uint32]float64{}
for key, value := range self.nodes {
nodes[key] = value.weight
if value.outbound == 0 {
leak += value.weight
}
self.nodes[key].weight = 0
}
leak *= alpha
for source := range self.nodes {
for target, weight := range self.edges[source] {
self.nodes[target].weight += alpha * nodes[source] * weight
}
self.nodes[source].weight += (1-alpha)*inverse + leak*inverse
}
delta = 0
for key, value := range self.nodes {
delta += math.Abs(value.weight - nodes[key])
}
}
for key, value := range self.nodes {
callback(key, value.weight)
}
}
// Reset clears all the current graph data.
func (self *Graph) Reset() {
self.edges = make(map[uint32](map[uint32]float64))
self.nodes = make(map[uint32]*node)
}
// Return a JSON string with the skill ranking
func (self *Graph) RankInJSON(alpha, epsilon float64) string {
var ranking = ""
self.Rank(alpha, epsilon, func(id uint32, rank float64) {
ranking += fmt.Sprintf("\"%d\": %f, ", id, rank)
})
ranking = ranking[:len(ranking)-2]
ranking = "{" + ranking + "}"
return ranking
}