Skip to content

Instantly share code, notes, and snippets.

@cobusc
Last active December 18, 2015 22:49
Show Gist options
  • Save cobusc/5856819 to your computer and use it in GitHub Desktop.
Save cobusc/5856819 to your computer and use it in GitHub Desktop.
Linear to non-linear sequence mapper and base62 encoder.
-module(map).
-export([map/1,
to_base_62/1,
test/1]).
%%
%% @doc Deterministically map a linear integer sequence to a non-linear sequence.
%%
-spec map(N::non_neg_integer()) -> string().
map(N)
when is_integer(N), N >= 0 ->
% Get LSB representation. It makes matching easier.
LsbBin = <<N:128/little-unsigned-integer>>,
% Extract the first 3 bytes, as well as the rest.
<<A:8,B:8,C:8,D/binary>> = LsbBin,
% Get a permutation of the first 3 bytes. There are only 5 variants,
% since we do not use the trivial case (A, B, C).
Val =
case N rem 5 of
0 -> <<A:8, C:8, B:8, D/binary>>;
1 -> <<B:8, A:8, C:8, D/binary>>;
2 -> <<B:8, C:8, A:8, D/binary>>;
3 -> <<C:8, A:8, B:8, D/binary>>;
4 -> <<C:8, B:8, A:8, D/binary>>
end,
% Convert permutation back to an integer representation
binary:decode_unsigned(Val, little).
%%
%% @doc Integer to base62 encoding
%%
-spec to_base_62(N::non_neg_integer()) -> string().
to_base_62(N)
when is_integer(N) ->
to_base_62(N, []).
to_base_62(0=N, []) ->
c(N);
to_base_62(0, Acc) ->
lists:map(fun c/1, Acc);
to_base_62(N, Acc) ->
to_base_62(N div 62, [N rem 62 | Acc]).
c(N) when is_integer(N), 0=<N, N=<9 ->
$0 + N;
c(N) when is_integer(N), 10=<N, N=<35 ->
$a + (N-10) ;
c(N) when is_integer(N), 36=<N, N=<61 ->
$A + (N-36).
%%
%% @doc A simple test function.
%%
test(N) ->
Sample = lists:seq(1,N),
Values = [ map(X) || X <- Sample ],
F = fun(X) ->
io:format("~s~n", [to_base_62(X)])
end,
ok = lists:foreach(F, Values),
Uniques = lists:usort(Values),
io:format("Number of duplicates in first ~B values = ~B~n", [N, N - erlang:length(Uniques)]).
%% Erlang R14B04 (erts-5.8.5) [source] [smp:2:2] [rq:2] [async-threads:0] [hipe] [kernel-poll:false]
%%
%% Eshell V5.8.5 (abort with ^G)
%% 1> c(map).
%% {ok,map}
%% 2> map:test(10).
%% 48
%% y64
%% co
%% 16c8
%% 5
%% oM
%% 1Vle
%% x2
%% 2tri
%% a
%% Number of duplicates in first 10 values = 0
%% ok
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment