-module(metamon@transform@dict). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/metamon/transform/dict.gleam"). -export([insert/2, remove/1, shuffle_keys/1]). -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( " Standard `Transform(Dict(k, v))` constructors used in metamorphic\n" " relations. The classic use case is asserting that a function is\n" " invariant under key reordering: `f(shuffle_keys(d)) == f(d)`.\n" ). -file("src/metamon/transform/dict.gleam", 11). ?DOC(" Insert / overwrite the binding `key -> value`.\n"). -spec insert(HMK, HML) -> metamon@transform:transform(gleam@dict:dict(HMK, HML)). insert(Key, Value) -> metamon@transform:new( <<"dict.insert"/utf8>>, fun(D) -> gleam@dict:insert(D, Key, Value) end ). -file("src/metamon/transform/dict.gleam", 16). ?DOC(" Delete `key` from the dict if present.\n"). -spec remove(HMP) -> metamon@transform:transform(gleam@dict:dict(HMP, any())). remove(Key) -> metamon@transform:new( <<"dict.remove"/utf8>>, fun(D) -> gleam@dict:delete(D, Key) end ). -file("src/metamon/transform/dict.gleam", 55). -spec fold_assign_keys( list({HNJ, HNK}), metamon@generator@seed:seed(), integer(), list({integer(), {HNJ, HNK}}) ) -> {metamon@generator@seed:seed(), list({integer(), {HNJ, HNK}})}. fold_assign_keys(Pairs, S, Length, Acc) -> case Pairs of [] -> {S, Acc}; [First | Rest] -> {Key, Next_state} = metamon@generator@seed:next_int_in( S, 0, Length * 100 ), fold_assign_keys(Rest, Next_state, Length, [{Key, First} | Acc]) end. -file("src/metamon/transform/dict.gleam", 74). -spec count(list(any()), integer()) -> integer(). count(Items, Acc) -> case Items of [] -> Acc; [_ | Rest] -> count(Rest, Acc + 1) end. -file("src/metamon/transform/dict.gleam", 70). -spec list_length(list(any())) -> integer(). list_length(Items) -> count(Items, 0). -file("src/metamon/transform/dict.gleam", 89). -spec insert_sorted({integer(), HNV}, list({integer(), HNV})) -> list({integer(), HNV}). insert_sorted(Item, Sorted) -> case Sorted of [] -> [Item]; [First | Rest] -> case erlang:element(1, Item) =< erlang:element(1, First) of true -> [Item, First | Rest]; false -> [First | insert_sorted(Item, Rest)] end end. -file("src/metamon/transform/dict.gleam", 81). -spec sort_by_first_int(list({integer(), HNS})) -> list({integer(), HNS}). sort_by_first_int(Items) -> case Items of [] -> []; [First | Rest] -> insert_sorted(First, sort_by_first_int(Rest)) end. -file("src/metamon/transform/dict.gleam", 100). -spec drop_first_int(list({integer(), HNY})) -> list(HNY). drop_first_int(Items) -> case Items of [] -> []; [First | Rest] -> [erlang:element(2, First) | drop_first_int(Rest)] end. -file("src/metamon/transform/dict.gleam", 43). -spec assign_keys_and_sort( list({HNF, HNG}), metamon@generator@seed:seed(), integer() ) -> list({HNF, HNG}). assign_keys_and_sort(Pairs, S, Length) -> Folded = fold_assign_keys(Pairs, S, Length, []), {_, With_keys} = Folded, _pipe = With_keys, _pipe@1 = sort_by_first_int(_pipe), drop_first_int(_pipe@1). -file("src/metamon/transform/dict.gleam", 31). -spec reorder_dict(gleam@dict:dict(HMZ, HNA), metamon@generator@seed:seed()) -> gleam@dict:dict(HMZ, HNA). reorder_dict(D, S) -> Pairs = maps:to_list(D), Length = list_length(Pairs), case Length =< 1 of true -> D; false -> Shuffled = assign_keys_and_sort(Pairs, S, Length), maps:from_list(Shuffled) end. -file("src/metamon/transform/dict.gleam", 25). ?DOC( " Re-insert all entries in a deterministically shuffled order. The\n" " resulting dict is `==`-equal to the input (since `Dict` ignores\n" " insertion order), but any consumer that observes traversal order\n" " will see a different sequence — exactly the property metamorphic\n" " tests want to catch.\n" ). -spec shuffle_keys(integer()) -> metamon@transform:transform(gleam@dict:dict(any(), any())). shuffle_keys(Seed_value) -> metamon@transform:new( <<<<"dict.shuffle_keys("/utf8, (erlang:integer_to_binary(Seed_value))/binary>>/binary, ")"/utf8>>, fun(D) -> reorder_dict(D, metamon@generator@seed:seed(Seed_value)) end ).