File: intervaltree.py

package info (click to toggle)
python-sqt 0.8.0-9
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 824 kB
  • sloc: python: 5,964; sh: 38; makefile: 10
file content (166 lines) | stat: -rw-r--r-- 3,914 bytes parent folder | download | duplicates (4)
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
import sys

__author__ = "Johannes Koester"

class Interval:
	""" Container for an interval with start and end point. """
	def __init__(self, start, end):
		self.start = start
		self.end = end

	def is_overlap(self, other):
		""" Return True if this interval overlaps with the given interval. """
		return (self.start - other.end <= 0 and self.end - other.start >= 0) or (self.end - other.start >= 0 and self.start - other.end <= 0)
	
	def __repr__(self):
		return "({},{})".format(self.start, self.end)

class IntervalNode:
	"""
	Node in the IntervalTree.
	"""
	def __init__(self, interval, obj, tree):
		self.interval = interval
		self.obj = obj
		self.right = None
		self.left = None
		self.maxend = self.interval.end
		self.height = 0
		self.tree = tree

	def recalc_maxend(self):
		self.maxend = self.interval.end
		if child in (self.left, self.right):
			if child:
				self.maxend = max(child.maxend, self.maxend)

	def __repr__(self):
		return str(self.interval)

class IntervalTree:
	"""
	Interval tree as proposed by Cormen et al.
	"""
	def __init__(self):
		self.root = None

	def insert(self, *args, obj = None):
		"""
		Insert a new interval into the tree. *args can be a start and endpoint given as ints, or a single object with the attributes start and end.
		"""
		node = self._convert_args(*args, obj = obj, node = True)
		self._insert(self.root, node)
		# balance the tree to avoid degeneration
		self._rotate()

	def find(self, *args):
		"""
		Find all intervals in the tree that intersect with the given interval. *args can be a start and endpoint given as ints, or a single object with the attributes start and end.
		"""
		for interval in self._find(self.root, self._convert_args(*args)):
			yield interval

	def _insert(self, root, node):
		if not root:
			self.root = node
			return
		maxend = None
		if node.interval.start < root.interval.start:
			if root.left:
				maxend = self._insert(root.left, node)
			else:
				root.left = node
		else:
			if root.right:
				maxend = self._insert(root.right, node)
			else:
				root.right = node

		root.height += 1		
		
		if not maxend:
			maxend = node.interval.end

		root.maxend = max(maxend, root.maxend)
		return root.maxend

	def _find(self, root, interval):
		if not root:
			return

		if interval.start > root.maxend:
			return

		if root.interval.is_overlap(interval):
			yield root.interval

		if interval.start < root.interval.start:
			for i in self._find(root.left, interval):
				yield i

		elif not interval.end < root.interval.start:
			for i in self._find(root.right, interval):
				yield i

	def _rotate(self):
		if self.root.right and self.root.left:
			heightdiff = self.root.right.height - self.root.left.height
			if heightdiff > 1:
				# perform a left rotation
				p = self.root
				q = self.root.right
				self.root = q
				p.right = q.left
				q.left = p
				q.height += 1
				p.height -= 1

				p.recalc_maxend()
				q.recalc_maxend()
			elif heightdiff < -1:
				# perform a right rotation
				p = self.root
				q = self.root.left
				self.root = q
				p.left = q.right
				q.right = p
				q.height += 1
				p.height -= 1
				
				p.recalc_maxend()
				q.recalc_maxend()

			
			
			
	
	def _inorder(self, root):
		if not root:
			return

		for i in self._inorder(root.left):
			yield i

		yield root

		for i in self._inorder(root.right):
			yield i

	def __iter__(self):
		for i in self._inorder(self.root):
			yield i

	def __repr__(self):
		return ", ".join(map(str, self))
		
	def _convert_args(self, *args, obj = None, node = False):
		""" Convert args into an interval. *args can be a start and endpoint given as ints, or a single object with the attributes start and end. """
		if len(args) == 2:
			interval = Interval(*args)
		else:
			interval =  Interval(args[0].start, args[0].end)
		if node:
			if not obj and len(args) == 1:
				obj = args[0]
			return IntervalNode(interval, obj, self)
		return interval