Skip to main content

src/twister.erl

-module(twister).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src\\twister.gleam").
-export([from_list/1, blank/0, add/2, set_modulo/2, run/2, run_default/3, run_generate/3, compose/2]).
-export_type([permutation/0]).

-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.

?MODULEDOC(
    " The main module of `twister`, containing basically all of the relevant API.\n"
    " \n"
    " ## Introduction\n"
    " To use this library, you first need to create a [`Permutation`](twister.html#Permutation),\n"
    " using one of the provided methods:\n"
    " 1. Create it from a `List` of indexes directly, using the\n"
    "    [`from_list`](twister.html#from_list) function\n"
    " 2. Create it using a builder pattern by first initializing it with\n"
    "    [`blank`](twister.html#blank), then adding indexes to the end one by one with\n"
    "    [`add`](twister.html#add).\n"
    " \n"
    " ## Examples\n"
    " \n"
    " ### Creating a Permutation\n"
    " \n"
    " > *Note* : Inside of the `Permutation` data type, the list of indices is stored in\n"
    "   reverse order. This is because when running one, the order is also reversed.\n"
    "   So, instead of needlessly reversing the internal List, it's just stored back to front.\n"
    " \n"
    " Using a List of indices\n"
    " ```gleam\n"
    " // Create a `Permutation` from a `List` of indices\n"
    " twister.from_list([1, 4, 2, 0, 3, 5])\n"
    " // -> Permutation(False, Some(5), [5, 3, 0, 2, 4, 1])\n"
    " //      This is the important part ^^^^^^^^^^^^^^^^\n"
    " ```\n"
    " \n"
    " Using the builder\n"
    " ```gleam\n"
    " // Create a `Permutation` using a builder\n"
    " twister.blank()\n"
    " |> twister.add(1)\n"
    " |> twister.add(4)\n"
    " |> twister.add(2)\n"
    " |> twister.add(0)\n"
    " |> twister.add(3)\n"
    " |> twister.add(5)\n"
    " // -> Permutation(False, Some(5), [5, 3, 0, 2, 4, 1])\n"
    " // Same as before, just more annoying. But sometimes useful\n"
    " ```\n"
    " \n"
    " ### Using a Permutation\n"
    " \n"
    " Using [`twister.run`](twister.html#run) returns a `Result(List(a), Nil)`;\n"
    " - `Ok(List(a))` if all of the indices are smaller than the length of the input `List`\n"
    " - `Error(Nil)` if some of the indices were outside the bounds of the input `List`\n"
    " ```gleam\n"
    " let perm = twister.from_list([1, 4, 2, 0, 3, 5])\n"
    " // Call it with run to get a `Result(List(a), Nil)`.\n"
    " assert twister.run(perm, [\"a\", \"b\", \"c\", \"d\", \"e\", \"f\"])\n"
    "   == Ok([\"b\", \"e\", \"c\", \"a\", \"d\", \"f\"])\n"
    " ```\n"
    " \n"
    " Otherwise, you can use [`twister.run_default`](twister.html#run_default) or\n"
    " [`twister.run_generate`](twister.html#run_generate) to replace missing elements if\n"
    " indices are outside of the bounds of the input `List`.\n"
    " ```gleam\n"
    " let perm = twister.from_list([1, 4, 2, 0, 3, 5])\n"
    " \n"
    " assert twister.run_generate(perm, [\"a\", \"b\", \"c\", \"d\", \"e\"], int.to_string)\n"
    "   == [\"b\", \"e\", \"c\", \"a\", \"d\", \"5\"]\n"
    " //   Index outside the bounds  ^^^ replaced with generated value\n"
    " assert twister.run_default(perm, [\"a\", \"b\", \"c\", \"d\", \"e\"], \"\")\n"
    "   == [\"b\", \"e\", \"c\", \"a\", \"d\", \"\"]\n"
    " //   Index outside the bounds  ^^ replaced with default value\n"
    " ```\n"
    " \n"
).

-type permutation() :: {permutation,
        boolean(),
        gleam@option:option(integer()),
        list(integer())}.

