##// END OF EJS Templates
ancestor: add lazy membership testing to lazyancestors...
Siddharth Agarwal -
r18091:f7f8159c default
parent child Browse files
Show More
@@ -1,277 +1,277 b''
1 1 # perf.py - performance test routines
2 2 '''helper extension to measure performance'''
3 3
4 4 from mercurial import cmdutil, scmutil, util, match, commands
5 5 import time, os, sys
6 6
7 7 def timer(func, title=None):
8 8 results = []
9 9 begin = time.time()
10 10 count = 0
11 11 while True:
12 12 ostart = os.times()
13 13 cstart = time.time()
14 14 r = func()
15 15 cstop = time.time()
16 16 ostop = os.times()
17 17 count += 1
18 18 a, b = ostart, ostop
19 19 results.append((cstop - cstart, b[0] - a[0], b[1]-a[1]))
20 20 if cstop - begin > 3 and count >= 100:
21 21 break
22 22 if cstop - begin > 10 and count >= 3:
23 23 break
24 24 if title:
25 25 sys.stderr.write("! %s\n" % title)
26 26 if r:
27 27 sys.stderr.write("! result: %s\n" % r)
28 28 m = min(results)
29 29 sys.stderr.write("! wall %f comb %f user %f sys %f (best of %d)\n"
30 30 % (m[0], m[1] + m[2], m[1], m[2], count))
31 31
32 32 def perfwalk(ui, repo, *pats):
33 33 try:
34 34 m = scmutil.match(repo[None], pats, {})
35 35 timer(lambda: len(list(repo.dirstate.walk(m, [], True, False))))
36 36 except Exception:
37 37 try:
38 38 m = scmutil.match(repo[None], pats, {})
39 39 timer(lambda: len([b for a, b, c in repo.dirstate.statwalk([], m)]))
40 40 except Exception:
41 41 timer(lambda: len(list(cmdutil.walk(repo, pats, {}))))
42 42
43 43 def perfstatus(ui, repo, **opts):
44 44 #m = match.always(repo.root, repo.getcwd())
45 45 #timer(lambda: sum(map(len, repo.dirstate.status(m, [], False, False,
46 46 # False))))
47 47 timer(lambda: sum(map(len, repo.status(**opts))))
48 48
49 49 def clearcaches(cl):
50 50 # behave somewhat consistently across internal API changes
51 51 if util.safehasattr(cl, 'clearcaches'):
52 52 cl.clearcaches()
53 53 elif util.safehasattr(cl, '_nodecache'):
54 54 from mercurial.node import nullid, nullrev
55 55 cl._nodecache = {nullid: nullrev}
56 56 cl._nodepos = None
57 57
58 58 def perfheads(ui, repo):
59 59 cl = repo.changelog
60 60 def d():
61 61 len(cl.headrevs())
62 62 clearcaches(cl)
63 63 timer(d)
64 64
65 65 def perftags(ui, repo):
66 66 import mercurial.changelog, mercurial.manifest
67 67 def t():
68 68 repo.changelog = mercurial.changelog.changelog(repo.sopener)
69 69 repo.manifest = mercurial.manifest.manifest(repo.sopener)
70 70 repo._tags = None
71 71 return len(repo.tags())
72 72 timer(t)
73 73
74 74 def perfancestors(ui, repo):
75 75 heads = repo.changelog.headrevs()
76 76 def d():
77 77 for a in repo.changelog.ancestors(heads):
78 78 pass
79 79 timer(d)
80 80
81 81 def perfancestorset(ui, repo, revset):
82 82 revs = repo.revs(revset)
83 83 heads = repo.changelog.headrevs()
84 84 def d():
85 s = set(repo.changelog.ancestors(heads))
85 s = repo.changelog.ancestors(heads)
86 86 for rev in revs:
87 87 rev in s
88 88 timer(d)
89 89
90 90 def perfdirstate(ui, repo):
91 91 "a" in repo.dirstate
92 92 def d():
93 93 repo.dirstate.invalidate()
94 94 "a" in repo.dirstate
95 95 timer(d)
96 96
97 97 def perfdirstatedirs(ui, repo):
98 98 "a" in repo.dirstate
99 99 def d():
100 100 "a" in repo.dirstate._dirs
101 101 del repo.dirstate._dirs
102 102 timer(d)
103 103
104 104 def perfdirstatewrite(ui, repo):
105 105 ds = repo.dirstate
106 106 "a" in ds
107 107 def d():
108 108 ds._dirty = True
109 109 ds.write()
110 110 timer(d)
111 111
112 112 def perfmanifest(ui, repo):
113 113 def d():
114 114 t = repo.manifest.tip()
115 115 m = repo.manifest.read(t)
116 116 repo.manifest.mapcache = None
117 117 repo.manifest._cache = None
118 118 timer(d)
119 119
120 120 def perfchangeset(ui, repo, rev):
121 121 n = repo[rev].node()
122 122 def d():
123 123 c = repo.changelog.read(n)
124 124 #repo.changelog._cache = None
125 125 timer(d)
126 126
127 127 def perfindex(ui, repo):
128 128 import mercurial.revlog
129 129 mercurial.revlog._prereadsize = 2**24 # disable lazy parser in old hg
130 130 n = repo["tip"].node()
131 131 def d():
132 132 cl = mercurial.revlog.revlog(repo.sopener, "00changelog.i")
133 133 cl.rev(n)
134 134 timer(d)
135 135
136 136 def perfstartup(ui, repo):
137 137 cmd = sys.argv[0]
138 138 def d():
139 139 os.system("HGRCPATH= %s version -q > /dev/null" % cmd)
140 140 timer(d)
141 141
142 142 def perfparents(ui, repo):
143 143 nl = [repo.changelog.node(i) for i in xrange(1000)]
144 144 def d():
145 145 for n in nl:
146 146 repo.changelog.parents(n)
147 147 timer(d)
148 148
149 149 def perflookup(ui, repo, rev):
150 150 timer(lambda: len(repo.lookup(rev)))
151 151
152 152 def perfrevrange(ui, repo, *specs):
153 153 revrange = scmutil.revrange
154 154 timer(lambda: len(revrange(repo, specs)))
155 155
156 156 def perfnodelookup(ui, repo, rev):
157 157 import mercurial.revlog
158 158 mercurial.revlog._prereadsize = 2**24 # disable lazy parser in old hg
159 159 n = repo[rev].node()
160 160 def d():
161 161 cl = mercurial.revlog.revlog(repo.sopener, "00changelog.i")
162 162 cl.rev(n)
163 163 timer(d)
164 164
165 165 def perfnodelookup(ui, repo, rev):
166 166 import mercurial.revlog
167 167 mercurial.revlog._prereadsize = 2**24 # disable lazy parser in old hg
168 168 n = repo[rev].node()
169 169 cl = mercurial.revlog.revlog(repo.sopener, "00changelog.i")
170 170 def d():
171 171 cl.rev(n)
172 172 clearcaches(cl)
173 173 timer(d)
174 174
175 175 def perflog(ui, repo, **opts):
176 176 ui.pushbuffer()
177 177 timer(lambda: commands.log(ui, repo, rev=[], date='', user='',
178 178 copies=opts.get('rename')))
179 179 ui.popbuffer()
180 180
181 181 def perftemplating(ui, repo):
182 182 ui.pushbuffer()
183 183 timer(lambda: commands.log(ui, repo, rev=[], date='', user='',
184 184 template='{date|shortdate} [{rev}:{node|short}]'
185 185 ' {author|person}: {desc|firstline}\n'))
186 186 ui.popbuffer()
187 187
188 188 def perfcca(ui, repo):
189 189 timer(lambda: scmutil.casecollisionauditor(ui, False, repo.dirstate))
190 190
191 191 def perffncacheload(ui, repo):
192 192 s = repo.store
193 193 def d():
194 194 s.fncache._load()
195 195 timer(d)
196 196
197 197 def perffncachewrite(ui, repo):
198 198 s = repo.store
199 199 s.fncache._load()
200 200 def d():
201 201 s.fncache._dirty = True
202 202 s.fncache.write()
203 203 timer(d)
204 204
205 205 def perffncacheencode(ui, repo):
206 206 s = repo.store
207 207 s.fncache._load()
208 208 def d():
209 209 for p in s.fncache.entries:
210 210 s.encode(p)
211 211 timer(d)
212 212
213 213 def perfdiffwd(ui, repo):
214 214 """Profile diff of working directory changes"""
215 215 options = {
216 216 'w': 'ignore_all_space',
217 217 'b': 'ignore_space_change',
218 218 'B': 'ignore_blank_lines',
219 219 }
220 220
221 221 for diffopt in ('', 'w', 'b', 'B', 'wB'):
222 222 opts = dict((options[c], '1') for c in diffopt)
223 223 def d():
224 224 ui.pushbuffer()
225 225 commands.diff(ui, repo, **opts)
226 226 ui.popbuffer()
227 227 title = 'diffopts: %s' % (diffopt and ('-' + diffopt) or 'none')
228 228 timer(d, title)
229 229
230 230 def perfrevlog(ui, repo, file_, **opts):
231 231 from mercurial import revlog
232 232 dist = opts['dist']
233 233 def d():
234 234 r = revlog.revlog(lambda fn: open(fn, 'rb'), file_)
235 235 for x in xrange(0, len(r), dist):
236 236 r.revision(r.node(x))
237 237
238 238 timer(d)
239 239
240 240 def perfrevset(ui, repo, expr):
241 241 def d():
242 242 repo.revs(expr)
243 243 timer(d)
244 244
245 245 cmdtable = {
246 246 'perfcca': (perfcca, []),
247 247 'perffncacheload': (perffncacheload, []),
248 248 'perffncachewrite': (perffncachewrite, []),
249 249 'perffncacheencode': (perffncacheencode, []),
250 250 'perflookup': (perflookup, []),
251 251 'perfrevrange': (perfrevrange, []),
252 252 'perfnodelookup': (perfnodelookup, []),
253 253 'perfparents': (perfparents, []),
254 254 'perfstartup': (perfstartup, []),
255 255 'perfstatus': (perfstatus,
256 256 [('u', 'unknown', False,
257 257 'ask status to look for unknown files')]),
258 258 'perfwalk': (perfwalk, []),
259 259 'perfmanifest': (perfmanifest, []),
260 260 'perfchangeset': (perfchangeset, []),
261 261 'perfindex': (perfindex, []),
262 262 'perfheads': (perfheads, []),
263 263 'perftags': (perftags, []),
264 264 'perfancestors': (perfancestors, []),
265 265 'perfancestorset': (perfancestorset, [], "REVSET"),
266 266 'perfdirstate': (perfdirstate, []),
267 267 'perfdirstatedirs': (perfdirstate, []),
268 268 'perfdirstatewrite': (perfdirstatewrite, []),
269 269 'perflog': (perflog,
270 270 [('', 'rename', False, 'ask log to follow renames')]),
271 271 'perftemplating': (perftemplating, []),
272 272 'perfdiffwd': (perfdiffwd, []),
273 273 'perfrevlog': (perfrevlog,
274 274 [('d', 'dist', 100, 'distance between the revisions')],
275 275 "[INDEXFILE]"),
276 276 'perfrevset': (perfrevset, [], "REVSET")
277 277 }
@@ -1,221 +1,264 b''
1 1 # ancestor.py - generic DAG ancestor algorithm for mercurial
2 2 #
3 3 # Copyright 2006 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 import heapq, util
9 9 from node import nullrev
10 10
11 11 def ancestor(a, b, pfunc):
12 12 """
13 13 Returns the common ancestor of a and b that is furthest from a
14 14 root (as measured by longest path) or None if no ancestor is
15 15 found. If there are multiple common ancestors at the same
16 16 distance, the first one found is returned.
17 17
18 18 pfunc must return a list of parent vertices for a given vertex
19 19 """
20 20
21 21 if a == b:
22 22 return a
23 23
24 24 a, b = sorted([a, b])
25 25
26 26 # find depth from root of all ancestors
27 27 # depth is stored as a negative for heapq
28 28 parentcache = {}
29 29 visit = [a, b]
30 30 depth = {}
31 31 while visit:
32 32 vertex = visit[-1]
33 33 pl = pfunc(vertex)
34 34 parentcache[vertex] = pl
35 35 if not pl:
36 36 depth[vertex] = 0
37 37 visit.pop()
38 38 else:
39 39 for p in pl:
40 40 if p == a or p == b: # did we find a or b as a parent?
41 41 return p # we're done
42 42 if p not in depth:
43 43 visit.append(p)
44 44 if visit[-1] == vertex:
45 45 # -(maximum distance of parents + 1)
46 46 depth[vertex] = min([depth[p] for p in pl]) - 1
47 47 visit.pop()
48 48
49 49 # traverse ancestors in order of decreasing distance from root
50 50 def ancestors(vertex):
51 51 h = [(depth[vertex], vertex)]
52 52 seen = set()
53 53 while h:
54 54 d, n = heapq.heappop(h)
55 55 if n not in seen:
56 56 seen.add(n)
57 57 yield (d, n)
58 58 for p in parentcache[n]:
59 59 heapq.heappush(h, (depth[p], p))
60 60
61 61 def generations(vertex):
62 62 sg, s = None, set()
63 63 for g, v in ancestors(vertex):
64 64 if g != sg:
65 65 if sg:
66 66 yield sg, s
67 67 sg, s = g, set((v,))
68 68 else:
69 69 s.add(v)
70 70 yield sg, s
71 71
72 72 x = generations(a)
73 73 y = generations(b)
74 74 gx = x.next()
75 75 gy = y.next()
76 76
77 77 # increment each ancestor list until it is closer to root than
78 78 # the other, or they match
79 79 try:
80 80 while True:
81 81 if gx[0] == gy[0]:
82 82 for v in gx[1]:
83 83 if v in gy[1]:
84 84 return v
85 85 gy = y.next()
86 86 gx = x.next()
87 87 elif gx[0] > gy[0]:
88 88 gy = y.next()
89 89 else:
90 90 gx = x.next()
91 91 except StopIteration:
92 92 return None
93 93
94 94 def missingancestors(revs, bases, pfunc):
95 95 """Return all the ancestors of revs that are not ancestors of bases.
96 96
97 97 This may include elements from revs.
98 98
99 99 Equivalent to the revset (::revs - ::bases). Revs are returned in
100 100 revision number order, which is a topological order.
101 101
102 102 revs and bases should both be iterables. pfunc must return a list of
103 103 parent revs for a given revs.
104 104 """
105 105
106 106 revsvisit = set(revs)
107 107 basesvisit = set(bases)
108 108 if not revsvisit:
109 109 return []
110 110 if not basesvisit:
111 111 basesvisit.add(nullrev)
112 112 start = max(max(revsvisit), max(basesvisit))
113 113 bothvisit = revsvisit.intersection(basesvisit)
114 114 revsvisit.difference_update(bothvisit)
115 115 basesvisit.difference_update(bothvisit)
116 116 # At this point, we hold the invariants that:
117 117 # - revsvisit is the set of nodes we know are an ancestor of at least one
118 118 # of the nodes in revs
119 119 # - basesvisit is the same for bases
120 120 # - bothvisit is the set of nodes we know are ancestors of at least one of
121 121 # the nodes in revs and one of the nodes in bases
122 122 # - a node may be in none or one, but not more, of revsvisit, basesvisit
123 123 # and bothvisit at any given time
124 124 # Now we walk down in reverse topo order, adding parents of nodes already
125 125 # visited to the sets while maintaining the invariants. When a node is
126 126 # found in both revsvisit and basesvisit, it is removed from them and
127 127 # added to bothvisit instead. When revsvisit becomes empty, there are no
128 128 # more ancestors of revs that aren't also ancestors of bases, so exit.
129 129
130 130 missing = []
131 131 for curr in xrange(start, nullrev, -1):
132 132 if not revsvisit:
133 133 break
134 134
135 135 if curr in bothvisit:
136 136 bothvisit.remove(curr)
137 137 # curr's parents might have made it into revsvisit or basesvisit
138 138 # through another path
139 139 for p in pfunc(curr):
140 140 revsvisit.discard(p)
141 141 basesvisit.discard(p)
142 142 bothvisit.add(p)
143 143 continue
144 144
145 145 # curr will never be in both revsvisit and basesvisit, since if it
146 146 # were it'd have been pushed to bothvisit
147 147 if curr in revsvisit:
148 148 missing.append(curr)
149 149 thisvisit = revsvisit
150 150 othervisit = basesvisit
151 151 elif curr in basesvisit:
152 152 thisvisit = basesvisit
153 153 othervisit = revsvisit
154 154 else:
155 155 # not an ancestor of revs or bases: ignore
156 156 continue
157 157
158 158 thisvisit.remove(curr)
159 159 for p in pfunc(curr):
160 160 if p == nullrev:
161 161 pass
162 162 elif p in othervisit or p in bothvisit:
163 163 # p is implicitly in thisvisit. This means p is or should be
164 164 # in bothvisit
165 165 revsvisit.discard(p)
166 166 basesvisit.discard(p)
167 167 bothvisit.add(p)
168 168 else:
169 169 # visit later
170 170 thisvisit.add(p)
171 171
172 172 missing.reverse()
173 173 return missing
174 174
175 175 class lazyancestors(object):
176 176 def __init__(self, cl, revs, stoprev=0, inclusive=False):
177 177 """Create a new object generating ancestors for the given revs. Does
178 178 not generate revs lower than stoprev.
179 179
180 This is computed lazily starting from revs. The object only supports
181 iteration.
180 This is computed lazily starting from revs. The object supports
181 iteration and membership.
182 182
183 183 cl should be a changelog and revs should be an iterable. inclusive is
184 184 a boolean that indicates whether revs should be included. Revs lower
185 185 than stoprev will not be generated.
186 186
187 187 Result does not include the null revision."""
188 188 self._parentrevs = cl.parentrevs
189 189 self._initrevs = revs
190 190 self._stoprev = stoprev
191 191 self._inclusive = inclusive
192 192
193 # Initialize data structures for __contains__.
194 # For __contains__, we use a heap rather than a deque because
195 # (a) it minimizes the number of parentrevs calls made
196 # (b) it makes the loop termination condition obvious
197 # Python's heap is a min-heap. Multiply all values by -1 to convert it
198 # into a max-heap.
199 self._containsvisit = [-rev for rev in revs]
200 heapq.heapify(self._containsvisit)
201 if inclusive:
202 self._containsseen = set(revs)
203 else:
204 self._containsseen = set()
205
193 206 def __iter__(self):
194 207 """Generate the ancestors of _initrevs in reverse topological order.
195 208
196 209 If inclusive is False, yield a sequence of revision numbers starting
197 210 with the parents of each revision in revs, i.e., each revision is *not*
198 211 considered an ancestor of itself. Results are in breadth-first order:
199 212 parents of each rev in revs, then parents of those, etc.
200 213
201 214 If inclusive is True, yield all the revs first (ignoring stoprev),
202 215 then yield all the ancestors of revs as when inclusive is False.
203 216 If an element in revs is an ancestor of a different rev it is not
204 217 yielded again."""
205 218 seen = set()
206 219 revs = self._initrevs
207 220 if self._inclusive:
208 221 for rev in revs:
209 222 yield rev
210 223 seen.update(revs)
211 224
212 225 parentrevs = self._parentrevs
213 226 stoprev = self._stoprev
214 227 visit = util.deque(revs)
215 228
216 229 while visit:
217 230 for parent in parentrevs(visit.popleft()):
218 231 if parent >= stoprev and parent not in seen:
219 232 visit.append(parent)
220 233 seen.add(parent)
221 234 yield parent
235
236 def __contains__(self, target):
237 """Test whether target is an ancestor of self._initrevs."""
238 # Trying to do both __iter__ and __contains__ using the same visit
239 # heap and seen set is complex enough that it slows down both. Keep
240 # them separate.
241 seen = self._containsseen
242 if target in seen:
243 return True
244
245 parentrevs = self._parentrevs
246 visit = self._containsvisit
247 stoprev = self._stoprev
248 heappop = heapq.heappop
249 heappush = heapq.heappush
250
251 targetseen = False
252
253 while visit and -visit[0] > target and not targetseen:
254 for parent in parentrevs(-heappop(visit)):
255 if parent < stoprev or parent in seen:
256 continue
257 # We need to make sure we push all parents into the heap so
258 # that we leave it in a consistent state for future calls.
259 heappush(visit, -parent)
260 seen.add(parent)
261 if parent == target:
262 targetseen = True
263
264 return targetseen
@@ -1,74 +1,106 b''
1 1 from mercurial import ancestor
2 2
3 3 # graph is a dict of child->parent adjacency lists for this graph:
4 4 # o 13
5 5 # |
6 6 # | o 12
7 7 # | |
8 8 # | | o 11
9 9 # | | |\
10 10 # | | | | o 10
11 11 # | | | | |
12 12 # | o---+ | 9
13 13 # | | | | |
14 14 # o | | | | 8
15 15 # / / / /
16 16 # | | o | 7
17 17 # | | | |
18 18 # o---+ | 6
19 19 # / / /
20 20 # | | o 5
21 21 # | |/
22 22 # | o 4
23 23 # | |
24 24 # o | 3
25 25 # | |
26 26 # | o 2
27 27 # |/
28 28 # o 1
29 29 # |
30 30 # o 0
31 31
32 32 graph = {0: [-1], 1: [0], 2: [1], 3: [1], 4: [2], 5: [4], 6: [4],
33 33 7: [4], 8: [-1], 9: [6, 7], 10: [5], 11: [3, 7], 12: [9],
34 34 13: [8]}
35 35 pfunc = graph.get
36 36
37 class mockchangelog(object):
38 parentrevs = graph.get
39
37 40 def runmissingancestors(revs, bases):
38 41 print "%% ancestors of %s and not of %s" % (revs, bases)
39 42 print ancestor.missingancestors(revs, bases, pfunc)
40 43
41 44 def test_missingancestors():
42 45 # Empty revs
43 46 runmissingancestors([], [1])
44 47 runmissingancestors([], [])
45 48
46 49 # If bases is empty, it's the same as if it were [nullrev]
47 50 runmissingancestors([12], [])
48 51
49 52 # Trivial case: revs == bases
50 53 runmissingancestors([0], [0])
51 54 runmissingancestors([4, 5, 6], [6, 5, 4])
52 55
53 56 # With nullrev
54 57 runmissingancestors([-1], [12])
55 58 runmissingancestors([12], [-1])
56 59
57 60 # 9 is a parent of 12. 7 is a parent of 9, so an ancestor of 12. 6 is an
58 61 # ancestor of 12 but not of 7.
59 62 runmissingancestors([12], [9])
60 63 runmissingancestors([9], [12])
61 64 runmissingancestors([12, 9], [7])
62 65 runmissingancestors([7, 6], [12])
63 66
64 67 # More complex cases
65 68 runmissingancestors([10], [11, 12])
66 69 runmissingancestors([11], [10])
67 70 runmissingancestors([11], [10, 12])
68 71 runmissingancestors([12], [10])
69 72 runmissingancestors([12], [11])
70 73 runmissingancestors([10, 11, 12], [13])
71 74 runmissingancestors([13], [10, 11, 12])
72 75
76 def genlazyancestors(revs, stoprev=0, inclusive=False):
77 print ("%% lazy ancestor set for %s, stoprev = %s, inclusive = %s" %
78 (revs, stoprev, inclusive))
79 return ancestor.lazyancestors(mockchangelog, revs, stoprev=stoprev,
80 inclusive=inclusive)
81
82 def printlazyancestors(s, l):
83 print [n for n in l if n in s]
84
85 def test_lazyancestors():
86 # Empty revs
87 s = genlazyancestors([])
88 printlazyancestors(s, [3, 0, -1])
89
90 # Standard example
91 s = genlazyancestors([11, 13])
92 printlazyancestors(s, [11, 13, 7, 9, 8, 3, 6, 4, 1, -1, 0])
93
94 # Including revs
95 s = genlazyancestors([11, 13], inclusive=True)
96 printlazyancestors(s, [11, 13, 7, 9, 8, 3, 6, 4, 1, -1, 0])
97
98 # Test with stoprev
99 s = genlazyancestors([11, 13], stoprev=6)
100 printlazyancestors(s, [11, 13, 7, 9, 8, 3, 6, 4, 1, -1, 0])
101 s = genlazyancestors([11, 13], stoprev=6, inclusive=True)
102 printlazyancestors(s, [11, 13, 7, 9, 8, 3, 6, 4, 1, -1, 0])
103
73 104 if __name__ == '__main__':
74 105 test_missingancestors()
106 test_lazyancestors()
@@ -1,36 +1,46 b''
1 1 % ancestors of [] and not of [1]
2 2 []
3 3 % ancestors of [] and not of []
4 4 []
5 5 % ancestors of [12] and not of []
6 6 [0, 1, 2, 4, 6, 7, 9, 12]
7 7 % ancestors of [0] and not of [0]
8 8 []
9 9 % ancestors of [4, 5, 6] and not of [6, 5, 4]
10 10 []
11 11 % ancestors of [-1] and not of [12]
12 12 []
13 13 % ancestors of [12] and not of [-1]
14 14 [0, 1, 2, 4, 6, 7, 9, 12]
15 15 % ancestors of [12] and not of [9]
16 16 [12]
17 17 % ancestors of [9] and not of [12]
18 18 []
19 19 % ancestors of [12, 9] and not of [7]
20 20 [6, 9, 12]
21 21 % ancestors of [7, 6] and not of [12]
22 22 []
23 23 % ancestors of [10] and not of [11, 12]
24 24 [5, 10]
25 25 % ancestors of [11] and not of [10]
26 26 [3, 7, 11]
27 27 % ancestors of [11] and not of [10, 12]
28 28 [3, 11]
29 29 % ancestors of [12] and not of [10]
30 30 [6, 7, 9, 12]
31 31 % ancestors of [12] and not of [11]
32 32 [6, 9, 12]
33 33 % ancestors of [10, 11, 12] and not of [13]
34 34 [0, 1, 2, 3, 4, 5, 6, 7, 9, 10, 11, 12]
35 35 % ancestors of [13] and not of [10, 11, 12]
36 36 [8, 13]
37 % lazy ancestor set for [], stoprev = 0, inclusive = False
38 []
39 % lazy ancestor set for [11, 13], stoprev = 0, inclusive = False
40 [7, 8, 3, 4, 1, 0]
41 % lazy ancestor set for [11, 13], stoprev = 0, inclusive = True
42 [11, 13, 7, 8, 3, 4, 1, 0]
43 % lazy ancestor set for [11, 13], stoprev = 6, inclusive = False
44 [7, 8]
45 % lazy ancestor set for [11, 13], stoprev = 6, inclusive = True
46 [11, 13, 7, 8]
General Comments 0
You need to be logged in to leave comments. Login now