%---------------------------------------------------------------------------%
% vim: ts=4 sw=4 et ft=mercury
%---------------------------------------------------------------------------%
%
% Test whether list.sort is stable.
%
%---------------------------------------------------------------------------%

:- module stable_sort.
:- interface.
:- import_module io.

:- pred main(io::di, io::uo) is det.

:- implementation.

:- import_module bool.
:- import_module int.
:- import_module list.
:- import_module random.
:- import_module random.sfc16.

main(!IO) :-
    generate_random_list(List),
    % io.write_line(List, !IO),
    % io.write_line(sort(List) `with_type` list({int, int}), !IO),
    is_sort_stable(List, Stable),
    (
        Stable = yes,
        io.write_string("list.sort/3 appears stable\n", !IO)
    ;
        Stable = no,
        io.write_string("list.sort/3 is not stable\n", !IO)
    ).

:- pred generate_random_list(list({int, int})::out) is det.

generate_random_list(List) :-
    RandomState0 = random.sfc16.init,
    % We generate random integers from 0 to 9. The list length must be
    % large enough to ensure that there are plenty of duplications,
    % otherwise the test is trivial.
    Count = 100,
    generate_random_list_2(Count, [], List, RandomState0, _).

:- pred generate_random_list_2(int::in,
    list({int, int})::in, list({int, int})::out,
    RandomState::in, RandomState::out) is det <= random(RandomState).

generate_random_list_2(Count, List0, List, !RandomState) :-
    ( if Count > 0 then
        uniform_int_in_range(0, 9, R1, !RandomState),
        uniform_int_in_range(0, 9, R2, !RandomState),
        generate_random_list_2(Count - 1, [{R1, R2} | List0], List,
            !RandomState)
    else
        List = List0
    ).

:- pred is_sort_stable(list({int, int})::in, bool::out) is det.

is_sort_stable(Unsorted, Stable) :-
    % If the sort is stable, then sorting on the second component
    % followed by sorting on the first component *should* give a
    % fully sorted result.
    list.sort(compare_second, Unsorted, SortedOnSecond),
    list.sort(compare_first, SortedOnSecond, SortedOnSecondThenFirst),

    % Check whether the result is fully sorted.
    list.sort(Unsorted, Sorted),
    ( if SortedOnSecondThenFirst = Sorted then
        Stable = yes
    else
        Stable = no
    ).

:- pred compare_first({int, int}::in, {int, int}::in, comparison_result::out)
    is det.

compare_first({A, _}, {B, _}, Res) :-
    compare(Res, A, B).

:- pred compare_second({int, int}::in, {int, int}::in, comparison_result::out)
    is det.

compare_second({_, A}, {_, B}, Res) :-
    compare(Res, A, B).
