141 lines
3.4 KiB
Elixir
141 lines
3.4 KiB
Elixir
defmodule AoC.Util.Range do
|
|
@doc """
|
|
Given a `start` and a `length`, returns a Range of
|
|
start..(start+len-1).
|
|
|
|
Returns the empty range if `len` is 0.
|
|
|
|
## Examples
|
|
|
|
iex> AoC.Util.Range.from_start(10, 2)
|
|
10..11
|
|
|
|
iex> AoC.Util.Range.from_start(10, 0)
|
|
..
|
|
|
|
iex> AoC.Util.Range.from_start(10, 10)
|
|
10..19
|
|
"""
|
|
@spec from_start(integer(), integer()) :: Range.t()
|
|
def from_start(start, len) do
|
|
if len == 0 do
|
|
..
|
|
else
|
|
start..(start + len - 1)
|
|
end
|
|
end
|
|
|
|
@doc """
|
|
Given a `value`, z, and a range (x,y) containing that value.
|
|
Return the `value`'s "place" in the `dest` range (s,t),
|
|
given by s + (z - x). Note that this does not check that
|
|
`source` and `dest` are the same size so it is possible the
|
|
returned value is not contained within `dest`.
|
|
|
|
iex> AoC.Util.Range.transpose(5, 1..10, 30..50)
|
|
34
|
|
"""
|
|
def transpose(value, source, dest) do
|
|
offset = value - (source |> Enum.at(0))
|
|
(dest |> Enum.at(0)) + offset
|
|
end
|
|
|
|
@doc """
|
|
Returns true iff r2 is completely contained within r1.
|
|
|
|
## Examples
|
|
|
|
iex> AoC.Util.Range.contains?(1..10, 2..8)
|
|
true
|
|
"""
|
|
@spec contains?(Range.t(), Range.t()) :: boolean
|
|
def contains?(r1, r2) do
|
|
r2.first >= r1.first and r2.last <= r1.last
|
|
end
|
|
|
|
@doc """
|
|
Returns the set difference r1 \\ r2.
|
|
|
|
## Examples
|
|
|
|
iex> AoC.Util.Range.difference(1..10, 11..20)
|
|
[1..10]
|
|
|
|
iex> AoC.Util.Range.difference(1..10, 2..5)
|
|
[1..1, 6..10]
|
|
|
|
iex> AoC.Util.Range.difference(1..10, -1..3)
|
|
[4..10]
|
|
"""
|
|
@spec difference(Range.t(), Range.t()) :: [Range.t()]
|
|
def difference(r1, r2) do
|
|
cond do
|
|
Range.disjoint?(r1, r2) ->
|
|
[r1]
|
|
|
|
AoC.Util.Range.contains?(r2, r1) ->
|
|
[]
|
|
|
|
AoC.Util.Range.contains?(r1, r2) ->
|
|
s1_len = r2.first - r1.first
|
|
s2_len = r1.last - r2.last
|
|
|
|
[
|
|
AoC.Util.Range.from_start(r1.first, s1_len),
|
|
AoC.Util.Range.from_start(r2.last + 1, s2_len)
|
|
]
|
|
|> Enum.reject(fn r -> r == .. end)
|
|
|
|
r2.first <= r1.first and r2.last <= r1.last ->
|
|
s1_len = r1.last - r2.last
|
|
[AoC.Util.Range.from_start(r2.last + 1, s1_len)]
|
|
|
|
r2.first >= r1.first and r2.last >= r1.last ->
|
|
s1_len = r2.first - r1.first
|
|
[AoC.Util.Range.from_start(r1.first, s1_len)]
|
|
end
|
|
end
|
|
|
|
@doc """
|
|
Returns the set intersection of r1 and r2.
|
|
|
|
## Examples
|
|
|
|
iex> AoC.Util.Range.intersection(1..10, 1..3)
|
|
1..3
|
|
|
|
iex> AoC.Util.Range.intersection(1..10, -1..3)
|
|
1..3
|
|
|
|
iex> AoC.Util.Range.intersection(-1..10, 1..3)
|
|
1..3
|
|
"""
|
|
@spec intersection(Range.t(), Range.t()) :: Range.t()
|
|
def intersection(r1, r2) do
|
|
cond do
|
|
Range.disjoint?(r1, r2) -> ..
|
|
AoC.Util.Range.contains?(r2, r1) -> r1
|
|
AoC.Util.Range.contains?(r1, r2) -> r2
|
|
r2.first <= r1.first and r2.last <= r1.last -> Range.new(r1.first, r2.last)
|
|
r2.first >= r1.first and r2.last >= r1.last -> Range.new(r2.first, r1.last)
|
|
end
|
|
end
|
|
|
|
@spec split_at(range :: Range.t(), value :: integer(), bound :: :lb | :ub) :: [Range.t()]
|
|
def split_at(range, value, bound) do
|
|
if value <= range.first or value >= range.last do
|
|
[range]
|
|
else
|
|
ls = min(range.first, value - 1)
|
|
le = min(range.last, value - 1)
|
|
rs = max(range.first, value + 1)
|
|
re = max(range.last, value + 1)
|
|
|
|
if bound == :lb do
|
|
[ls..value, rs..re]
|
|
else
|
|
[ls..le, value..re]
|
|
end
|
|
end
|
|
end
|
|
end
|