View Source Warder.Range (warder v0.1.0)

Range Implementation

Creation / Manipulation

Ranges should not be created or modified manually. Use new/3, new!/3 or empty/0 to create ranges.

Ecto Type

If ecto and postgrex are installed, you can use the Warder.Range type:

defmodule Model do
  use Ecto.Schema

  alias Warder.Range

  @type t :: %{range: Range.t(integer())}

  schema "models" do
    field :range, Range,
      db_type: :int8range,
      inner_type: :integer
  end
end

Options

  • db_type - The database type to use for the range column. For example :int8range.
  • inner_type - The ecto type of the elements in the range. For example :integer.

Summary

Types

An empty range is one that does not contain any values between the lower and upper bounds.

Range with specified bounds.

t()

A range of any subtype.

A specified or empty range with the bound type of subtype.

Functions

Are the ranges adjacent?

Compare ranges for ordering

Does first contain second?

Computes the difference of the ranges.

Computes the difference of the ranges.

Create a new empty range.

Computes the intersection of the ranges.

Guard to detect empty ranges.

Is the first range strictly left of the second?

Computes the smallest range that includes both of the given ranges.

Create a new range for the given bounds.

Create a new range for the given bounds.

Does the first range not extend to the left of the second?

Does the first range not extend to the right of the second?

Does the first range overlap with the second range, that is, have any elements in common?

Is the first range strictly right of the second?

Computes the union of the ranges.

Computes the union of the ranges.

Types

@type empty() :: %Warder.Range{
  lower: :empty,
  lower_inclusive: nil,
  upper: :empty,
  upper_inclusive: nil
}

An empty range is one that does not contain any values between the lower and upper bounds.

Link to this type

specified(subtype)

View Source (since 0.1.0)
@type specified(subtype) :: %Warder.Range{
  lower: subtype | :unbound,
  lower_inclusive: boolean(),
  upper: subtype | :unbound,
  upper_inclusive: boolean()
}

Range with specified bounds.

Fields

  • lower - The lower bound of the range.
  • upper - The upper bound of the range.
  • lower_inclusive - Whether the lower bound is inclusive.
  • upper_inclusive - Whether the upper bound is inclusive.
@type t() :: t(term())

A range of any subtype.

Link to this type

t(subtype)

View Source (since 0.1.0)
@type t(subtype) :: specified(subtype) | empty()

A specified or empty range with the bound type of subtype.

Functions

Link to this function

adjacent?(first, second)

View Source (since 0.1.0)
@spec adjacent?(first :: t(subtype), second :: t(subtype)) :: boolean()
when subtype: Warder.Element.t()

Are the ranges adjacent?

Examples

iex> Warder.Range.adjacent?(Warder.Range.new!(1, 10), Warder.Range.new!(10, 20))
true

iex> Warder.Range.adjacent?(Warder.Range.new!(1, 10), Warder.Range.new!(15, 20))
false
Link to this function

compare(first, second)

View Source (since 0.1.0)
@spec compare(first :: t(subtype), second :: t(subtype)) :: :lt | :eq | :gt
when subtype: Warder.Element.t()

Compare ranges for ordering

The module Warder.Range can be used as the sorter argument for Enum.sort/2 and Enum.sort_by/3.

Examples

iex> Warder.Range.compare(Warder.Range.new!(1, 10), Warder.Range.new!(5, 15))
:lt

iex> Warder.Range.compare(Warder.Range.new!(1, 10), Warder.Range.new!(1, 10))
:eq

iex> Warder.Range.compare(Warder.Range.new!(1, 10), Warder.Range.new!(0, 5))
:gt
Link to this function

contains?(first, second)

View Source (since 0.1.0)
@spec contains?(first :: t(subtype) | subtype, second :: t(subtype) | subtype) ::
  boolean()
when subtype: Warder.Element.t()

Does first contain second?

Both first and second can either be a range or an element.

Examples

iex> Warder.Range.contains?(Warder.Range.new!(1, 10), Warder.Range.new!(5, 10))
true

iex> Warder.Range.contains?(Warder.Range.new!(1, 10), Warder.Range.new!(15, 20))
false

iex> Warder.Range.contains?(Warder.Range.new!(1, 10), 5)
true

iex> Warder.Range.contains?(Warder.Range.new!(1, 10), 20)
false
Link to this function

difference(first, second)

View Source (since 0.1.0)
@spec difference(first :: t(subtype), second :: t(subtype)) ::
  {:ok, t(subtype)} | {:error, Warder.Range.DisjointRangesError.t()}
when subtype: Warder.Element.t()

Computes the difference of the ranges.

The second range must not be contained in the first in such a way that the difference would not be a single range.

Examples

iex> Warder.Range.difference(Warder.Range.new!(1, 10), Warder.Range.new!(5, 15))
{:ok, %Warder.Range{lower: 1, upper: 5, lower_inclusive: true, upper_inclusive: false}}

