Skip to content

Instantly share code, notes, and snippets.

@ulfurinn
Last active September 23, 2023 20:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ulfurinn/a12822830c506a9bc8b1d310d1ca4ae5 to your computer and use it in GitHub Desktop.
Save ulfurinn/a12822830c506a9bc8b1d310d1ca4ae5 to your computer and use it in GitHub Desktop.
IntoArray

An Elixir implementation of the output iterator/IntoIterator idiom over Collectable and Arrays.

defmodule IntoArray do
defstruct [:array, offset: 0, grow: false, raise_on_grow: false]
def new(array, options \\ []) do
%__MODULE__{
array: array,
offset: Keyword.get(options, :offset, 0),
grow: Keyword.get(options, :grow, false),
raise_on_grow: Keyword.get(options, :raise_on_grow, false)
}
end
defimpl Collectable do
def into(iter) do
fun = fn
iter = %@for{
array: array,
offset: offset,
grow: grow,
raise_on_grow: raise_on_grow
},
{:cont, value} ->
cond do
offset < Arrays.size(array) ->
%@for{iter | array: Arrays.replace(array, offset, value), offset: offset + 1}
grow ->
%@for{iter | array: Arrays.append(array, value), offset: offset + 1}
raise_on_grow ->
raise ArgumentError, "tried writing past the array boundary"
true ->
iter
end
%@for{array: array}, :done ->
array
_, :halt ->
nil
end
{iter, fun}
end
end
end
defmodule IntoArrayTest do
use ExUnit.Case
test "writes with zero offset" do
initial = Arrays.empty(size: 5)
modified = [1, 2, 3] |> Enum.into(IntoArray.new(initial))
assert [1, 2, 3, nil, nil] = Arrays.to_list(modified)
end
test "writes with non-zero offset" do
initial = Arrays.empty(size: 5)
modified = [1, 2, 3] |> Enum.into(IntoArray.new(initial, offset: 1))
assert [nil, 1, 2, 3, nil] = Arrays.to_list(modified)
end
test "without grow, ignores writes past the boundary" do
initial = Arrays.empty(size: 5)
modified = [1, 2, 3] |> Enum.into(IntoArray.new(initial, offset: 3))
assert [nil, nil, nil, 1, 2] = Arrays.to_list(modified)
end
test "with grow, writes past the boundary" do
initial = Arrays.empty(size: 5)
modified = [1, 2, 3] |> Enum.into(IntoArray.new(initial, offset: 3, grow: true))
assert [nil, nil, nil, 1, 2, 3] = Arrays.to_list(modified)
end
test "with raise_on_grow, raises on trying to write past the boundary" do
initial = Arrays.empty(size: 5)
assert_raise ArgumentError, fn ->
[1, 2, 3] |> Enum.into(IntoArray.new(initial, offset: 3, raise_on_grow: true))
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment