Packages

Lazy, external iterators for Elixir.

Current section

Files

Jump to
iterex lib iter iterable peeker.ex
Raw

lib/iter/iterable/peeker.ex

defmodule Iter.Iterable.Peeker do
defstruct iterable: nil, elements: [], size: 0
@moduledoc """
The result of "peeking" into an iterable.
"""
alias Iter.{Impl, IntoIterable, Iterable}
@type t :: %__MODULE__{
iterable: Iterable.t(),
elements: [Iterable.element()],
size: non_neg_integer()
}
@doc """
Create a new "peeker" iterable.
"""
@spec new(Iterable.t()) :: t
def new(iterable), do: %__MODULE__{iterable: iterable}
defimpl Iterable do
use Impl
@doc false
@impl true
def next(%{elements: [], iterable: iterable}), do: Iterable.next(iterable)
def next(%{elements: [element], iterable: iterable}), do: {:ok, element, iterable}
def next(%{elements: [element | rest], size: size} = peeker),
do: {:ok, element, %{peeker | elements: rest, size: size - 1}}
@doc false
@impl true
def peek(%{elements: [], iterable: iterable} = peeker) do
case Iterable.next(iterable) do
{:ok, element, iterable} ->
{:ok, element, %{peeker | elements: [element], size: 1, iterable: iterable}}
:done ->
:done
end
end
def peek(%{elements: [element | _]} = peeker) do
{:ok, element, peeker}
end
@doc false
@impl true
def peek(peeker, 0), do: {:ok, [], 0, peeker}
def peek(%{elements: elements, size: size} = peeker, how_many) when how_many == size do
{:ok, elements, size, peeker}
end
def peek(%{elements: elements, size: size} = peeker, how_many)
when how_many < size and how_many > 0 do
{:ok, :lists.sublist(elements, how_many), how_many, peeker}
end
def peek(%{elements: elements, iterable: iterable, size: size} = peeker, how_many) do
remaining = how_many - size
with {:ok, got, extra_elements, iterable} <- do_peek(iterable, remaining, [], 0) do
elements = elements ++ extra_elements
size = got + size
{:ok, elements, size, %{peeker | elements: elements, iterable: iterable, size: size}}
end
end
defp do_peek(iterable, 0, result, so_far), do: {:ok, so_far, :lists.reverse(result), iterable}
defp do_peek(iterable, remaining, result, so_far) do
case Iterable.next(iterable) do
{:ok, element, iterable} ->
do_peek(iterable, remaining - 1, [element | result], so_far + 1)
:done when result == [] ->
:done
:done ->
{:ok, so_far, :lists.reverse(result), Iterable.Empty.new()}
end
end
end
defimpl IntoIterable do
@doc false
def into_iterable(self), do: self
end
end