Quickselect algorithm: Difference between revisions

Content added Content deleted
Line 2,083: Line 2,083:
{{out}}
{{out}}
<pre>{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}</pre>
<pre>{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}</pre>

=={{header|Mercury}}==
{{works with|Mercury|22.01.1}}


To more strictly follow the task instructions (which call for printing from 1st to 10th ''largest'', in order) I use '''>''' as my ordering predicate. In the generic parts of the code, the predicate is called "Less_than", but '''<''' is simply a conventional name for an ordering predicate. You can read it as meaning "comes before", or "has ordinal less than".


<lang Mercury>%%%-------------------------------------------------------------------

:- module quickselect_task.

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

:- implementation.
:- import_module array.
:- import_module exception.
:- import_module int.
:- import_module list.
:- import_module random.
:- import_module random.sfc64.
:- import_module string.

%%%-------------------------------------------------------------------
%%%
%%% Partitioning a subarray into two halves: one with elements less
%%% than or equal to a pivot, the other with elements greater than or
%%% equal to a pivot.
%%%
%%% The implementation is tail-recursive.
%%%

:- pred partition(pred(T, T), T, int, int, array(T), array(T), int).
:- mode partition(pred(in, in) is semidet, in, in, in,
array_di, array_uo, out).
partition(Less_than, Pivot, I_first, I_last, Arr0, Arr, I_pivot) :-
I = I_first - 1,
J = I_last + 1,
partition_loop(Less_than, Pivot, I, J, Arr0, Arr, I_pivot).

:- pred partition_loop(pred(T, T), T, int, int,
array(T), array(T), int).
:- mode partition_loop(pred(in, in) is semidet, in, in, in,
array_di, array_uo, out).
partition_loop(Less_than, Pivot, I, J, Arr0, Arr, Pivot_index) :-
if (I = J) then (Arr = Arr0,
Pivot_index = I)
else (I1 = I + 1,
I2 = search_right(Less_than, Pivot, I1, J, Arr0),
(if (I2 = J) then (Arr = Arr0,
Pivot_index = J)
else (J1 = J - 1,
J2 = search_left(Less_than, Pivot, I2, J1, Arr0),
swap(I2, J2, Arr0, Arr1),
partition_loop(Less_than, Pivot, I2, J2, Arr1, Arr,
Pivot_index)))).

:- func search_right(pred(T, T), T, int, int, array(T)) = int.
:- mode search_right(pred(in, in) is semidet,
in, in, in, in) = out is det.
search_right(Less_than, Pivot, I, J, Arr0) = K :-
if (I = J) then (I = K)
else if Less_than(Pivot, Arr0^elem(I)) then (I = K)
else (search_right(Less_than, Pivot, I + 1, J, Arr0) = K).

:- func search_left(pred(T, T), T, int, int, array(T)) = int.
:- mode search_left(pred(in, in) is semidet,
in, in, in, in) = out is det.
search_left(Less_than, Pivot, I, J, Arr0) = K :-
if (I = J) then (J = K)
else if Less_than(Arr0^elem(J), Pivot) then (J = K)
else (search_left(Less_than, Pivot, I, J - 1, Arr0) = K).

