Sorting algorithms/Quicksort: Difference between revisions

Content added Content deleted
Line 1,958: Line 1,958:
recursion second second sort sorted sorted than than
recursion second second sort sorted sorted than than
the the the the the the the the
the the the the the the the the
the the to to two use</pre>

=== A quicksort working on arrays of linear elements ===


<lang ats>(*------------------------------------------------------------------*)
(* Quicksort in ATS2, for arrays of (possibly) linear values. *)
(*------------------------------------------------------------------*)

#include "share/atspre_staload.hats"

#define NIL list_vt_nil ()
#define :: list_vt_cons

(*------------------------------------------------------------------*)

(* A simple quicksort working on arrays of non-linear values, using
a programmer-selectible pivot.

It is based on the "in-place" task pseudocode. *)

extern fun {a : vt@ype} (* A "less-than" predicate. *)
array_quicksort$lt {px, py : addr}
(pfx : !(a @ px),
pfy : !(a @ py) |
px : ptr px,
py : ptr py) : bool

extern fun {a : vt@ype}
array_quicksort$select_pivot_index {n : int}
{i, j : nat | i < j; j < n}
(arr : &array (a, n) >> _,
first : size_t i,
last : size_t j)
: [k : nat | k < n] size_t k

extern fun {a : vt@ype}
array_quicksort {n : int}
(arr : &array (a, n) >> _,
n : size_t n) : void

(* - - - - - - - - - - - - - - - - - - - - - - *)

prfn (* Subdivide an array view into three views. *)
array_v_subdivide3 {a : vt@ype} {p : addr} {n1, n2, n3 : nat}
(pf : @[a][n1 + n2 + n3] @ p)
:<prf> @(@[a][n1] @ p,
@[a][n2] @ (p + n1 * sizeof a),
@[a][n3] @ (p + (n1 + n2) * sizeof a)) =
let
prval (pf1, pf23) =
array_v_split {a} {p} {n1 + n2 + n3} {n1} pf
prval (pf2, pf3) =
array_v_split {a} {p + n1 * sizeof a} {n2 + n3} {n2} pf23
in
@(pf1, pf2, pf3)
end

prfn (* Join three contiguous array views into one view. *)
array_v_join3 {a : vt@ype} {p : addr} {n1, n2, n3 : nat}
(pf1 : @[a][n1] @ p,
pf2 : @[a][n2] @ (p + n1 * sizeof a),
pf3 : @[a][n3] @ (p + (n1 + n2) * sizeof a))
:<prf> @[a][n1 + n2 + n3] @ p =
let
prval pf23 =
array_v_unsplit {a} {p + n1 * sizeof a} {n2, n3} (pf2, pf3)
prval pf = array_v_unsplit {a} {p} {n1, n2 + n3} (pf1, pf23)
in
pf
end

fn {a : vt@ype} (* Safely swap two elements of an array. *)
swap_elems_1 {n : int}
{i, j : nat | i < n; j < n}
{p : addr}
(pfarr : !array_v(a, p, n) >> _ |
p : ptr p,
i : size_t i,
j : size_t j) : void =
let
fn {a : vt@ype}
swap {n : int}
{i, j : nat | i < j; j < n}
{p : addr}
(pfarr : !array_v(a, p, n) >> _ |
p : ptr p,
i : size_t i,
j : size_t j) : void =
{

(* Safely swapping linear elements requires that views of
those elements be split off from the rest of the
array. Why? Because those elements will temporarily be in
an uninitialized state. (Actually they will be "?!", but
the difference is unimportant here.)

Remember, a linear value is consumed by using it.

The view for the whole array can be reassembled only after
new values have been stored, making the entire array once
again initialized.

For simplicity, we assume i < j, and handle the case j < i
separately at runtime, rather than by clever manipulation
of views. The case i = j is special, because you cannot
safely do an ordinary swap of a linear value in a single
location. You have to simply leave it alone. *)

prval @(pf1, pf2, pf3) =
array_v_subdivide3 {a} {p} {i, j - i, n - j} pfarr
prval @(pfi, pf2_) = array_v_uncons pf2
prval @(pfj, pf3_) = array_v_uncons pf3

val pi = ptr_add<a> (p, i)
and pj = ptr_add<a> (p, j)

val xi = ptr_get<a> (pfi | pi)
and xj = ptr_get<a> (pfj | pj)

val () = ptr_set<a> (pfi | pi, xj)
and () = ptr_set<a> (pfj | pj, xi)

prval pf2 = array_v_cons (pfi, pf2_)
prval pf3 = array_v_cons (pfj, pf3_)
prval () = pfarr := array_v_join3 (pf1, pf2, pf3)
}
in
if i < j then
swap {n} {i, j} {p} (pfarr | p, i, j)
else if j < i then
swap {n} {j, i} {p} (pfarr | p, j, i)
else
()
end

fn {a : vt@ype} (* Safely swap two elements of an array. *)
swap_elems_2 {n : int}
{i, j : nat | i < n; j < n}
(arr : &array(a, n) >> _,
i : size_t i,
j : size_t j) : void =
swap_elems_1 (view@ arr | addr@ arr, i, j)

overload swap_elems with swap_elems_1
overload swap_elems with swap_elems_2
overload swap with swap_elems

fn {a : vt@ype} (* Safely compare two elements of an array. *)
lt_elems_1 {n : int}
{i, j : nat | i < n; j < n}
{p : addr}
(pfarr : !array_v(a, p, n) |
p : ptr p,
i : size_t i,
j : size_t j) : bool =
let
fn
compare {n : int}
{i, j : nat | i < j; j < n}
{p : addr}
(pfarr : !array_v(a, p, n) |
p : ptr p,
i : size_t i,
j : size_t j,
gt : bool) : bool =
let
prval @(pf1, pf2, pf3) =
array_v_subdivide3 {a} {p} {i, j - i, n - j} pfarr
prval @(pfi, pf2_) = array_v_uncons pf2
prval @(pfj, pf3_) = array_v_uncons pf3

val pi = ptr_add<a> (p, i)
and pj = ptr_add<a> (p, j)

val retval =
if gt then
array_quicksort$lt<a> (pfj, pfi | pj, pi)
else
array_quicksort$lt<a> (pfi, pfj | pi, pj)

prval pf2 = array_v_cons (pfi, pf2_)
prval pf3 = array_v_cons (pfj, pf3_)
prval () = pfarr := array_v_join3 (pf1, pf2, pf3)
in
retval
end
in
if i < j then
compare {n} {i, j} {p} (pfarr | p, i, j, false)
else if j < i then
compare {n} {j, i} {p} (pfarr | p, j, i, true)
else
false
end

fn {a : vt@ype} (* Safely compare two elements of an array. *)
lt_elems_2 {n : int}
{i, j : nat | i < n; j < n}
(arr : &array (a, n),
i : size_t i,
j : size_t j) : bool =
lt_elems_1 (view@ arr | addr@ arr, i, j)

overload lt_elems with lt_elems_1
overload lt_elems with lt_elems_2

implement {a}
array_quicksort {n} (arr, n) =
let
sortdef index = {i : nat | i < n}
typedef index (i : int) = [0 <= i; i < n] size_t i
typedef index = [i : index] index i

fun
quicksort {i, j : index}
(arr : &array(a, n) >> _,
first : index i,
last : index j) : void =
if first < last then
{
val i_pivot : index =
array_quicksort$select_pivot_index<a> (arr, first, last)

fun
search_rightwards (arr : &array (a, n),
left : index) : index =
if lt_elems<a> (arr, left, i_pivot) then
let
val () = assertloc (succ left <> n)
in
search_rightwards (arr, succ left)
end
else
left

fun
search_leftwards (arr : &array (a, n),
right : index) : index =
if lt_elems<a> (arr, i_pivot, right) then
let
val () = assertloc (right <> i2sz 0)
in
search_leftwards (arr, pred right)
end
else
right

fun
partition (arr : &array (a, n) >> _,
left0 : index,
right0 : index) : @(index, index) =
let
val left = search_rightwards (arr, left0)
and right = search_leftwards (arr, right0)
in
if left <= right then
let
val () = assertloc (succ left <> n)
and () = assertloc (right <> i2sz 0)
in
swap (arr, left, right);
partition (arr, succ left, pred right)
end
else
@(left, right)
end

val @(left, right) = partition (arr, first, last)

val () = quicksort (arr, first, right)
and () = quicksort (arr, left, last)
}
in
if i2sz 2 <= n then
quicksort {0, n - 1} (arr, i2sz 0, pred n)
end

(*------------------------------------------------------------------*)

implement
array_quicksort$lt<Strptr1> (pfx, pfy | px, py) =
compare (!px, !py) < 0

implement
array_quicksort$select_pivot_index<Strptr1> {n} (arr, first, last) =
(* Median of three, with swapping around of elements during pivot
selection. See https://archive.ph/oYENx *)
let
val middle = first + ((last - first) / i2sz 2)
in
if lt_elems<Strptr1> (arr, middle, first)
xor lt_elems<Strptr1> (arr, last, first) then
begin
swap (arr, first, middle);
if lt_elems<Strptr1> (arr, last, first) then
swap (arr, first, last);
middle
end
else if lt_elems<Strptr1> (arr, middle, first)
xor lt_elems<Strptr1> (arr, middle, last) then
begin
if lt_elems<Strptr1> (arr, last, first) then
swap (arr, first, last);
middle
end
else
begin
swap (arr, middle, last);
if lt_elems<Strptr1> (arr, last, first) then
swap (arr, first, last);
middle
end
end

implement
list_vt_map$fopr<string><Strptr1> (s) = string0_copy s

implement
list_vt_freelin$clear<Strptr1> (x) = strptr_free x

implement
main0 () =
let
val example_strings =
$list_vt
("choose", "any", "element", "of", "the", "array",
"to", "be", "the", "pivot",
"divide", "all", "other", "elements", "except",
"the", "pivot", "into", "two", "partitions",
"all", "elements", "less", "than", "the", "pivot",
"must", "be", "in", "the", "first", "partition",
"all", "elements", "greater", "than", "the", "pivot",
"must", "be", "in", "the", "second", "partition",
"use", "recursion", "to", "sort", "both", "partitions",
"join", "the", "first", "sorted", "partition", "the",
"pivot", "and", "the", "second", "sorted", "partition")

val example_strptrs =
list_vt_map<string><Strptr1> (example_strings)

prval () = lemma_list_vt_param example_strptrs
val n = length example_strptrs

val @(pf, pfgc | p) = array_ptr_alloc<Strptr1> (i2sz n)
macdef arr = !p

val () = array_initize_list_vt<Strptr1> (arr, n, example_strptrs)
val () = array_quicksort<Strptr1> (arr, i2sz n)
val sorted_strptrs = array2list (arr, i2sz n)

fun
print_strptrs {n : nat} .<n>.
(strptrs : !list_vt (Strptr1, n),
i : int) : void =
case+ strptrs of
| NIL => if i <> 1 then println! () else ()
| @ head :: tail =>
begin
print! head;
if i = 8 then
begin
println! ();
print_strptrs (tail, 1)
end
else
begin
print! " ";
print_strptrs (tail, succ i)
end;
fold@ strptrs
end
in
println! (length example_strings);
println! (length sorted_strptrs);
print_strptrs (sorted_strptrs, 1);
list_vt_freelin<Strptr1> sorted_strptrs;
array_ptr_free (pf, pfgc | p);
list_vt_free<string> example_strings
end

(*------------------------------------------------------------------*)</lang>

{{out}}
<pre>$ patscc -O3 -DATS_MEMALLOC_LIBC quicksort_task_for_arrays_2.dats && ./a.out
62
62
all all all and any array be be
be both choose divide elements element elements elements
except first first in into less must partitions
pivot pivot than the greater in join must
of other partition partition partition pivot partition partitions
pivot pivot second recursion second sort sorted than
the sorted the the the the the the
the the to to two use</pre>
the the to to two use</pre>