This is a simple, efficient implementation of intervals and interval
trees for Common Lisp. This is useful if you wish to have a lot of
start-end
pairs, and find all overlapping intervals at a point, or
within an interval:
(defvar *tree* (interval:make-tree))
(interval:insert *tree* '(1 . 1))
(interval:insert *tree* '(1 . 3))
(interval:insert *tree* '(2 . 4))
(interval:insert *tree* '(5 . 9))
(interval:find *tree* '(1 . 1)) ;; => #<1-1>
(interval:find *tree* '(1 . 2)) ;; => NIL
(interval:find-all *tree* '(1 . 1))
;; => (#<1-1> #<1-3>)
;;; Equivalent to:
(interval:find-all *tree* 1)
;; => (#<1-1> #<1-3>)
(interval:find-all *tree* '(1 . 2))
;; => (#<1-1> #<1-3> #<2-4>)
The implementation for this is based on an augmented AA tree as discussed in the following:
A short summary of this method can (at the time of writing) be found on wikipedia.
Plain numeric intervals are not terribly useful on their own. Thus,
intervals are represented in the structure interval:interval
, which
you can extend as you please:
(defstruct (my-interval (:include interval:interval))
my-data)
(defun my-interval< (i1 i2)
(char< (interval:interval-start i1) (interval:interval-start i2)))
(defun my-interval= (i1 i2)
(and (char= (interval:interval-start i1) (interval:interval-start i2))
(char= (interval:interval-end i1) (interval:interval-end i2))))
(setf *mytree* (interval:make-tree :interval-before-p #'my-interval<
:interval-equal-p #'my-interval=
:value-before-p #'char<=))
(interval:insert *mytree* (make-my-interval :start #\a :end #\c))
(interval:insert *mytree* (make-my-interval :start #\b :end #\m))
(interval:find *mytree* '(#\a . #\c))
;; => (#<a-c>)
(interval:find-all *mytree* #\b)
;; => (#<a-c> #<b-m>)
(setf (my-interval-my-data (interval:find *mytree* '(#\a . #\c)))
"something useful for later retrieval")
(mapcar #'my-interval-my-data
(interval:find-all *mytree* #\b))
;; => ("something useful for later retrieval" NIL)
As you can see in this example, we've made a new interval which compares alphabetically, and where we can attach data.
One thing of note is that INTERVAL:INTERVAL-START
and
INTERVAL:INTERVAL-END
are used instead of MY-INTERVAL-START
and
MY-INTERVAL-END
. This is not strictly necessary; however, you will
not be able to search or delete based on "plain" intervals, or use the
(START . END)
or single-value syntax.
Delete an interval that is interval-equal-p to INTERVAL
from TREE
.
INTERVAL
may be any type of interval, or a cons in the form (START
. END)
.
Find a specific interval that is :interval-equal-p to INTERVAL
in
TREE
and return it, or NIL.
INTERVAL
may be any type of interval, or a cons in the form (START
. END)
.
Find all intervals intersecting INTERVAL
in TREE
. INTERVAL
does
not have to be matched exactly in TREE
.
Alternatively, INTERVAL
may be either a cons of (START . END)
, or
a single value, which will be used as both the start and the
end (effectively finding intervals at a point).
Insert INTERVAL
into TREE
, if an equivalent interval (by
interval-equal-p) is not already in TREE
, returning INTERVAL
.
Otherwise, return the existing interval.
INSERTED-P
is T
if there was no existing interval, or NIL
if
the existing interval was returned.
INTERVAL
may alternatively be a cons in the form (START . END)
.
In this case, a simple interval is created and inserted.
Return the END
value of the interval.
Return the START
value of the interval.
A simple example (also used in tests) which compares interval starts
numerically by #'<
.
A simple example (also used in tests) which compares interval equality
numerically by #'=
.
Returns a simple interval with specified START
and END
.
Make an interval tree given the specified functions. By default, these are simple numeric comparisons.
INTERVAL-BEFORE-P
should take two intervals, A
and B
, and test
whether the start of A
comes before the start of B
. This is
used solely for tree placement. A
and B
might be equal, but the
test may be a less-than-not-equal test.
INTERVAL-EQUAL-P
should take two intervals, A
and B
, and test
whether they are equal (e.g., the starts and ends are the same). This
should not be an identity test (i.e., not EQ
).
VALUE-BEFORE-P
should take two values, A
and B
, which are
used as start or end values, and compare whether A
comes before B
.
For closed intervals, use a less-than-or-equal function. For open
intervals, use a less-than function. Half-open intervals are currently
not supported.
Return the function used to compare intervals. It should return
whether one interval START
comes before another interval START
.
Return a tree dumped into a list form. This is currently only useful for testing.
Return the function used to compare intervals. It should return
whether one interval is equal to another. It should not be an identity
comparison (i.e., not EQ
).
Tests TREE
for AA-tree and interval-tree invariants, to make sure the
tree is valid. It returns T
, or raises an error if the invariants are
not met.
Return the function used to compare values (i.e., start and end). For closed intervals, use a less-than-or-equal function. For open intervals, use a less-than function. Half-open intervals are currently not supported.