%%%-------------------------------------------------------------------
%%%
%%% Quickselect with a random pivot.
%%%
%%% The implementation is tail-recursive. One has to pass the routine
%%% a random number generator of type M, attached to the IO state.
%%%
%%% I use a random pivot to get O(n) worst case *expected* running
%%% time. Code using a random pivot is easy to write and read, and for
%%% most purposes comes close enough to a criterion set by Scheme's
%%% SRFI-132: "Runs in O(n) time." (See
%%% https://srfi.schemers.org/srfi-132/srfi-132.html)
%%%
%%% Of course we are not bound here by SRFI-132, but still I respect
%%% it as a guide.
%%%
%%% A "median of medians" pivot gives O(n) running time, but is more
%%% complicated. (That is, of course, assuming you are not writing
%%% your own random number generator and making it a complicated one.)
%%%

%% quickselect/8 selects the (K+1)th largest element of Arr.
:- pred quickselect(pred(T, T)::pred(in, in) is semidet, int::in,
array(T)::array_di, array(T)::array_uo,
T::out, M::in, io::di, io::uo)
is det <= urandom(M, io).
quickselect(Less_than, K, Arr0, Arr, Elem, M, !IO) :-
bounds(Arr0, I_first, I_last),
quickselect(Less_than, I_first, I_last, K, Arr0, Arr, Elem, M, !IO).

%% quickselect/10 selects the (K+1)th largest element of
%% Arr[I_first..I_last].
:- pred quickselect(pred(T, T)::pred(in, in) is semidet,
int::in, int::in, int::in,
array(T)::array_di, array(T)::array_uo,
T::out, M::in, io::di, io::uo)
is det <= urandom(M, io).
quickselect(Less_than, I_first, I_last, K, Arr0, Arr, Elem, M, !IO) :-
if (0 =< K, K =< I_last - I_first)
then (K_adjusted_for_range = K + I_first,
quickselect_loop(Less_than, I_first, I_last,
K_adjusted_for_range,
Arr0, Arr, Elem, M, !IO))
else throw("out of range").

:- pred quickselect_loop(pred(T, T)::pred(in, in) is semidet,
int::in, int::in, int::in,
array(T)::array_di, array(T)::array_uo,
T::out, M::in, io::di, io::uo)
is det <= urandom(M, io).
quickselect_loop(Less_than, I_first, I_last, K,
Arr0, Arr, Elem, M, !IO) :-
if (I_first = I_last) then (Arr = Arr0,
Elem = Arr0^elem(I_first))
else (uniform_int_in_range(M, I_first, I_last - I_first + 1,
I_pivot, !IO),
Pivot = Arr0^elem(I_pivot),

%% Move the last element to where the pivot had been. Perhaps
%% the pivot was already the last element, of course. In any
%% case, we shall partition only from I_first to I_last - 1.
Elem_last = Arr0^elem(I_last),
Arr1 = (Arr0^elem(I_pivot) := Elem_last),

%% Partition the array in the range I_first..I_last - 1,
%% leaving out the last element (which now can be considered
%% garbage).
partition(Less_than, Pivot, I_first, I_last - 1, Arr1, Arr2,
I_final),

%% Now everything that is less than the pivot is to the left
%% of I_final.

%% Put the pivot at I_final, moving the element that had been
%% there to the end. If I_final = I_last, then this element is
%% actually garbage and will be overwritten with the pivot,
%% which turns out to be the greatest element. Otherwise, the
%% moved element is not less than the pivot and so the
%% partitioning is preserved.
Elem_to_move = Arr2^elem(I_final),
Arr3 = (Arr2^elem(I_last) := Elem_to_move),
Arr4 = (Arr3^elem(I_final) := Pivot),

%% Compare I_final and K, to see what to do next.
(if (I_final < K)
then quickselect_loop(Less_than, I_final + 1, I_last, K,
Arr4, Arr, Elem, M, !IO)
else if (K < I_final)
then quickselect_loop(Less_than, I_first, I_final - 1, K,
Arr4, Arr, Elem, M, !IO)
else (Arr = Arr4,
Elem = Arr4^elem(I_final)))).

%%%-------------------------------------------------------------------

:- func example_numbers = list(int).
example_numbers = [9, 8, 7, 6, 5, 0, 1, 2, 3, 4].

main(!IO) :-
(sfc64.init(P, S)),
make_io_urandom(P, S, M, !IO),
Print_kth_largest = (pred(K::in, di, uo) is det -->
print_kth_largest(K, example_numbers, M)),
print_line("", !IO),
print_line("In order from greatest to least:", !IO),
print_line("", !IO),
print_line(" nth value", !IO),
foldl(Print_kth_largest, 1 `..` 10, !IO),
print_line("", !IO).

:- pred print_kth_largest(int::in, list(int)::in,
M::in, io::di, io::uo)
is det <= urandom(M, io).
print_kth_largest(K, Numbers_list, M, !IO) :-
(array.from_list(Numbers_list, Arr0)),

%% Notice that the "Less_than" predicate is actually "greater
%% than". :) One can think of this as meaning that a greater number
%% has an *ordinal* that is "less than"; that is, it "comes before"
%% in the order.
quickselect(>, K - 1, Arr0, _, Elem, M, !IO),

format(" %2d %d\n", [i(K), i(Elem)], !IO).

%%%-------------------------------------------------------------------
%%% local variables:
%%% mode: mercury
%%% prolog-indent-width: 2
%%% end:</lang>

{{out}}
<pre>$ mmc quickselect_task.m && ./quickselect_task

In order from greatest to least:

nth value
1 9
2 8
3 7
4 6
5 5
6 4
7 3
8 2
9 1
10 0
</pre>


=={{header|NetRexx}}==
=={{header|NetRexx}}==