-file("src\\twister.gleam", 104).
?DOC(
    " Create a new `Permutation` from a given list of indexes.\n"
    " \n"
    " When later running a permutation *on* some `List`, that `List` must have a length more than\n"
    " or equal to the largest index in the list of indexes within, or it will return `Error(Nil)`.\n"
).
-spec from_list(list(integer())) -> permutation().
from_list(L) ->
    {permutation, false, twister@util:largest(L), lists:reverse(L)}.

-file("src\\twister.gleam", 118).
?DOC(
    " Create a new, blank `Permutation`.\n"
    " \n"
    " This function is meant to be used as the starting point for constructing a `Permutation`\n"
    " using the builder pattern, by using the [`add`](twister.html#add) function to add a new\n"
    " index at the end of the list of indexes.\n"
).
-spec blank() -> permutation().
blank() ->
    {permutation, false, none, []}.

-file("src\\twister.gleam", 135).
?DOC(
    " Add an index to the end of a `Permutation`.\n"
    " \n"
    " This function is meant to be used after creating a blank `Permutation` using the\n"
    " [`blank`](twister.html#blank) function, but there's nothing stopping you from using it to\n"
    " add elements to a `Permutation` created using the [`from_list`](twister.html#from_list)\n"
    " function.\n"
    " \n"
    " When later running a permutation *on* some `List`, that `List` must have a length more\n"
    " than or equal to the largest index in the list of indexes within, or it will return\n"
    " `Error(Nil)`.\n"
).
-spec add(permutation(), integer()) -> permutation().
add(Perm, I) ->
    {permutation, Modulo, Old_max, Output} = Perm,
    New_max = case Old_max of
        {some, Prev_max} ->
            gleam@int:max(Prev_max, I);

        none ->
            I
    end,
    {permutation, Modulo, {some, New_max}, [I | Output]}.

-file("src\\twister.gleam", 144).
-spec set_modulo(permutation(), boolean()) -> permutation().
set_modulo(Perm, To) ->
    {permutation, To, erlang:element(3, Perm), erlang:element(4, Perm)}.

-file("src\\twister.gleam", 295).
-spec map_modulo(list(integer()), integer()) -> list(integer()).
map_modulo(Indexes, Modulo) ->
    _pipe = Indexes,
    gleam@list:map(_pipe, fun(I) -> _pipe@1 = gleam@int:modulo(I, Modulo),
            gleam@result:unwrap(_pipe@1, 0) end).

-file("src\\twister.gleam", 175).
-spec run_loop(list(DMC), list(integer()), list(DMC)) -> {ok, list(DMC)} |
    {error, nil}.
run_loop(Over, Indexes, Acc) ->
    case Indexes of
        [] ->
            {ok, Acc};

        [Index | Rest] ->
            case twister@util:at(Over, Index) of
                {ok, El} ->
                    run_loop(Over, Rest, [El | Acc]);

                {error, nil} ->
                    {error, nil}
            end
    end.

-file("src\\twister.gleam", 163).
?DOC(
    " Run a created `Permutation` on some `List`.\n"
    " \n"
    " This function returns a `Result` because there's no guarantee that the provided\n"
    " `List` contains enough elements to build the permuted output.\n"
    " \n"
    " For example, if I have a `Permutation` like `[0, 2, 5]` and I pass in `[\"a\", \"b\"]`,\n"
    " there simply aren't enough elements in the `List` to find the element at index 5,\n"
    " or even 2. So this function returns `Error(Nil)`.\n"
    " \n"
    " If you wish to guarantee that a `Permutation` always returns a `List` the exact same\n"
    " length as the `List` of specified indexes, use the [`run_default`](twister.html#run_default)\n"
    " function, and choose a default value to return when the index is out of bounds.\n"
).
-spec run(permutation(), list(DLX)) -> {ok, list(DLX)} | {error, nil}.
run(Perm, L) ->
    case {Perm, erlang:length(L)} of
        {{permutation, _, _, []}, _} ->
            {ok, []};

        {{permutation, _, none, _}, _} ->
            {error, nil};

        {{permutation, false, {some, Max}, Non_empty}, Len} when Len >= Max ->
            run_loop(L, Non_empty, []);

        {{permutation, true, {some, _}, Non_empty@1}, Len@1} when Len@1 > 0 ->
            run_loop(
                L,
                begin
                    _pipe = Non_empty@1,
                    map_modulo(_pipe, erlang:length(L))
                end,
                []
            );

        {{permutation, _, {some, _}, _}, _} ->
            {error, nil}
    end.

