-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathgraph.js
More file actions
171 lines (159 loc) · 5.45 KB
/
graph.js
File metadata and controls
171 lines (159 loc) · 5.45 KB
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
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
/* All of the functions in this file operate on a graph. The type of graph is
* expected to be as follows:
* type name = string
* type graph = name -> {net : int,
* neighbors : name -> {net : int,
* transactions : (int * string) array}}
*/
/*
* Add a directed edge to the graph.
* Call this twice to properly add a transaction.
*/
function addTransactionHalf(graph, from, to, amount, description) {
if (graph[from] == undefined) {
graph[from] = {};
graph[from]["net"] = amount
graph[from]["neighbors"] = {};
graph[from]["neighbors"][to] = {};
graph[from]["neighbors"][to]["net"] = amount;
graph[from]["neighbors"][to]["transactions"] = [];
graph[from]["neighbors"][to]["transactions"][0] = {
"amount":amount,
"description":description
};
} else if (graph[from]["neighbors"][to] == undefined) {
graph[from]["net"] += amount;
graph[from]["neighbors"][to] = {};
graph[from]["neighbors"][to]["net"] = amount;
graph[from]["neighbors"][to]["transactions"] = [];
graph[from]["neighbors"][to]["transactions"][0] = {
"amount":amount,
"description":description
};
} else {
graph[from]["net"] += amount;
graph[from]["neighbors"][to]["net"] += amount;
var length = graph[from]["neighbors"][to]["transactions"].length
graph[from]["neighbors"][to]["transactions"][length] = {
"amount":amount,
"description":description
};
}
}
/*
* Get the net debt of the person name. Returns a negative value if the person
* is owed money.
*/
function getNet(graph, name) {
if (graph[name] == undefined) {
return 0;
} else {
return graph[name]["net"];
}
}
/*
* Get the net debt from owes to. Returns a negative value if to owes from more
* money than from owes to.
*/
function getNetBetween(graph, from, to) {
if (graph[from] == undefined || graph[from]["neighbors"][to] == undefined) {
return 0;
} else {
return graph[from]["neighbors"][to]["net"];
}
}
/*
* Returns the neighbors of name in graph, complete with information about debt
* and transaction between them.
*/
function getNeighbors(graph, name) {
if (graph[name] == undefined) {
return {};
} else {
return graph[name]["neighbors"];
}
}
/*
* Returns the transaction history between from and to.
*/
function getTransactions(graph, from, to) {
if (graph[from] == undefined || graph[from]["neighbors"][to] == undefined) {
return [];
} else {
return graph[from]["neighbors"][to]["transactions"];
}
}
/*
* Finds the heaviest path from left to right, where heaviest is defined as
* length multiplied by minimum weight across the edges in the path and
* maxWeight. Call this before adding a new transaction in order to find any
* cycles that adding the transaction would create. Returns "none" if no cycles
* are found.
*/
function bestCycle(graph, left, right) {
var visited = {};
var paths = [];
var frontier = [] // maybe make this a priority queue
var next = newNeighbors(left);
for (var i = 0; i < next.length; i++) {
frontier.push({"name":next[i], "path": [next[i]]});
}
function newNeighbors(name) {
var neighbors = getNeighbors(graph, name);
var result = []
for (var neighbor in neighbors) {
if (!(neighbor in visited) && neighbors[neighbor]["net"] > 0) {
result[result.length] = neighbor;
}
}
return result;
}
// Breadth first search the graph to find all of the positive paths.
while (frontier.length > 0) {
// remove first element
var current = frontier.shift()
if (current["name"] === right) {
paths[paths.length] = current["path"];
}
visited[current["name"]] = true;
var next = newNeighbors(current["name"]);
for (var i = 0; i < next.length; i++) {
var newPath = current["path"].slice()
newPath.push(next[i]);
frontier.push({"name":next[i],
// this copying of path is slow
"path":newPath});
}
}
if (paths.length === 0) {
return "none";
}
function pathWeight(graph, left, right, path) {
/* The weight in a path is the minimum weight in the path,
* because we cannot resolve more debt than the smallest. */
var min = getNetBetween(graph, right, left);
if (getNetBetween(graph, left, path[0]) < min) {
min = getNetBetween(graph, left, path[0]);
}
for (var i = 1; i < path.length; i++) {
var current = getNetBetween(graph, path[i - 1], path[i]);
if (current < min) {
min = current;
}
}
return path.length * min;
}
// Pick the best path.
var bestPath = paths[0];
var bestWeight = pathWeight(graph, left, right, bestPath);
for (var i = 1; i < paths.length; i++) {
var currentWeight = pathWeight(graph, left, right, paths[i]);
if (currentWeight > bestWeight ||
(currentWeight === bestWeight &&
path[1].length < bestPath.length)) {
bestWeight = currentWeight;
bestPath = paths[i];
}
}
return {"path":bestPath, "amount":bestWeight / bestPath.length};
}