View Source Interval (Interval v0.1.3)

An interval represents the points between two endpoints.

The interval can be empty. The empty interval is never contained in any other interval, and contains itself no points.

It can be left and/or right unbounded, in which case it contains all points in the unbounded direction. A fully unbounded interval contains all other intervals, except the empty interval.

interval-notation

Interval Notation

Throughout the documentation and comments, you'll see a notation for writing about intervals. As this library is inspired by the functionality in PostgreSQL's range types, we align ourselves with it's notation choice and borrow it (https://www.postgresql.org/docs/current/rangetypes.html) with the exception that we write the normalised empty interval as (0,0).

This notation is also described in ISO 31-11.

[left-inclusive, right-inclusive]
(left-exclusive, right-exclusive)
[left-inclusive, right-exclusive)
(left-exclusive, right-inclusive]
(0,0)

An unbounded interval is written by omitting the bound type and point:

,right-exclusive)
[left-inclusive,

When specifying bound types we sometimes leave the point out and just write the left and right bounds:

[]
()
(]
[)
(
)
[
]

types-of-interval

Types of Interval

This library ships with a few different types of intervals. The built-in intervals are intervals for

However, you can quite easily implement an interval using your own point types, by implementing the Interval.Point protocol.

The Interval.Point protocols defines a few handfuls of functions you'll need to define on your struct to be able to use it as a point in the interval.

An obvious usecase for this would be to implement an interval that works with the https://hexdocs.pm/decimal library.

discrete-vs-continuous-intervals

Discrete vs Continuous intervals

Depending on the type of point used, an interval is either said to be discrete or continuous.

A discrete interval represents a set of finite points (like integers or dates). A continuous can be said to represent the infinite number of points between two endpoints (like float and datetime).

With discrete points, it is possible to define what the next and previous point is, and we normalise these intervals to the bound type [).
The only exception is the empty interval, which is still represented as the exclusive bounded zero point. For integers that would be (0,0).

The distinction between discrete and continuous intervals is important because the two behave slightly differently in some of the libraries functions. E.g. A discrete interval is adjacent to another discrete interval, if there is no points between the two interval.
Contrast this to continuous intervals like real numbers where there is always an infinite number of real numbers between two distinct real numbers, and so continuous intervals are only said to be adjacent to each other if they include the same point, and one point is inclusive where the other is exclusive.

Where relevant, the function documentation will mention the differences between discrete and continuous intervals.

create-an-interval

Create an Interval

See new/1.

normalization

Normalization

When creating an interval through new/1, it will get normalized so that intervals that represents the same exact same points, are also represented in the same way in the struct. This allows you to compare two intervals for equality by using == (and using pattern matching).

It is therefore not recommended to modify an Interval struct directly, but instead do so by using one of the functions that modify the interval.

An interval is said to be empty if it spans zero points. We represent the empty interval as an exclusive interval in between two "zero points".
Any empty interval will be normalized to this (0,0) interval. see Interval.Point.zero/1 for details on what a zero point is.

Link to this section Summary

Types

t()

Shorthand for t:t(any())

The Interval struct, representing all points between two endpoints.

Functions

Is the interval a adjacent to b, to the left of b.

Is the interval a adjacent to b, to the right of b.

Does a contain the point x?

Does a contain b?

Is the interval empty?

Is the interval left-inclusive?

Is the interval right-inclusive?

Compute the intersection between a and b.

Create a new unbounded interval.

Create a new interval.

Normalize an Interval struct

Does a overlap with b?

Partition an interval a into 3 intervals using x

Create a new Interval containing a single point.

Return the "size" of the interval. The returned value depends on the Point implementation used.

Is a strictly left of b.

Is a strictly right of b.

Check if the interval is left-unbounded.

Check if the interval is right-unbounded.

Computes the union of a and b.

Link to this section Types

@type bound() :: :inclusive | :exclusive
@type t() :: t(any())

Shorthand for t:t(any())

@type t(point) :: %Interval{
  left: :unbounded | {bound(), point},
  right: :unbounded | {bound(), point}
}

The Interval struct, representing all points between two endpoints.

The struct has two fields: left and right, representing the left (lower) and right (upper) points in the interval.

Link to this section Functions

@spec adjacent_left_of?(t(), t()) :: boolean()

Is the interval a adjacent to b, to the left of b.

a is adjacent to b left of b, if a and b do not overlap, and there are no points between a.right and b.left.

a: [---)
b:     [---)
r: true

a: [---]
b:     [---]
r: false (overlaps)

a: (---)
b:        (---)
r: false (points exist between a.right and b.left)

examples

Examples

iex> adjacent_left_of?(new(left: 1, right: 2), new(left: 2, right: 3))
true

iex> adjacent_left_of?(new(left: 1, right: 3), new(left: 2, right: 4))
false

iex> adjacent_left_of?(new(left: 3, right: 4), new(left: 1, right: 2))
false

iex> adjacent_left_of?(new(right: 2, bounds: "[]"), new(left: 3))
true
Link to this function

adjacent_right_of?(a, b)

View Source
@spec adjacent_right_of?(t(), t()) :: boolean()

Is the interval a adjacent to b, to the right of b.

a is adjacent to b right of b, if a and b do not overlap, and there are no points between a.left and b.right.

a:     [---)
b: [---)
r: true

a:     [---)
b: [---]
r: false (overlaps)

a:        (---)
b: (---)
r: false (points exist between a.left and b.right)

examples

Examples

iex> adjacent_right_of?(new(left: 2, right: 3), new(left: 1, right: 2))
true

iex> adjacent_right_of?(new(left: 1, right: 3), new(left: 2, right: 4))
false

iex> adjacent_right_of?(new(left: 1, right: 2), new(left: 3, right: 4))
false

iex> adjacent_right_of?(new(left: 3), new(right: 2, bounds: "]"))
true
Link to this function

contains_point?(a, x)

View Source (since 0.1.4)

Does a contain the point x?

examples

Examples

iex> contains_point?(new(left: 1, right: 2), 0)
false

iex> contains_point?(new(left: 1, right: 2), 1)
true
@spec contains?(t(), t()) :: boolean()

Does a contain b?

a contains b of all points in b is also in a.

For an interval a to contain an interval b, all points in b must be contained in a:

a: [-------]
b:   [---]
r: true

a: [---]
b: [---]
r: true

a: [---]
b: (---)
r: true

a: (---)
b: [---]
r: false

a:   [---]
b: [-------]
r: false

This means that a.left is less than b.left (or unbounded), and a.right is greater than b.right (or unbounded)

If a and b's point match, then b is "in" a if a and b share bound types.

E.g. if a.left and b.left matches, then a contains b if both a and b's left is inclusive or exclusive.

If either of b endpoints are unbounded, then a only contains b if the corresponding endpoint in a is also unbounded.

examples

Examples

iex> contains?(new(left: 1, right: 2), new(left: 1, right: 2))
true

iex> contains?(new(left: 1, right: 3), new(left: 2, right: 3))
true

iex> contains?(new(left: 2, right: 3), new(left: 1, right: 4))
false

iex> contains?(new(left: 1, right: 3), new(left: 1, right: 2))
true

iex> contains?(new(left: 1, right: 2, bounds: "()"), new(left: 1, right: 3))
false

iex> contains?(new(right: 1), new(left: 0, right: 1))
true

Is the interval empty?

An empty interval is an interval that represents no points. Any interval containing no points is considered empty.

An unbounded interval is never empty.

For continuous points, the interval is empty when the left and right points are identical, and the point is not included in the interval.

For discrete points, the interval is empty when the left and right point isn't inclusive, and there are no points between the left and right point.

examples

Examples

iex> empty?(new(left: 0, right: 0))
true

iex> empty?(single(1.0))
false

iex> empty?(new(left: 1, right: 2))
false
Link to this function

from_endpoints(left, right)

View Source
Link to this function

inclusive_left?(interval)

View Source

Is the interval left-inclusive?

The interval is left-inclusive if the left endpoint value is included in the interval.

Note

Discrete intervals (like integers and dates) are always normalized to be left-inclusive right-exclusive ([)) which this function reflects.

iex> inclusive_left?(new(left: 1.0, right: 2.0, bounds: "[]"))
true

iex> inclusive_left?(new(left: 1.0, right: 2.0, bounds: "[)"))
true

iex> inclusive_left?(new(left: 1.0, right: 2.0, bounds: "()"))
false
Link to this function

inclusive_right?(interval)

View Source

Is the interval right-inclusive?

The interval is right-inclusive if the right endpoint value is included in the interval.

Note

Discrete intervals (like integers and dates) are always normalized to be left-inclusive right-exclusive ([)) which this function reflects.

iex> inclusive_right?(new(left: 1.0, right: 2.0, bounds: "[]"))
true

iex> inclusive_right?(new(left: 1.0, right: 2.0, bounds: "[)"))
false

iex> inclusive_right?(new(left: 1.0, right: 2.0, bounds: "()"))
false

Compute the intersection between a and b.

The intersection contains all of the points that are both in a and b.

If either a or b are empty, the returned interval will be empty.

a: [----]
b:    [----]
r:    [-]

a: (----)
b:    (----)
r:    (-)

a: [----)
b:    [----)
r:    [-)

examples

Examples:

Discrete:

a: [----)
b:    [----)
c:    [-)
iex> intersection(new(left: 1, right: 3), new(left: 2, right: 4))
new(left: 2, right: 3)

Continuous:

a: [----)
b:    [----)
c:    [-)
iex> intersection(new(left: 1.0, right: 3.0), new(left: 2.0, right: 4.0))
new(left: 2.0, right: 3.0)

a: (----)
b:    (----)
c:    (-)
iex> intersection(
...>   new(left: 1.0, right: 3.0, bounds: "()"),
...>   new(left: 2.0, right: 4.0, bounds: "()")
...> )
new(left: 2.0, right: 3.0, bounds: "()")

a: [-----)
b:   [-------
c:   [---)
iex> intersection(new(left: 1.0, right: 3.0), new(left: 2.0))
new(left: 2.0, right: 3.0)
@spec new() :: t()

Create a new unbounded interval.

This is the exactly the same as new/1 with an empty list of options.

Create a new interval.

options

Options

  • left The left (or lower) endpoint of the interval
  • right The right (or upper) endpoint of the interval
  • bounds The bound mode to use. Defaults to "[)"

A nil (left or right) endpoint is considered unbounded.
The endpoint will also be considered unbounded if the bounds is explicitly set as unbounded.

bounds

Bounds

The bounds options contains the left and right bound mode to use. The bound can be inclusive, exclusive or unbounded.

The following valid bound values are supported:

  • "[)" left-inclusive, right-exclusive (default)
  • "(]" left-exclusive, right-inclusive
  • "[]" left-inclusive, right-inclusive
  • "()" left-exclusive, right-exclusive
  • ")" left-unbounded, right-exclusive
  • "]" left-unbounded, right-inclusive
  • "(" left-exclusive, right-unbounded
  • "[" left-inclusive, right-unbounded

examples

Examples

iex> new([])

iex> new(left: 1)

iex> new(left: 1, right: 1, bounds: "[]")

iex> new(left: 10, right: 20, bounds: "()")

Normalize an Interval struct

@spec overlaps?(t(), t()) :: boolean()

Does a overlap with b?

a overlaps with b if any point in a is also in b.

a: [---)
b:   [---)
r: true

a: [---)
b:     [---)
r: false

a: [---]
b:     [---]
r: true

a: (---)
b:     (---)
r: false

a: [---)
b:        [---)
r: false

examples

Examples

[--a--)
    [--b--)

iex> overlaps?(new(left: 1, right: 3), new(left: 2, right: 4))
true


[--a--)
      [--b--)

iex> overlaps?(new(left: 1, right: 3), new(left: 3, right: 5))
false


[--a--]
      [--b--]

iex> overlaps?(new(left: 1, right: 3), new(left: 2, right: 4))
true


(--a--)
      (--b--)

iex> overlaps?(new(left: 1, right: 3), new(left: 3, right: 5))
false


[--a--)
         [--b--)

iex> overlaps?(new(left: 1, right: 2), new(left: 3, right: 4))
false
Link to this function

partition(a, x)

View Source (since 0.1.4)
@spec partition(t(), Interval.Point.t()) :: [t()] | []

Partition an interval a into 3 intervals using x:

  • The interval with all points from a < x
  • The interval with just x
  • The interval with all points from a > x

If x is not in a this function returns an empty list.

examples

Examples

iex> partition(new(left: 1, right: 5, bounds: "[]"), 3)
[
  new(left: 1, right: 3, bounds: "[)"),
  new(left: 3, right: 3, bounds: "[]"),
  new(left: 3, right: 5, bounds: "(]")
]

iex> partition(new(left: 1, right: 5), -10)
[]

Create a new Interval containing a single point.

Link to this function

size(a, unit \\ nil)

View Source (since 0.1.3)
@spec size(t(), unit :: any()) :: any()

Return the "size" of the interval. The returned value depends on the Point implementation used.

  • If the interval is unbounded, this function returns nil.

Note

The size is implemented as right - left, ignoring inclusive/exclusive bounds. This works for discrete intervals because they are always normalized to [). The implementation is the same for continuous intervals, but here we completely ignore the bounds, so the size of (1.0, 3.0) is exactly the same as the size of [1.0, 3.0].

A second argument unit can be given to this function, which in turn is handed to the call to Interval.Point.subtract/3, to return the difference between the two points in the desired unit.

The default value for unit is Point implementation specific.

These are the default returned sizes for the built-in implementations:

for-discrete-intervals

For Discrete Intervals

For discrete point types, the size represents the number of elements the interval contains (or nil, when empty).

I.e. for Date the size is the number of Date structs the interval can be said to "contain" (aka. number of days)

examples

Examples

iex> size(new(left: 1, right: 1, bounds: "[]"))
1

iex> size(new(left: 1, right: 3, bounds: "[)"))
2

# Note that this interval will be normalized to an empty (0,0) interval
# but the math is still the same: `right - left` 
iex> size(new(left: 1, right: 2, bounds: "()"))
0

for-continuous-intervals

For Continuous Intervals

For continuous intervals, the size is reported as the difference between the left and right points.

examples-1

Examples

# The size of the interval `[1.0, 5.0)` is also 4:
iex> size(new(left: 1.0, right: 5.0, bounds: "[)"))
4.0

# And likewise, so is the size of `[1.0, 5.0]` (note the bound change)
iex> size(new(left: 1.0, right: 5.0, bounds: "[]"))
4.0

# Exactly one point contained in  this continuous interval,
# so technically not empty, but it also has zero  size.
iex> size(new(left: 1.0, right: 1.0, bounds: "[]"))
0.0

# Empty continuous interval
iex> size(new(left: 1.0, right: 1.0, bounds: "()"))
0.0
@spec strictly_left_of?(t(), t()) :: boolean()

Is a strictly left of b.

a is strictly left of b if no point in a is in b, and all points in a is left (<) of all points in b.

examples

Examples

a: [---)
b:     [---)
r: true

a: [---)
b:        [---)
r: true

a: [---)
b:    [---)
r: false (overlaps)

iex> strictly_left_of?(new(left: 1, right: 2), new(left: 3, right: 4))
true

iex> strictly_left_of?(new(left: 1, right: 3), new(left: 2, right: 4))
false

iex> strictly_left_of?(new(left: 3, right: 4), new(left: 1, right: 2))
false
Link to this function

strictly_right_of?(a, b)

View Source
@spec strictly_right_of?(t(), t()) :: boolean()

Is a strictly right of b.

a is strictly right of b if no point in a is in b, and all points in a is right (>) of all points in b.

examples

Examples

a:     [---)
b: [---)
r: true

a:        [---)
b: [---)
r: true

a:    [---)
b: [---)
r: false (overlaps)

iex> strictly_right_of?(new(left: 1, right: 2), new(left: 3, right: 4))
false

iex> strictly_right_of?(new(left: 1, right: 3), new(left: 2, right: 4))
false

iex> strictly_right_of?(new(left: 3, right: 4), new(left: 1, right: 2))
true
Link to this function

unbounded_left?(interval)

View Source

Check if the interval is left-unbounded.

The interval is left-unbounded if all points left of the right bound is included in this interval.

examples

Examples

iex> unbounded_left?(new())
true

iex> unbounded_left?(new(right: 2))
true

iex> unbounded_left?(new(left: 1, right: 2))
false
Link to this function

unbounded_right?(interval)

View Source

Check if the interval is right-unbounded.

The interval is right-unbounded if all points right of the left bound is included in this interval.

examples

Examples

iex> unbounded_right?(new(right: 1))
false

iex> unbounded_right?(new())
true

iex> unbounded_right?(new(left: 1))
true

Computes the union of a and b.

The union contains all of the points that are either in a or b.

If either a or b are empty, the returned interval will be empty.

a: [---)
b:   [---)
r: [-----)

examples

Examples

[--A--)
    [--B--)
[----C----)

iex> union(new(left: 1, right: 3), new(left: 2, right: 4))
new(left: 1, right: 4)


[-A-)
    [-B-)
[---C---)

iex> union(new(left: 1, right: 2), new(left: 2, right: 3))
new(left: 1, right: 3)

iex> union(new(left: 1, right: 2), new(left: 3, right: 4))
new(left: 0, right: 0)