-file("src\\twister.gleam", 243).
-spec run_generate_loop(
    list(DMP),
    list(integer()),
    fun((integer()) -> DMP),
    list(DMP)
) -> list(DMP).
run_generate_loop(Over, Indexes, Fun, Acc) ->
    case Indexes of
        [] ->
            Acc;

        [Index | Rest] ->
            run_generate_loop(
                Over,
                Rest,
                Fun,
                [case twister@util:at(Over, Index) of
                        {ok, El} ->
                            El;

                        {error, nil} ->
                            Fun(Index)
                    end | Acc]
            )
    end.

-file("src\\twister.gleam", 200).
?DOC(
    " Run a created `Permutation` on some `List`.\n"
    " \n"
    " This function always returns a `List(a)` by simply putting the provided `default`\n"
    " argument in the returned `List` whenever the requested index is out of bounds.\n"
    " \n"
    " ## Note\n"
    " If while creating the `Permutation`, the [`set_modulo`](twister.html#set_modulo)\n"
    " function was used, this function will behave identically to [`run`](twister.html#run),\n"
    " as all of the indexes will definitely be within the bounds of the provided `List`.\n"
).
-spec run_default(permutation(), list(DMJ), DMJ) -> list(DMJ).
run_default(Perm, L, Default) ->
    case Perm of
        {permutation, _, _, []} ->
            [];

        {permutation, false, _, Non_empty} ->
            run_generate_loop(L, Non_empty, fun(_) -> Default end, []);

        {permutation, true, _, Non_empty@1} ->
            run_generate_loop(
                L,
                begin
                    _pipe = Non_empty@1,
                    map_modulo(_pipe, erlang:length(L))
                end,
                fun(_) -> Default end,
                []
            )
    end.

-file("src\\twister.gleam", 230).
?DOC(
    " Run a created `Permutation` on some `List`.\n"
    " \n"
    " This function always returns a `List(a)` by simply generating a value in the returned\n"
    " `List` whenever the requested index is out of bounds, with the index in question\n"
    " as the input argument.\n"
    " \n"
    " ## Note\n"
    " If while creating the `Permutation`, the [`set_modulo`](twister.html#set_modulo)\n"
    " function was used, this function will behave identically to [`run`](twister.html#run),\n"
    " as all of the indexes will definitely be within the bounds of the provided `List`.\n"
).
-spec run_generate(permutation(), list(DMM), fun((integer()) -> DMM)) -> list(DMM).
run_generate(Perm, L, Fun) ->
    case Perm of
        {permutation, _, _, []} ->
            [];

        {permutation, false, _, Non_empty} ->
            run_generate_loop(L, Non_empty, Fun, []);

        {permutation, true, _, Non_empty@1} ->
            run_generate_loop(
                L,
                begin
                    _pipe = Non_empty@1,
                    map_modulo(_pipe, erlang:length(L))
                end,
                Fun,
                []
            )
    end.

-file("src\\twister.gleam", 272).
?DOC(
    " Transform two `Permutation`s into a third, by executing the first one, then the second one.\n"
    " \n"
    " Importantly, the first permutation must have an internal length of `n + 1`, where `n` is\n"
    " the largest index of the second, and its' length will be that of the second one.\n"
    " \n"
    " Lastly, the resulting `Permutation` will essentially be a new one, not inheriting the properties\n"
    " of the input ones (specifically the modulo from [`set_modulo`](twister.html#set_modulo)).\n"
).
-spec compose(permutation(), permutation()) -> {ok, permutation()} |
    {error, nil}.
compose(First, Second) ->
    _pipe = run(Second, erlang:element(4, First)),
    gleam@result:map(_pipe, fun(Indexes) -> from_list(Indexes) end).