/usr/lib/python2.7/dist-packages/intervaltree/node.py is in python-intervaltree 2.1.0-2.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
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 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 | """
intervaltree: A mutable, self-balancing interval tree for Python 2 and 3.
Queries may be by point, by range overlap, or by range envelopment.
Core logic: internal tree nodes.
Copyright 2013-2015 Chaim-Leib Halbert
Modifications Copyright 2014 Konstantin Tretyakov
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
from operator import attrgetter
from math import floor, log
def l2(num):
"""
log base 2
:rtype real
"""
return log(num, 2)
class Node(object):
def __init__(self,
x_center=None,
s_center=set(),
left_node=None,
right_node=None):
self.x_center = x_center
self.s_center = set(s_center)
self.left_node = left_node
self.right_node = right_node
self.depth = 0 # will be set when rotated
self.balance = 0 # ditto
self.rotate()
@classmethod
def from_interval(cls, interval):
"""
:rtype : Node
"""
center = interval.begin
return Node(center, [interval])
@classmethod
def from_intervals(cls, intervals):
"""
:rtype : Node
"""
if not intervals:
return None
node = Node()
node = node.init_from_sorted(sorted(intervals))
return node
def init_from_sorted(self, intervals):
if not intervals:
return None
center_iv = intervals[len(intervals) // 2]
self.x_center = center_iv.begin
self.s_center = set()
s_left = []
s_right = []
for k in intervals:
if k.end <= self.x_center:
s_left.append(k)
elif k.begin > self.x_center:
s_right.append(k)
else:
self.s_center.add(k)
self.left_node = Node.from_intervals(s_left)
self.right_node = Node.from_intervals(s_right)
return self.rotate()
def center_hit(self, interval):
"""Returns whether interval overlaps self.x_center."""
return interval.contains_point(self.x_center)
def hit_branch(self, interval):
"""
Assuming not center_hit(interval), return which branch
(left=0, right=1) interval is in.
"""
return interval.begin > self.x_center
def refresh_balance(self):
"""
Recalculate self.balance and self.depth based on child node values.
"""
left_depth = self.left_node.depth if self.left_node else 0
right_depth = self.right_node.depth if self.right_node else 0
self.depth = 1 + max(left_depth, right_depth)
self.balance = right_depth - left_depth
def compute_depth(self):
"""
Recursively computes true depth of the subtree. Should only
be needed for debugging. Unless something is wrong, the
depth field should reflect the correct depth of the subtree.
"""
left_depth = self.left_node.compute_depth() if self.left_node else 0
right_depth = self.right_node.compute_depth() if self.right_node else 0
return 1 + max(left_depth, right_depth)
def rotate(self):
"""
Does rotating, if necessary, to balance this node, and
returns the new top node.
"""
self.refresh_balance()
if abs(self.balance) < 2:
return self
# balance > 0 is the heavy side
my_heavy = self.balance > 0
child_heavy = self[my_heavy].balance > 0
if my_heavy == child_heavy or self[my_heavy].balance == 0:
## Heavy sides same
# self save
# save -> 1 self
# 1
#
## Heavy side balanced
# self save save
# save -> 1 self -> 1 self.rot()
# 1 2 2
return self.srotate()
else:
return self.drotate()
def srotate(self):
"""Single rotation. Assumes that balance is +-2."""
# self save save
# save 3 -> 1 self -> 1 self.rot()
# 1 2 2 3
#
# self save save
# 3 save -> self 1 -> self.rot() 1
# 2 1 3 2
#assert(self.balance != 0)
heavy = self.balance > 0
light = not heavy
save = self[heavy]
#print("srotate: bal={},{}".format(self.balance, save.balance))
#self.print_structure()
self[heavy] = save[light] # 2
#assert(save[light])
save[light] = self.rotate() # Needed to ensure the 2 and 3 are balanced under new subnode
# Some intervals may overlap both self.x_center and save.x_center
# Promote those to the new tip of the tree
promotees = [iv for iv in save[light].s_center if save.center_hit(iv)]
if promotees:
for iv in promotees:
save[light] = save[light].remove(iv) # may trigger pruning
# TODO: Use Node.add() here, to simplify future balancing improvements.
# For now, this is the same as augmenting save.s_center, but that may
# change.
save.s_center.update(promotees)
save.refresh_balance()
return save
def drotate(self):
# First rotation
my_heavy = self.balance > 0
self[my_heavy] = self[my_heavy].srotate()
self.refresh_balance()
# Second rotation
result = self.srotate()
return result
def add(self, interval):
"""
Returns self after adding the interval and balancing.
"""
if self.center_hit(interval):
self.s_center.add(interval)
return self
else:
direction = self.hit_branch(interval)
if not self[direction]:
self[direction] = Node.from_interval(interval)
self.refresh_balance()
return self
else:
self[direction] = self[direction].add(interval)
return self.rotate()
def remove(self, interval):
"""
Returns self after removing the interval and balancing.
If interval is not present, raise ValueError.
"""
# since this is a list, called methods can set this to [1],
# making it true
done = []
return self.remove_interval_helper(interval, done, should_raise_error=True)
def discard(self, interval):
"""
Returns self after removing interval and balancing.
If interval is not present, do nothing.
"""
done = []
return self.remove_interval_helper(interval, done, should_raise_error=False)
def remove_interval_helper(self, interval, done, should_raise_error):
"""
Returns self after removing interval and balancing.
If interval doesn't exist, raise ValueError.
This method may set done to [1] to tell all callers that
rebalancing has completed.
See Eternally Confuzzled's jsw_remove_r function (lines 1-32)
in his AVL tree article for reference.
"""
#trace = interval.begin == 347 and interval.end == 353
#if trace: print('\nRemoving from {} interval {}'.format(
# self.x_center, interval))
if self.center_hit(interval):
#if trace: print('Hit at {}'.format(self.x_center))
if not should_raise_error and interval not in self.s_center:
done.append(1)
#if trace: print('Doing nothing.')
return self
try:
# raises error if interval not present - this is
# desired.
self.s_center.remove(interval)
except:
self.print_structure()
raise KeyError(interval)
if self.s_center: # keep this node
done.append(1) # no rebalancing necessary
#if trace: print('Removed, no rebalancing.')
return self
# If we reach here, no intervals are left in self.s_center.
# So, prune self.
return self.prune()
else: # interval not in s_center
direction = self.hit_branch(interval)
if not self[direction]:
if should_raise_error:
raise ValueError
done.append(1)
return self
#if trace:
# print('Descending to {} branch'.format(
# ['left', 'right'][direction]
# ))
self[direction] = self[direction].remove_interval_helper(interval, done, should_raise_error)
# Clean up
if not done:
#if trace:
# print('Rotating {}'.format(self.x_center))
# self.print_structure()
return self.rotate()
return self
def search_overlap(self, point_list):
"""
Returns all intervals that overlap the point_list.
"""
result = set()
for j in point_list:
self.search_point(j, result)
return result
def search_point(self, point, result):
"""
Returns all intervals that contain point.
"""
for k in self.s_center:
if k.begin <= point < k.end:
result.add(k)
if point < self.x_center and self[0]:
return self[0].search_point(point, result)
elif point > self.x_center and self[1]:
return self[1].search_point(point, result)
return result
def prune(self):
"""
On a subtree where the root node's s_center is empty,
return a new subtree with no empty s_centers.
"""
if not self[0] or not self[1]: # if I have an empty branch
direction = not self[0] # graft the other branch here
#if trace:
# print('Grafting {} branch'.format(
# 'right' if direction else 'left'))
result = self[direction]
#if result: result.verify()
return result
else:
# Replace the root node with the greatest predecessor.
heir, self[0] = self[0].pop_greatest_child()
#if trace:
# print('Replacing {} with {}.'.format(
# self.x_center, heir.x_center
# ))
# print('Removed greatest predecessor:')
# self.print_structure()
#if self[0]: self[0].verify()
#if self[1]: self[1].verify()
# Set up the heir as the new root node
(heir[0], heir[1]) = (self[0], self[1])
#if trace: print('Setting up the heir:')
#if trace: heir.print_structure()
# popping the predecessor may have unbalanced this node;
# fix it
heir.refresh_balance()
heir = heir.rotate()
#heir.verify()
#if trace: print('Rotated the heir:')
#if trace: heir.print_structure()
return heir
def pop_greatest_child(self):
"""
Used when pruning a node with both a left and a right branch.
Returns (greatest_child, node), where:
* greatest_child is a new node to replace the removed node.
* node is the subtree after:
- removing the greatest child
- balancing
- moving overlapping nodes into greatest_child
Assumes that self.s_center is not empty.
See Eternally Confuzzled's jsw_remove_r function (lines 34-54)
in his AVL tree article for reference.
"""
#print('Popping from {}'.format(self.x_center))
if not self.right_node: # This node is the greatest child.
# To reduce the chances of an overlap with a parent, return
# a child node containing the smallest possible number of
# intervals, as close as possible to the maximum bound.
ivs = sorted(self.s_center, key=attrgetter('end', 'begin'))
max_iv = ivs.pop()
new_x_center = self.x_center
while ivs:
next_max_iv = ivs.pop()
if next_max_iv.end == max_iv.end: continue
new_x_center = max(new_x_center, next_max_iv.end)
def get_new_s_center():
for iv in self.s_center:
if iv.contains_point(new_x_center): yield iv
# Create a new node with the largest x_center possible.
child = Node.from_intervals(get_new_s_center())
# [iv for iv in self.s_center if iv.contains_point(child_x_center)]
# )
child.x_center = new_x_center
self.s_center -= child.s_center
#print('Pop hit! Returning child = {}'.format(
# child.print_structure(tostring=True)
# ))
#assert not child[0]
#assert not child[1]
if self.s_center:
#print(' and returning newnode = {}'.format( self ))
#self.verify()
return child, self
else:
#print(' and returning newnode = {}'.format( self[0] ))
#if self[0]: self[0].verify()
return child, self[0] # Rotate left child up
else:
#print('Pop descent to {}'.format(self[1].x_center))
(greatest_child, self[1]) = self[1].pop_greatest_child()
self.refresh_balance()
new_self = self.rotate()
# Move any overlaps into greatest_child
for iv in set(new_self.s_center):
if iv.contains_point(greatest_child.x_center):
new_self.s_center.remove(iv)
greatest_child.add(iv)
#print('Pop Returning child = {}'.format(
# greatest_child.print_structure(tostring=True)
# ))
if new_self.s_center:
#print('and returning newnode = {}'.format(
# new_self.print_structure(tostring=True)
# ))
#new_self.verify()
return greatest_child, new_self
else:
new_self = new_self.prune()
#print('and returning prune = {}'.format(
# new_self.print_structure(tostring=True)
# ))
#if new_self: new_self.verify()
return greatest_child, new_self
def contains_point(self, p):
"""
Returns whether this node or a child overlaps p.
"""
for iv in self.s_center:
if iv.contains_point(p):
return True
branch = self[p > self.x_center]
return branch and branch.contains_point(p)
def all_children(self):
return self.all_children_helper(set())
def all_children_helper(self, result):
result.update(self.s_center)
if self[0]:
self[0].all_children_helper(result)
if self[1]:
self[1].all_children_helper(result)
return result
def verify(self, parents=set()):
"""
## DEBUG ONLY ##
Recursively ensures that the invariants of an interval subtree
hold.
"""
assert(isinstance(self.s_center, set))
bal = self.balance
assert abs(bal) < 2, \
"Error: Rotation should have happened, but didn't! \n{}".format(
self.print_structure(tostring=True)
)
self.refresh_balance()
assert bal == self.balance, \
"Error: self.balance not set correctly! \n{}".format(
self.print_structure(tostring=True)
)
assert self.s_center, \
"Error: s_center is empty! \n{}".format(
self.print_structure(tostring=True)
)
for iv in self.s_center:
assert hasattr(iv, 'begin')
assert hasattr(iv, 'end')
assert iv.begin < iv.end
assert iv.overlaps(self.x_center)
for parent in sorted(parents):
assert not iv.contains_point(parent), \
"Error: Overlaps ancestor ({})! \n{}\n\n{}".format(
parent, iv, self.print_structure(tostring=True)
)
if self[0]:
assert self[0].x_center < self.x_center, \
"Error: Out-of-order left child! {}".format(self.x_center)
self[0].verify(parents.union([self.x_center]))
if self[1]:
assert self[1].x_center > self.x_center, \
"Error: Out-of-order right child! {}".format(self.x_center)
self[1].verify(parents.union([self.x_center]))
def __getitem__(self, index):
"""
Returns the left child if input is equivalent to False, or
the right side otherwise.
"""
if index:
return self.right_node
else:
return self.left_node
def __setitem__(self, key, value):
"""Sets the left (0) or right (1) child."""
if key:
self.right_node = value
else:
self.left_node = value
def __str__(self):
"""
Shows info about this node.
Since Nodes are internal data structures not revealed to the
user, I'm not bothering to make this copy-paste-executable as a
constructor.
"""
return "Node<{0}, depth={1}, balance={2}>".format(
self.x_center,
self.depth,
self.balance
)
#fieldcount = 'c_count,has_l,has_r = <{}, {}, {}>'.format(
# len(self.s_center),
# bool(self.left_node),
# bool(self.right_node)
#)
#fields = [self.x_center, self.balance, fieldcount]
#return "Node({}, b={}, {})".format(*fields)
def count_nodes(self):
"""
Count the number of Nodes in this subtree.
:rtype: int
"""
count = 1
if self.left_node:
count += self.left_node.count_nodes()
if self.right_node:
count += self.right_node.count_nodes()
return count
def depth_score(self, n, m):
"""
Calculates flaws in balancing the tree.
:param n: size of tree
:param m: number of Nodes in tree
:rtype: real
"""
if n == 0:
return 0.0
# dopt is the optimal maximum depth of the tree
dopt = 1 + int(floor(l2(m)))
f = 1 / float(1 + n - dopt)
return f * self.depth_score_helper(1, dopt)
def depth_score_helper(self, d, dopt):
"""
Gets a weighted count of the number of Intervals deeper than dopt.
:param d: current depth, starting from 0
:param dopt: optimal maximum depth of a leaf Node
:rtype: real
"""
# di is how may levels deeper than optimal d is
di = d - dopt
if di > 0:
count = di * len(self.s_center)
else:
count = 0
if self.right_node:
count += self.right_node.depth_score_helper(d + 1, dopt)
if self.left_node:
count += self.left_node.depth_score_helper(d + 1, dopt)
return count
def print_structure(self, indent=0, tostring=False):
"""
For debugging.
"""
nl = '\n'
sp = indent * ' '
rlist = [str(self) + nl]
if self.s_center:
for iv in sorted(self.s_center):
rlist.append(sp + ' ' + repr(iv) + nl)
if self.left_node:
rlist.append(sp + '<: ') # no CR
rlist.append(self.left_node.print_structure(indent + 1, True))
if self.right_node:
rlist.append(sp + '>: ') # no CR
rlist.append(self.right_node.print_structure(indent + 1, True))
result = ''.join(rlist)
if tostring:
return result
else:
print(result)
|