:- module(_,_).

% - The first version, from the previous slide:

x2x3([], []).
x2x3([X|Xs], [NX|NXs]) :-
    NX is -X * 2,
    X < 0,
    x2x3(Xs, NXs).
x2x3([X|Xs], [NX|NXs]) :-
    NX is X * 3,
    X >= 0,
    x2x3(Xs, NXs).

% - Delaying head unification and adding determinism:

x2x3_2([], []).
x2x3_2([X|Xs], Out) :-
    X < 0, !,
    NX is -X * 2,
    Out = [NX|NXs],
    x2x3_2(Xs, NXs).
x2x3_2([X|Xs], Out) :-
    X >= 0, !,
    NX is X * 3,
    Out = [NX|NXs],
    x2x3_2(Xs, NXs).

%   Try:
% ?- x2x3_2([1,-2,3],L).

% - The speedup, with a list of `N` positive and negative numbers (`N` even):

test(N) :-
    numbers(N, L),
    speedup(x2x3(L,_), x2x3_2(L,_)).

% ?- test(100000).

numbers(0, []).
numbers(N, [N,M|Ns]) :-
    N > 0,
    M is -N,
    N1 is N - 2,
    numbers(N1, Ns).

% - `speedup/2` runs the two goals and compares their times, as in
%   the first example (`010_swap.pl`).

%! \begin{hide}
speedup(Slow, Fast) :-
    speedup_loop(true, Slow, Fast).

speedup_loop(Loop, Slow, Fast) :-
    time_of(Loop, Slow, TS),
    time_of(Loop, Fast, TF),
    show_speedup(TS, TF).

time_of(Loop, Goal, T) :-
    repeat_time(1, Loop, Goal, T).

repeat_time(N, Loop, Goal, T) :-
    loop_time(N, Goal, TG),
    (   TG >= 100
    ->  loop_time(N, Loop, TL),
        (   TG - TL > TG / 10        % (else too fast to tell
        ->  T is (TG - TL) / N       %  from the loop itself)
        ;   T = 0
        )
    ;   N1 is N * 10,
        repeat_time(N1, Loop, Goal, T)
    ).

loop_time(N, Goal, T) :-
    statistics(runtime, [T0, _]),
    ( between(1, N, _), once(Goal), fail ; true ),
    statistics(runtime, [T1, _]),
    T is T1 - T0.

show_speedup(TS, TF) :-
    format("Slow: ", []), show_time(TS),
    format(", fast: ", []), show_time(TF),
    (   TS > 0, TF > 0
    ->  S is TS / TF,
        format(", speedup: ~1f~n", [S])
    ;   nl
    ).

show_time(T) :-
    T > 0, !,
    format("~4f ms", [T]).
show_time(_) :-
    format("too fast to measure", []).

:- use_module(library(between)).
%! \end{hide}