iex> Warder.Range.difference(Warder.Range.new!(1, 10), Warder.Range.new!(15, 20))
{:ok, %Warder.Range{lower: 1, upper: 10, lower_inclusive: true, upper_inclusive: false}}

iex> Warder.Range.difference(Warder.Range.new!(1, 10), Warder.Range.new!(2, 8))
{:error, %Warder.Range.DisjointRangesError{
  lower: %Warder.Range{lower: 1, upper: 2, lower_inclusive: true, upper_inclusive: false},
  upper: %Warder.Range{lower: 8, upper: 10, lower_inclusive: true, upper_inclusive: false}
}}
Link to this function

difference!(first, second)

View Source (since 0.1.0)
@spec difference!(first :: t(subtype), second :: t(subtype)) :: t(subtype)
when subtype: Warder.Element.t()

Computes the difference of the ranges.

See difference/2 for details.

Examples

iex> Warder.Range.difference!(Warder.Range.new!(1, 10), Warder.Range.new!(5, 15))
%Warder.Range{lower: 1, upper: 5, lower_inclusive: true, upper_inclusive: false}

iex> Warder.Range.difference!(Warder.Range.new!(1, 10), Warder.Range.new!(2, 8))
** (Warder.Range.DisjointRangesError) The result of range difference would not be contiguous.
Before difference: %Warder.Range{lower: 1, upper: 2, lower_inclusive: true, upper_inclusive: false}
After difference: %Warder.Range{lower: 8, upper: 10, lower_inclusive: true, upper_inclusive: false}
@spec empty() :: empty()

Create a new empty range.

Examples

iex> Warder.Range.empty()
%Warder.Range{lower: :empty, lower_inclusive: nil, upper: :empty, upper_inclusive: nil}
Link to this function

intersection(first, second)

View Source (since 0.1.0)
@spec intersection(first :: t(subtype), second :: t(subtype)) :: t(subtype)
when subtype: Warder.Element.t()

Computes the intersection of the ranges.

Examples

iex> Warder.Range.intersection(Warder.Range.new!(1, 10), Warder.Range.new!(5, 15))
%Warder.Range{lower: 5, upper: 10, lower_inclusive: true, upper_inclusive: false}

iex> Warder.Range.intersection(Warder.Range.new!(1, 10), Warder.Range.new!(15, 20))
%Warder.Range{lower: :empty, upper: :empty, lower_inclusive: nil, upper_inclusive: nil}
Link to this macro

is_empty(range)

View Source (since 0.1.0) (macro)

Guard to detect empty ranges.

Link to this function

left?(first, second)

View Source (since 0.1.0)
@spec left?(first :: t(subtype), second :: t(subtype)) :: boolean()
when subtype: Warder.Element.t()

Is the first range strictly left of the second?

Examples

iex> Warder.Range.left?(Warder.Range.new!(1, 10), Warder.Range.new!(11, 20))
true

iex> Warder.Range.left?(Warder.Range.new!(1, 10), Warder.Range.new!(5, 15))
false
Link to this function

merge(first, second)

View Source (since 0.1.0)
@spec merge(first :: t(subtype), second :: t(subtype)) :: t(subtype)
when subtype: Warder.Element.t()

Computes the smallest range that includes both of the given ranges.

Examples

iex> Warder.Range.merge(Warder.Range.new!(1, 10), Warder.Range.new!(5, 15))
%Warder.Range{lower: 1, upper: 15, lower_inclusive: true, upper_inclusive: false}

iex> Warder.Range.merge(Warder.Range.new!(1, 10), Warder.Range.new!(15, 20))
%Warder.Range{lower: 1, upper: 20, lower_inclusive: true, upper_inclusive: false}
Link to this function

new(lower, upper, options \\ [])

View Source (since 0.1.0)
@spec new(
  lower :: subtype | :unbound,
  upper :: subtype | :unbound,
  options :: [lower_inclusive: boolean(), upper_inclusive: boolean()]
) :: {:ok, t(subtype)} | {:error, Warder.Range.BoundOrderError.t()}
when subtype: Warder.Element.t()

Create a new range for the given bounds.

The lower bound must be less than or equal to the upper bound.

If the bounds are equal, that is, there are no values between the lower and upper bound, the resulting range is empty.

Canonicalization

Ranges are canonicalized on creation. Therefore the bounds & inclusion might shift to ensure the range is in a consistent state while keeping the same meaning.

Options

  • :lower_inclusive - Whether the lower bound is inclusive. Defaults to true.
  • :upper_inclusive - Whether the upper bound is inclusive. Defaults to false.

Examples

iex> Warder.Range.new(1, 10)
{:ok, %Warder.Range{lower: 1, upper: 10, lower_inclusive: true, upper_inclusive: false}}

iex> Warder.Range.new(:unbound, 10)
{:ok, %Warder.Range{lower: :unbound, upper: 10, lower_inclusive: true, upper_inclusive: false}}

