-
Notifications
You must be signed in to change notification settings - Fork 0
/
traverse.go
232 lines (194 loc) · 4.71 KB
/
traverse.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
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
// Package traverse provides merkledag traversal functions
package traverse
import (
"errors"
"time"
"github.com/ipfs/go-ipfs/Godeps/_workspace/src/golang.org/x/net/context"
mdag "github.com/ipfs/go-ipfs/merkledag"
)
// Order is an identifier for traversal algorithm orders
type Order int
const (
DFSPre Order = iota // depth-first pre-order
DFSPost // depth-first post-order
BFS // breadth-first
)
// Options specifies a series of traversal options
type Options struct {
DAG mdag.DAGService // the dagservice to fetch nodes
Order Order // what order to traverse in
Func Func // the function to perform at each step
ErrFunc ErrFunc // see ErrFunc. Optional
SkipDuplicates bool // whether to skip duplicate nodes
}
// State is a current traversal state
type State struct {
Node *mdag.Node
Depth int
}
type traversal struct {
opts Options
seen map[string]struct{}
}
func (t *traversal) shouldSkip(n *mdag.Node) (bool, error) {
if t.opts.SkipDuplicates {
k, err := n.Key()
if err != nil {
return true, err
}
if _, found := t.seen[string(k)]; found {
return true, nil
}
t.seen[string(k)] = struct{}{}
}
return false, nil
}
func (t *traversal) callFunc(next State) error {
return t.opts.Func(next)
}
// getNode returns the node for link. If it return an error,
// stop processing. if it returns a nil node, just skip it.
//
// the error handling is a little complicated.
func (t *traversal) getNode(link *mdag.Link) (*mdag.Node, error) {
getNode := func(l *mdag.Link) (*mdag.Node, error) {
ctx, cancel := context.WithTimeout(context.TODO(), time.Minute)
defer cancel()
next, err := l.GetNode(ctx, t.opts.DAG)
if err != nil {
return nil, err
}
skip, err := t.shouldSkip(next)
if skip {
next = nil
}
return next, err
}
next, err := getNode(link)
if err != nil && t.opts.ErrFunc != nil { // attempt recovery.
err = t.opts.ErrFunc(err)
next = nil // skip regardless
}
return next, err
}
// Func is the type of the function called for each dag.Node visited by Traverse.
// The traversal argument contains the current traversal state.
// If an error is returned, processing stops.
type Func func(current State) error
// If there is a problem walking to the Node, and ErrFunc is provided, Traverse
// will call ErrFunc with the error encountered. ErrFunc can decide how to handle
// that error, and return an error back to Traversal with how to proceed:
// * nil - skip the Node and its children, but continue processing
// * all other errors halt processing immediately.
//
// If ErrFunc is nil, Traversal will stop, as if:
//
// opts.ErrFunc = func(err error) { return err }
//
type ErrFunc func(err error) error
func Traverse(root *mdag.Node, o Options) error {
t := traversal{
opts: o,
seen: map[string]struct{}{},
}
state := State{
Node: root,
Depth: 0,
}
switch o.Order {
default:
return dfsPreTraverse(state, &t)
case DFSPre:
return dfsPreTraverse(state, &t)
case DFSPost:
return dfsPostTraverse(state, &t)
case BFS:
return bfsTraverse(state, &t)
}
}
type dfsFunc func(state State, t *traversal) error
func dfsPreTraverse(state State, t *traversal) error {
if err := t.callFunc(state); err != nil {
return err
}
if err := dfsDescend(dfsPreTraverse, state, t); err != nil {
return err
}
return nil
}
func dfsPostTraverse(state State, t *traversal) error {
if err := dfsDescend(dfsPostTraverse, state, t); err != nil {
return err
}
if err := t.callFunc(state); err != nil {
return err
}
return nil
}
func dfsDescend(df dfsFunc, curr State, t *traversal) error {
for _, l := range curr.Node.Links {
node, err := t.getNode(l)
if err != nil {
return err
}
if node == nil { // skip
continue
}
next := State{
Node: node,
Depth: curr.Depth + 1,
}
if err := df(next, t); err != nil {
return err
}
}
return nil
}
func bfsTraverse(root State, t *traversal) error {
if skip, err := t.shouldSkip(root.Node); skip || err != nil {
return err
}
var q queue
q.enq(root)
for q.len() > 0 {
curr := q.deq()
if curr.Node == nil {
return errors.New("failed to dequeue though queue not empty")
}
// call user's func
if err := t.callFunc(curr); err != nil {
return err
}
for _, l := range curr.Node.Links {
node, err := t.getNode(l)
if err != nil {
return err
}
if node == nil { // skip
continue
}
q.enq(State{
Node: node,
Depth: curr.Depth + 1,
})
}
}
return nil
}
type queue struct {
s []State
}
func (q *queue) enq(n State) {
q.s = append(q.s, n)
}
func (q *queue) deq() State {
if len(q.s) < 1 {
return State{}
}
n := q.s[0]
q.s = q.s[1:]
return n
}
func (q *queue) len() int {
return len(q.s)
}