Bounds v0.1.1 Bounds View Source
Bounds
is a formalization of the {pos, len}
tuple that is used in Erlang to slice binaries.
A Bounds value is similar to a Range
value, with a few differences:
A Bounds value can be zero-length. (A Range value must represent at least one element.)
A Bounds value is always ascending. (A Range value may be descending.)
A Bounds value always has non-negative values. (A Range value may represent negative values.)
Bounds values may therefore represent single points, equivalent to {pos, 0}
tuples.
Many functions in Bounds
return these "point-bounds" when used near the Bounds value's end points.
Because point-bounds require special handling and aren't always useful, functions like point?/1
,
range?/1
, points/1
, and ranges/1
can be used to filter point-bounds out of your final result.
However, you don't need to filter point-bounds out of intermediate results; Bounds handles point-bounds as inputs to further calculations just fine.
Implementation
Bounds values are normalized as an interval [lower, upper]
for convenience of calculation. All functions
in the Bounds
module expect a %Bounds{}
to have been created by a call to a constructor function (e.g.
new/2
, from_poslen/1
, from_range/1
.) If you construct a Bounds value yourself, the following guard must hold:
%Bounds{lower: lower, upper: upper} when
is_integer(lower) and is_integer(upper) and lower >= 0 and upper >= lower
Enumeration
Like Range
, Bounds
implements Enumerable
; and thus, like Range values, Bounds values can be
understood as a compact representation of an equivalent list value.
Unlike Range
, Bounds
has several decorators which implement Enumerable
differently, to
represent the different, equivalent abstractions that a Bounds value can be understood as.
A Bounds value, passed directly to Enum
functions, will enumerate as a collection of all
integer points in the closed interval [lower, upper]
. If your algorithm wants "point" Bounds
values (values where lower == upper
) to be included as values in the enumeration, then this is the
approach you want.
A common use-case is enumerating all half-closed intervals of some length n
(e.g. [lower, lower + n)
)
which are contained by a given Bounds value. For example, if the Bounds value represents the bounds of a binary,
then you might want the bounds of each byte of the binary: [0, 1)
, [1, 2)
, etc. The functions stepwise/2
,
split_stepwise/2
, and partitioned/3
will help with this.
Link to this section Summary
Functions
Creates a zero-length Bounds value centered on the integer point
.
Creates a new Bounds value by limiting the lower and upper bounds of value_bounds
to be no more than
the lower and upper bounds of clamp_bounds
.
Removes the open interval sub_bounds
from the closed interval bounds
and returns whatever is
left over.
Returns the end points (i.e. the points on either end) of the closed interval represented by the Bounds value.
Constructs a Bounds value representing the bounds of the given binary bin
, i.e. the
interval
Converts a {pos, len}
tuple to an equivalent Bounds value.
Converts a Range
to an equivalent Bounds value.
Casts a compatible value to a Bounds value.
Constructs a new Bounds value from a position and a length.
Returns a Bounds.Partitioned
decorator value, which implements Enumerable
.
Determines whether a Bounds value is zero-length.
Filters the passed Map
or Enumerable
value for only the Bounds values
which are zero-length (i.e. points.)
Determines whether a Bounds value is a valid Range
(i.e. has nonzero size.)
Filters the passed Map
or Enumerable
value for only the Bounds values
which are valid Range
s (i.e. which are of nonzero length.)
Returns the size of the Bounds value when interpreted as a half-closed interval [lower, upper)
.
Takes a sub-interval of bounds
as a new Bounds value.
Returns two sub-intervals representing the result of "breaking" bounds
at a given position.
Splits a Bounds value into three parts, returned as a map with the following keys
Returns a Bounds.Stepwise
decorator value, which implements Enumerable
.
Returns the integer point that a given Bounds value represents.
Converts a Bounds value to an equivalent {pos, len}
tuple.
Returns the Range
of integer points that a given Bounds value represents.
Returns a new Bounds value representing the result of translating (sliding) the endpoints of
bounds
up or down by the integer displacement disp
.
Link to this section Functions
at_point(point) View Source
Creates a zero-length Bounds value centered on the integer point
.
clamp(value_bounds, clamp_bounds) View Source
Creates a new Bounds value by limiting the lower and upper bounds of value_bounds
to be no more than
the lower and upper bounds of clamp_bounds
.
If value_bounds
fully contains clamp_bounds
, the result of the clamp will be clamp_bounds
exactly.
concat(bounds_enum) View Source
concat(bounds_a, bounds_b) View Source
difference(bounds, sub_bounds) View Source
Removes the open interval sub_bounds
from the closed interval bounds
and returns whatever is
left over.
If sub_bounds
has one or both endpoints in common with bounds
, point (zero-length)
bounds will be left over at the shared endpoint.
Examples
Subtracting from the middle produces two interval-bounds:
iex> Bounds.difference(%Bounds{lower: 0, upper: 10}, %Bounds{lower: 3, upper: 6})
[%Bounds{lower: 0, upper: 3}, %Bounds{lower: 6, upper: 10}]
Subtracting from one end produces one point-bound and one interval-bound:
iex> Bounds.difference(%Bounds{lower: 0, upper: 10}, %Bounds{lower: 0, upper: 2})
[%Bounds{lower: 0, upper: 0}, %Bounds{lower: 2, upper: 10}]
Subtracting everything produces two point-bounds:
iex> Bounds.difference(%Bounds{lower: 0, upper: 10}, %Bounds{lower: 0, upper: 10})
[%Bounds{lower: 0, upper: 0}, %Bounds{lower: 10, upper: 10}]
Subtracting from a point always produces the same point:
iex> Bounds.difference(%Bounds{lower: 5, upper: 5}, %Bounds{lower: 0, upper: 10})
[%Bounds{lower: 5, upper: 5}]
endpoints(bounds) View Source
Returns the end points (i.e. the points on either end) of the closed interval represented by the Bounds value.
If the Bounds value represents a point, there will be one end point returned (the same point.) Otherwise, there will be two points returned.
from_binary(bin) View Source
Constructs a Bounds value representing the bounds of the given binary bin
, i.e. the
interval:
[0, byte_size(bin))
from_poslen(poslen) View Source
Converts a {pos, len}
tuple to an equivalent Bounds value.
from_range(arg) View Source
Converts a Range
to an equivalent Bounds value.
new(poslen_or_range) View Source
Casts a compatible value to a Bounds value.
new(pos, len) View Source
Constructs a new Bounds value from a position and a length.
partitioned(bounds, step, offset) View Source
Returns a Bounds.Partitioned
decorator value, which implements Enumerable
.
The values enumerated from a Bounds.Partitioned
decorator will be Bounds values representing
a set of contiguous intervals. Most of these will be steps of size step
. The first full interval
(if it exists) will be [offset, offset + step)
.
Unlike with intervals/2
, the values enumerated from a Bounds.Partitioned
value will include intervals
smaller than step
—namely:
if
offset
is nonzero, an initial interval[0, offset)
will appear at the beginning of the enumeration (if it exists.)if, after removing the initial interval,
step
does not evenly divide the bounds, then a final interval[step * n, step * n + remainder)
will appear at the end of the enumeration (if it exists.)
This enumeration strategy is useful when you are using Bounds to compactly represent a set of
values, and you wish to "bin" these values into contiguous bins of size step
.
point?(bounds) View Source
Determines whether a Bounds value is zero-length.
points(m) View Source
Filters the passed Map
or Enumerable
value for only the Bounds values
which are zero-length (i.e. points.)
range?(bounds) View Source
Determines whether a Bounds value is a valid Range
(i.e. has nonzero size.)
ranges(m) View Source
Filters the passed Map
or Enumerable
value for only the Bounds values
which are valid Range
s (i.e. which are of nonzero length.)
size(bounds) View Source
Returns the size of the Bounds value when interpreted as a half-closed interval [lower, upper)
.
slice(bounds, slicing_bounds) View Source
Takes a sub-interval of bounds
as a new Bounds value.
slicing_bounds
does not represent an absolute interval to intersect with bounds
,
but rather is taken as a relative offset and length, as in Enum.slice/3
.
This operation can be understood as having equivalent semantics to that of Enum.slice/3
. The
offset and length will be "clamped" so that the result will be fully contained by bounds
.
split_at(bounds, point_or_idx_or_offset) View Source
Returns two sub-intervals representing the result of "breaking" bounds
at a given position.
The second argument point_or_idx_or_offset
can take a few forms, each with a different result:
- If it is an integer ≥ 0, it will be used to calculate a point to break relative to
bounds.lower
; - If it is an integer < 0, it will be used to calculate a point to break relative to
bounds.upper
; - If it is a Bounds value and
Bounds.point?(bounds)
is true, it will be used as an absolute point to break at.
Examples
Non-negative relative offsets:
iex> Bounds.split_at(%Bounds{lower: 5, upper: 10}, 1)
[%Bounds{lower: 5, upper: 6}, %Bounds{lower: 6, upper: 10}]
Negative relative offsets:
iex> Bounds.split_at(%Bounds{lower: 5, upper: 10}, -1)
[%Bounds{lower: 5, upper: 9}, %Bounds{lower: 9, upper: 10}]
Absolute break positions:
iex> Bounds.split_at(%Bounds{lower: 5, upper: 10}, %Bounds{lower: 7, upper: 7})
[%Bounds{lower: 5, upper: 7}, %Bounds{lower: 7, upper: 10}]
If the interval is broken at one of its endpoints, one of the resulting sub-intervals will be a point bound:
iex> Bounds.split_at(%Bounds{lower: 0, upper: 10}, 0)
[%Bounds{lower: 0, upper: 0}, %Bounds{lower: 0, upper: 10}]
split_stepwise(bounds, step) View Source
Splits a Bounds value into three parts, returned as a map with the following keys:
:whole
: the bounds of the contiguous set of values whose bounds are both 1. within the original bounds, and 2. divisible bystep_size
. This is equivalent to theunion/2
of thestepwise/2
enumeration of the givenbounds
at the givenstep_size
.:partial_before
: the interval extending from the beginning of the original bounds, to the beginning ofwhole
.:partial_after
: the interval extending from the end ofwhole
, to the end of the original bounds.
Any/all of the values in this map may turn out to be zero-sized "point" Bounds.
stepwise(bounds, step_size \\ 1) View Source
Returns a Bounds.Stepwise
decorator value, which implements Enumerable
.
The values enumerated from a Bounds.Stepwise
decorator are themselves Bounds values,
representing a set of contiguous intervals, each of size step_size
. The enumeration always begins
with the interval [0, step_size)
(if it exists.)
Any bounded interval smaller than step_size
is not considered a part of the enumeration.
This enumeration strategy is useful when you have a sequence of unit-sized chunks (like the bytes
in a binary), in which a representation for one element is encoded as step_size
contiguous chunks.
The enumerated values will then represent the bounds of the representations of all
potentially-valid elements.
Examples
Get the bounds of each single byte of a binary:
iex> Bounds.from_binary("foo") |> Bounds.stepwise(1) |> Enum.to_list()
[%Bounds{lower: 0, upper: 1}, %Bounds{lower: 1, upper: 2}, %Bounds{lower: 2, upper: 3}]
Get the bounds of a sequence of 32-bit values in a binary:
iex> Bounds.from_binary("abcdEFGH") |> Bounds.stepwise(4) |> Enum.to_list()
[%Bounds{lower: 0, upper: 4}, %Bounds{lower: 4, upper: 8}]
to_point!(bounds) View Source
Returns the integer point that a given Bounds value represents.
Raises an exception if the given Bounds value does not represent a point.
to_poslen(bounds) View Source
Converts a Bounds value to an equivalent {pos, len}
tuple.
to_range(bounds) View Source
Returns the Range
of integer points that a given Bounds value represents.
Raises an exception if the given Bounds value does not represent a range of points (i.e. because it instead represents a single point.)
translate(bounds, disp) View Source
Returns a new Bounds value representing the result of translating (sliding) the endpoints of
bounds
up or down by the integer displacement disp
.