iex> Warder.Range.new(1, 1, upper_inclusive: false)
{:ok, %Warder.Range{lower: :empty, upper: :empty, lower_inclusive: nil, upper_inclusive: nil}}

iex> Warder.Range.new(10, 1)
{:error, %Warder.Range.BoundOrderError{lower: 10, upper: 1}}
Link to this function

new!(lower, upper, options \\ [])

View Source (since 0.1.0)
@spec new!(
  lower :: subtype,
  upper :: subtype,
  options :: [lower_inclusive: boolean(), upper_inclusive: boolean()]
) :: t(subtype)
when subtype: Warder.Element.t()

Create a new range for the given bounds.

See new/3 for details.

Examples

iex> Warder.Range.new!(1, 10)
%Warder.Range{lower: 1, upper: 10, lower_inclusive: true, upper_inclusive: false}

iex> Warder.Range.new!(10, 1)
** (Warder.Range.BoundOrderError) The range lower bound (10) must be less than or equal to range upper bound (1).
Link to this function

no_extend_left?(first, second)

View Source (since 0.1.0)
@spec no_extend_left?(first :: t(subtype), second :: t(subtype)) :: boolean()
when subtype: Warder.Element.t()

Does the first range not extend to the left of the second?

Examples

iex> Warder.Range.no_extend_left?(Warder.Range.new!(5, 15), Warder.Range.new!(1, 10))
true

iex> Warder.Range.no_extend_left?(Warder.Range.new!(1, 10), Warder.Range.new!(5, 10))
false
Link to this function

no_extend_right?(first, second)

View Source (since 0.1.0)
@spec no_extend_right?(first :: t(subtype), second :: t(subtype)) :: boolean()
when subtype: Warder.Element.t()

Does the first range not extend to the right of the second?

Examples

iex> Warder.Range.no_extend_right?(Warder.Range.new!(1, 10), Warder.Range.new!(5, 15))
true

iex> Warder.Range.no_extend_right?(Warder.Range.new!(1, 15), Warder.Range.new!(5, 10))
false
Link to this function

overlap?(first, second)

View Source (since 0.1.0)
@spec overlap?(first :: t(subtype), second :: t(subtype)) :: boolean()
when subtype: Warder.Element.t()

Does the first range overlap with the second range, that is, have any elements in common?

Examples

iex> Warder.Range.overlap?(Warder.Range.new!(1, 10), Warder.Range.new!(5, 15))
true

iex> Warder.Range.overlap?(Warder.Range.new!(1, 10), Warder.Range.new!(15, 20))
false
Link to this function

right?(first, second)

View Source (since 0.1.0)
@spec right?(first :: t(subtype), second :: t(subtype)) :: boolean()
when subtype: Warder.Element.t()

Is the first range strictly right of the second?

Examples

iex> Warder.Range.right?(Warder.Range.new!(11, 20), Warder.Range.new!(1, 10))
true

iex> Warder.Range.right?(Warder.Range.new!(5, 15), Warder.Range.new!(1, 10))
false
Link to this function

union(first, second)

View Source (since 0.1.0)
@spec union(first :: t(subtype), second :: t(subtype)) ::
  {:ok, t(subtype)} | {:error, Warder.Range.NotContiguousError.t()}
when subtype: Warder.Element.t()

Computes the union of the ranges.

The ranges must overlap or be adjacent, so that the union is a single range.

Examples

iex> Warder.Range.union(Warder.Range.new!(1, 10), Warder.Range.new!(5, 15))
{:ok, %Warder.Range{lower: 1, upper: 15, lower_inclusive: true, upper_inclusive: false}}

iex> Warder.Range.union(Warder.Range.new!(1, 10), Warder.Range.new!(15, 20))
{:error, %Warder.Range.NotContiguousError{
  first: %Warder.Range{lower: 1, upper: 10, lower_inclusive: true, upper_inclusive: false},
  second: %Warder.Range{lower: 15, upper: 20, lower_inclusive: true, upper_inclusive: false}
}}
Link to this function

union!(first, second)

View Source (since 0.1.0)
@spec union!(first :: t(subtype), second :: t(subtype)) :: t(subtype)
when subtype: Warder.Element.t()

Computes the union of the ranges.

See union/2.

Examples

iex> Warder.Range.union!(Warder.Range.new!(1, 10), Warder.Range.new!(5, 15))
%Warder.Range{lower: 1, upper: 15, lower_inclusive: true, upper_inclusive: false}

iex> Warder.Range.union!(Warder.Range.new!(1, 10), Warder.Range.new!(15, 20))
** (Warder.Range.NotContiguousError) The first range and second range are not contiguous.
First: %Warder.Range{lower: 1, upper: 10, lower_inclusive: true, upper_inclusive: false}
Second: %Warder.Range{lower: 15, upper: 20, lower_inclusive: true, upper_inclusive: false}