%%% @doc %%% Tuple operations. %%% %%% This module provides functions for working with tuples, including creation, %%% modification, and conversion operations. Tuples are intended as fixed-size %%% containers for multiple elements with constant-time access but linear-time %%% modification. %%% %%% Note: The following functions for tuples are found in Erlang BIFs: %%% - element/2 - accesses a tuple by index (1-based) %%% - setelement/3 - updates a value in a tuple by index (1-based) %%% - tuple_size/1 - gets the number of elements in a tuple %%% %%% Examples: %%% ``` %%% % Create a tuple with duplicated elements %%% {hello, hello, hello} = tuple:duplicate(hello, 3), %%% %%% % Insert element at specific position (0-based indexing) %%% {foo, bar, baz} = tuple:insert_at({bar, baz}, 0, foo), %%% %%% % Convert tuple to list %%% [foo, bar, baz] = tuple:to_list({foo, bar, baz}), %%% %%% % Compute sum and product %%% 6 = tuple:sum({1, 2, 3}), %%% 24 = tuple:product({2, 3, 4}). %%% ''' %%% @end -module(tuple). %% API -export([duplicate/2, insert_at/3, delete_at/2, append/2]). -export([to_list/1, from_list/1]). -export([sum/1, product/1]). -export([reverse/1, zip/2, unzip/1]). -export([map/2, filter/2, all/2, any/2]). -export([elem/2, put_elem/3, size/1]). %% Type specifications -type tuple_index() :: non_neg_integer(). -export_type([tuple_index/0]). %%% @doc %%% Creates a new tuple. %%% %%% Creates a tuple of Size containing the given Data at every position. %%% Equivalent to Erlang's make_tuple/2. %%% %%% Examples: %%% ``` %%% {hello, hello, hello} = tuple:duplicate(hello, 3), %%% {} = tuple:duplicate(anything, 0), %%% {42} = tuple:duplicate(42, 1). %%% ''' %%% @end -spec duplicate(term(), non_neg_integer()) -> tuple(). duplicate(Data, Size) when is_integer(Size), Size >= 0 -> erlang:make_tuple(Size, Data); duplicate(_, Size) when is_integer(Size), Size < 0 -> error(badarg); duplicate(_, _) -> error(badarg). %%% @doc %%% Inserts an element into a tuple. %%% %%% Inserts Value into Tuple at the given Index (0-based). %%% Raises badarg if Index is negative or greater than the length of Tuple. %%% %%% Examples: %%% ``` %%% {foo, bar, baz} = tuple:insert_at({bar, baz}, 0, foo), %%% {bar, baz, bong} = tuple:insert_at({bar, baz}, 2, bong). %%% ''' %%% @end -spec insert_at(tuple(), tuple_index(), term()) -> tuple(). insert_at(Tuple, Index, Value) when is_tuple(Tuple), is_integer(Index), Index >= 0 -> Size = tuple_size(Tuple), if Index =< Size -> erlang:insert_element(Index + 1, Tuple, Value); true -> error(badarg) end; insert_at(_, _, _) -> error(badarg). %%% @doc %%% Removes an element from a tuple. %%% %%% Deletes the element at the given Index (0-based) from Tuple. %%% Raises badarg if Index is negative or greater than or equal to the length of Tuple. %%% %%% Examples: %%% ``` %%% {bar, baz} = tuple:delete_at({foo, bar, baz}, 0), %%% {foo, baz} = tuple:delete_at({foo, bar, baz}, 1). %%% ''' %%% @end -spec delete_at(tuple(), tuple_index()) -> tuple(). delete_at(Tuple, Index) when is_tuple(Tuple), is_integer(Index), Index >= 0 -> Size = tuple_size(Tuple), if Index < Size -> erlang:delete_element(Index + 1, Tuple); true -> error(badarg) end; delete_at(_, _) -> error(badarg). %%% @doc %%% Appends an element to the end of a tuple. %%% %%% Adds Value as the last element of Tuple, creating a new tuple. %%% Equivalent to insert_at(Tuple, tuple_size(Tuple), Value). %%% %%% Examples: %%% ``` %%% {foo, bar, baz} = tuple:append({foo, bar}, baz), %%% {hello} = tuple:append({}, hello). %%% ''' %%% @end -spec append(tuple(), term()) -> tuple(). append(Tuple, Value) when is_tuple(Tuple) -> erlang:append_element(Tuple, Value); append(_, _) -> error(badarg). %%% @doc %%% Converts a tuple to a list. %%% %%% Returns a new list with all the tuple elements in the same order. %%% Equivalent to Erlang's tuple_to_list/1. %%% %%% Examples: %%% ``` %%% [foo, bar, baz] = tuple:to_list({foo, bar, baz}), %%% [] = tuple:to_list({}). %%% ''' %%% @end -spec to_list(tuple()) -> list(). to_list(Tuple) when is_tuple(Tuple) -> erlang:tuple_to_list(Tuple); to_list(_) -> error(badarg). %%% @doc %%% Converts a list to a tuple. %%% %%% Returns a new tuple with all the list elements in the same order. %%% Equivalent to Erlang's list_to_tuple/1. %%% %%% Examples: %%% ``` %%% {foo, bar, baz} = tuple:from_list([foo, bar, baz]), %%% {} = tuple:from_list([]). %%% ''' %%% @end -spec from_list(list()) -> tuple(). from_list(List) when is_list(List) -> erlang:list_to_tuple(List); from_list(_) -> error(badarg). %%% @doc %%% Computes the sum of tuple elements. %%% %%% Returns the sum of all numeric elements in the tuple. %%% Returns 0 for an empty tuple. %%% %%% Examples: %%% ``` %%% 510 = tuple:sum({255, 255}), %%% 255.0 = tuple:sum({255, 0.0}), %%% 0 = tuple:sum({}). %%% ''' %%% @end -spec sum(tuple()) -> number(). sum(Tuple) when is_tuple(Tuple) -> sum_impl(Tuple, tuple_size(Tuple)); sum(_) -> error(badarg). %%% @doc %%% Computes the product of tuple elements. %%% %%% Returns the product of all numeric elements in the tuple. %%% Returns 1 for an empty tuple. %%% %%% Examples: %%% ``` %%% 65025 = tuple:product({255, 255}), %%% 255.0 = tuple:product({255, 1.0}), %%% 1 = tuple:product({}). %%% ''' %%% @end -spec product(tuple()) -> number(). product(Tuple) when is_tuple(Tuple) -> product_impl(Tuple, tuple_size(Tuple)); product(_) -> error(badarg). %%% @doc %%% Reverses the elements of a tuple. %%% %%% Returns a new tuple with elements in reverse order. %%% %%% Examples: %%% ``` %%% {c, b, a} = tuple:reverse({a, b, c}), %%% {} = tuple:reverse({}). %%% ''' %%% @end -spec reverse(tuple()) -> tuple(). reverse(Tuple) when is_tuple(Tuple) -> List = erlang:tuple_to_list(Tuple), erlang:list_to_tuple(lists:reverse(List)); reverse(_) -> error(badarg). %%% @doc %%% Zips two tuples into a tuple of pairs. %%% %%% Combines two tuples element-wise into pairs. If the tuples have different %%% sizes, the result will have the size of the smaller tuple. %%% %%% Examples: %%% ``` %%% {{a, 1}, {b, 2}, {c, 3}} = tuple:zip({a, b, c}, {1, 2, 3}), %%% {{a, 1}, {b, 2}} = tuple:zip({a, b}, {1, 2, 3}). %%% ''' %%% @end -spec zip(tuple(), tuple()) -> tuple(). zip(Tuple1, Tuple2) when is_tuple(Tuple1), is_tuple(Tuple2) -> Size1 = tuple_size(Tuple1), Size2 = tuple_size(Tuple2), MinSize = min(Size1, Size2), zip_impl(Tuple1, Tuple2, 1, MinSize, []); zip(_, _) -> error(badarg). %%% @doc %%% Unzips a tuple of pairs into two tuples. %%% %%% Separates a tuple of pairs into two tuples. %%% %%% Examples: %%% ``` %%% {{a, b, c}, {1, 2, 3}} = tuple:unzip({{a, 1}, {b, 2}, {c, 3}}), %%% {{}, {}} = tuple:unzip({}). %%% ''' %%% @end -spec unzip(tuple()) -> {tuple(), tuple()}. unzip(Tuple) when is_tuple(Tuple) -> unzip_impl(Tuple, 1, tuple_size(Tuple), [], []); unzip(_) -> error(badarg). %%% @doc %%% Maps a function over all elements of a tuple. %%% %%% Applies Fun to each element of Tuple and returns a new tuple with the results. %%% %%% Examples: %%% ``` %%% {2, 4, 6} = tuple:map(fun(X) -> X * 2 end, {1, 2, 3}), %%% {} = tuple:map(fun(X) -> X end, {}). %%% ''' %%% @end -spec map(fun((term()) -> term()), tuple()) -> tuple(). map(Fun, Tuple) when is_function(Fun, 1), is_tuple(Tuple) -> List = erlang:tuple_to_list(Tuple), MappedList = lists:map(Fun, List), erlang:list_to_tuple(MappedList); map(_, _) -> error(badarg). %%% @doc %%% Filters elements of a tuple based on a predicate. %%% %%% Returns a new tuple containing only elements for which Fun returns true. %%% %%% Examples: %%% ``` %%% {2, 4} = tuple:filter(fun(X) -> X rem 2 =:= 0 end, {1, 2, 3, 4}), %%% {} = tuple:filter(fun(_) -> false end, {1, 2, 3}). %%% ''' %%% @end -spec filter(fun((term()) -> boolean()), tuple()) -> tuple(). filter(Fun, Tuple) when is_function(Fun, 1), is_tuple(Tuple) -> List = erlang:tuple_to_list(Tuple), FilteredList = lists:filter(Fun, List), erlang:list_to_tuple(FilteredList); filter(_, _) -> error(badarg). %%% @doc %%% Tests whether all elements of a tuple satisfy a predicate. %%% %%% Returns true if Fun returns true for all elements, false otherwise. %%% Returns true for an empty tuple. %%% %%% Examples: %%% ``` %%% true = tuple:all(fun(X) -> X > 0 end, {1, 2, 3}), %%% false = tuple:all(fun(X) -> X > 2 end, {1, 2, 3}), %%% true = tuple:all(fun(_) -> false end, {}). %%% ''' %%% @end -spec all(fun((term()) -> boolean()), tuple()) -> boolean(). all(Fun, Tuple) when is_function(Fun, 1), is_tuple(Tuple) -> all_impl(Fun, Tuple, 1, tuple_size(Tuple)); all(_, _) -> error(badarg). %%% @doc %%% Tests whether any element of a tuple satisfies a predicate. %%% %%% Returns true if Fun returns true for at least one element, false otherwise. %%% Returns false for an empty tuple. %%% %%% Examples: %%% ``` %%% true = tuple:any(fun(X) -> X > 2 end, {1, 2, 3}), %%% false = tuple:any(fun(X) -> X > 5 end, {1, 2, 3}), %%% false = tuple:any(fun(_) -> true end, {}). %%% ''' %%% @end -spec any(fun((term()) -> boolean()), tuple()) -> boolean(). any(Fun, Tuple) when is_function(Fun, 1), is_tuple(Tuple) -> any_impl(Fun, Tuple, 1, tuple_size(Tuple)); any(_, _) -> error(badarg). %%% @doc %%% Gets an element from a tuple by index. %%% %%% Returns the element at the given Index (0-based) in Tuple. %%% Raises badarg if Index is out of bounds. %%% %%% Examples: %%% ``` %%% foo = tuple:elem({foo, bar, baz}, 0), %%% baz = tuple:elem({foo, bar, baz}, 2). %%% ''' %%% @end -spec elem(tuple(), tuple_index()) -> term(). elem(Tuple, Index) when is_tuple(Tuple), is_integer(Index), Index >= 0 -> Size = tuple_size(Tuple), if Index < Size -> erlang:element(Index + 1, Tuple); true -> error(badarg) end; elem(_, _) -> error(badarg). %%% @doc %%% Puts an element in a tuple by index. %%% %%% Returns a new tuple with Value at the given Index (0-based). %%% Raises badarg if Index is out of bounds. %%% %%% Examples: %%% ``` %%% {hello, bar, baz} = tuple:put_elem({foo, bar, baz}, 0, hello), %%% {foo, bar, world} = tuple:put_elem({foo, bar, baz}, 2, world). %%% ''' %%% @end -spec put_elem(tuple(), tuple_index(), term()) -> tuple(). put_elem(Tuple, Index, Value) when is_tuple(Tuple), is_integer(Index), Index >= 0 -> Size = tuple_size(Tuple), if Index < Size -> erlang:setelement(Index + 1, Tuple, Value); true -> error(badarg) end; put_elem(_, _, _) -> error(badarg). %%% @doc %%% Gets the size of a tuple. %%% %%% Returns the number of elements in Tuple. %%% Equivalent to Erlang's tuple_size/1. %%% %%% Examples: %%% ``` %%% 3 = tuple:size({foo, bar, baz}), %%% 0 = tuple:size({}). %%% ''' %%% @end -spec size(tuple()) -> non_neg_integer(). size(Tuple) when is_tuple(Tuple) -> tuple_size(Tuple); size(_) -> error(badarg). %%%============================================================================= %%% Internal functions %%%============================================================================= %% @private sum_impl(_Tuple, 0) -> 0; sum_impl(Tuple, Index) -> erlang:element(Index, Tuple) + sum_impl(Tuple, Index - 1). %% @private product_impl(_Tuple, 0) -> 1; product_impl(Tuple, Index) -> erlang:element(Index, Tuple) * product_impl(Tuple, Index - 1). %% @private zip_impl(_Tuple1, _Tuple2, Index, MaxIndex, Acc) when Index > MaxIndex -> erlang:list_to_tuple(lists:reverse(Acc)); zip_impl(Tuple1, Tuple2, Index, MaxIndex, Acc) -> Elem1 = erlang:element(Index, Tuple1), Elem2 = erlang:element(Index, Tuple2), zip_impl(Tuple1, Tuple2, Index + 1, MaxIndex, [{Elem1, Elem2} | Acc]). %% @private unzip_impl(_Tuple, Index, MaxIndex, Acc1, Acc2) when Index > MaxIndex -> {erlang:list_to_tuple(lists:reverse(Acc1)), erlang:list_to_tuple(lists:reverse(Acc2))}; unzip_impl(Tuple, Index, MaxIndex, Acc1, Acc2) -> {Elem1, Elem2} = erlang:element(Index, Tuple), unzip_impl(Tuple, Index + 1, MaxIndex, [Elem1 | Acc1], [Elem2 | Acc2]). %% @private all_impl(_Fun, _Tuple, Index, Size) when Index > Size -> true; all_impl(Fun, Tuple, Index, Size) -> case Fun(erlang:element(Index, Tuple)) of true -> all_impl(Fun, Tuple, Index + 1, Size); false -> false end. %% @private any_impl(_Fun, _Tuple, Index, Size) when Index > Size -> false; any_impl(Fun, Tuple, Index, Size) -> case Fun(erlang:element(Index, Tuple)) of true -> true; false -> any_impl(Fun, Tuple, Index + 1, Size) end.