Skip to content

Commit

Permalink
[util] Add Ranges.quicksort (#277)
Browse files Browse the repository at this point in the history
  • Loading branch information
titzer authored Sep 24, 2024
1 parent f1395dc commit 817cbbd
Show file tree
Hide file tree
Showing 3 changed files with 186 additions and 14 deletions.
44 changes: 43 additions & 1 deletion lib/util/Ranges.v3
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,22 @@

// Utility methods for Ranges.
component Ranges {
// Make a copy of a range (as an array).
// Compute a hash of {r}, given a {hash} function for each element.
def hash<T>(h0: int, r: Range<T>, hash: T -> int) -> int {
for (e in r) h0 = h0 * 33 + hash(e);
return h0;
}
// Map {func} over an input {array}, returning a new array of the results.
def map<A, B>(r: Range<A>, func: A -> B) -> Array<B> {
var result = Array<B>.new(r.length);
for (i < r.length) result[i] = func(r[i]);
return result;
}
// Map {func} over an input {src}, storing the results in the {dest}.
def mapInto<A, B>(src: Range<A>, func: A -> B, dest: Range<B>) {
for (i < src.length) dest[i] = func(src[i]);
}
// Make a copy of a range as an array.
def dup<T>(r: Range<T>) -> Array<T> {
var result = Array<T>.new(r.length);
for (i < r.length) result[i] = r[i];
Expand Down Expand Up @@ -34,4 +49,31 @@ component Ranges {
}
return range;
}
// Sort the range {r} in place using Quicksort using the {cmp} function.
def quicksort<T>(r: Range<T>, cmp: (T, T) -> bool) {
if (r.length <= 1) return;
if (r.length == 2) {
var a = r[0], b = r[1];
if (cmp(b, a)) { r[0] = b; r[1] = a; }
return;
}
def pi = partition(r, cmp);
if (pi > 1) quicksort(r[0 ... pi], cmp);
if (pi < r.length) quicksort(r[pi ...], cmp);
}
private def partition<T>(r: Range<T>, cmp: (T, T) -> bool) -> int {
var i = 0, j = r.length - 1;
def pivot = r[j];
while (i <= j) {
var a = r[i];
if (cmp(a, pivot)) {
i++;
} else {
r[i] = r[j];
r[j] = a;
j--;
}
}
return i;
}
}
9 changes: 9 additions & 0 deletions lib/util/StringBuilder.v3
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,15 @@ class StringBuilder {
var l = Utf8.encode(codepoint, buf, p);
length += l;
}
// Append a range of values as [v1, v2, ...] with each element rendered with the format specifier {fmt}.
def putRange<T>(fmt: string, r: Range<T>) -> this {
putc('[');
for (i < r.length) {
if (i > 0) csp();
put1(fmt, r[i]);
}
putc(']');
}
// Append a line terminator.
def ln() -> this {
putc('\n');
Expand Down
147 changes: 134 additions & 13 deletions test/lib/RangesTest.v3
Original file line number Diff line number Diff line change
Expand Up @@ -5,36 +5,157 @@ def T = LibTests.register("Ranges", _, _);
def X = [
T("reverse_int", test_reverse_int),
T("reverse_string", test_reverse_string),
T("sort_ints", test_sort_ints),
T("sort_floats", test_sort_floats),
T("sort_strings", test_sort_strings),
T("sort_perm3", test_sort_perm3),
T("sort_perm5", test_sort_perm5),
()
];

def assert_reverse<T>(t: LibTest, expected: Range<T>, input: Range<T>) {
Ranges.reverse(input);
def putall<T>(buf: StringBuilder, r: Range<T>) -> StringBuilder {
buf.puts("[");
for (i < r.length) {
if (i > 0) buf.csp();
var v = r[i];
if (string.?(v)) buf.putsq(string.!(v));
else buf.putd(v);
}
return buf.puts("]");
}

def report<T>(t: LibTest, expected: Range<T>, got: Range<T>) {
var buf = StringBuilder.new().puts("expected: ");
putall(buf, expected);
buf.puts(", got ");
putall(buf, got);
t.fail(buf.toString());
}

def assert_func_in_place<T>(t: LibTest, func: Range<T> -> void, expected: Range<T>, input: Range<T>) {
func(input);
var got = input; // reverse is done in place

if (got.length != expected.length) return t.fail("wrong result length");
for (i < expected.length) t.asserteq(expected[i], got[i]);
for (i < expected.length) if (expected[i] != got[i]) return report(t, expected, got);
}

def test_reverse_int(t: LibTest) {
def p = assert_reverse<int>(t, _, _);
def test = assert_func_in_place<int>(t, Ranges.reverse, _, _);

p([], []);
p([1], [1]);
p([3, 4], [4, 3]);
p([-44, -55, -66], [-66, -55, -44]);
test([], []);
test([1], [1]);
test([3, 4], [4, 3]);
test([-44, -55, -66], [-66, -55, -44]);
}

def test_reverse_string(t: LibTest) {
def p = assert_reverse<string>(t, _, _);
def test = assert_func_in_place<string>(t, Ranges.reverse, _, _);
def foo = "foo", bar = "bar";
def ganz = "ganz", geschichte = "geschichte", pflanz = "pflanz", gebaut = "gebaut", danke = "danke", dir = "dir", sehr = "sehr";

p([], []);
p([bar], [bar]);
p([foo, bar], [bar, foo]);
p(
test([], []);
test([bar], [bar]);
test([foo, bar], [bar, foo]);
test(
[ganz, geschichte, pflanz, gebaut, danke, dir, sehr],
[sehr, dir, danke, gebaut, pflanz, geschichte, ganz]
);
}

def test_sort_ints(t: LibTest) {
def test = assert_func_in_place<int>(t, Ranges.quicksort(_, int.<), _, _);

test([], []);
test([1], [1]);
test([-1, 1], [1, -1]);
test([-1, 1], [-1, 1]);
test([-3, -2], [-3, -2]);
test([-3, -2], [-2, -3]);
test([0, 1, 2], [2, 0, 1]);
test([0, 1, 2], [1, 2, 0]);
test([0, 1, 2, 3], [3, 2, 0, 1]);
test([0, 1, 2, 3, 4], [3, 2, 0, 1, 4]);
test([0, 1, 2, 3, 4, 5], [5, 3, 2, 0, 1, 4]);
}

def test_sort_floats(t: LibTest) {
def test = assert_func_in_place<float>(t, Ranges.quicksort(_, float.<), _, _);

test([], []);
test([1], [1]);
test([1, 1.1f], [1, 1.1f]);
test([-1.1f, 1], [-1.1f, 1]);
test([1, float.infinity], [1, float.infinity]);
test([1, float.infinity], [float.infinity, 1]);

test([0, 1, 2], [2, 0, 1]);
test([0, 1, 2], [1, 2, 0]);
test([0, 1, 2, 3], [3, 2, 0, 1]);
test([0, 1, 2, 3, 4], [3, 2, 0, 1, 4]);
test([0, 1, 2, 3, 4, 5], [5, 3, 2, 0, 1, 4]);
}

def test_sort_strings(t: LibTest) {
def test = assert_func_in_place<string>(t, Ranges.quicksort(_, Strings.asciiLt), _, _);
def foo = "foo", bar = "bar";
def ganz = "ganz", geschichte = "geschichte", pflanz = "pflanz", gebaut = "gebaut", danke = "danke", dir = "dir", sehr = "sehr";

test([], []);
test([foo], [foo]);
test([bar, foo], [foo, bar]);
test([bar, danke, dir, ganz, gebaut, geschichte, pflanz],
[gebaut, bar, dir, ganz, geschichte, pflanz, danke]);
}

def test_sort_perm3(t: LibTest) {
def test = assert_func_in_place<i64>(t, Ranges.quicksort(_, i64.<), _, _);
def X = 0L, Y = 0x1_0000_0000L, Z = 0x1000_0000_0000_0000L;
def expected = [X, Y, Z];

test(expected, [X, Y, Z]);
test(expected, [X, Z, Y]);
test(expected, [Y, X, Z]);
test(expected, [Y, Z, X]);
test(expected, [Z, X, Y]);
test(expected, [Z, Y, X]);
}

def test_sort_perm5(t: LibTest) {
def sorted = [0uL, 1uL, 2uL, 3uL, 4uL];
def sort = Ranges.quicksort(_, u64.<);
def check = assert_func_in_place<u64>(t, sort, sorted, _);
Permuter.new(sorted).genDupTo(check);
}

class Permuter<T>(set: Range<T>) {
private def storage = Array<T>.new(set.length);

def genAll() -> Array<Array<T>> {
var vec = Vector<Array<T>>.new();
enumRec(0, 0, dupToA(_, vec.put));
return vec.extract();
}
def genTo<R>(f: Range<T> -> R) {
enumRec(0, 0, f);
}
def genDupTo<R>(f: Range<T> -> R) {
enumRec(0, 0, dupTo(_, f));
}
private def enumRec<R>(pos: int, mask: int, f: Range<T> -> R) {
if (pos == set.length) return void(f(storage));
for (i < set.length) {
var bit = 1 << u5.!(i);
if ((mask & bit) == 0) {
storage[pos] = set[i];
enumRec(pos + 1, mask | bit, f);
}
}
}
private def dupToA<R>(r: Range<T>, f: Array<T> -> R) {
f(Ranges.dup(r));
}
private def dupTo<R>(r: Range<T>, f: Range<T> -> R) {
f(Ranges.dup(r));
}
}

0 comments on commit 817cbbd

Please sign in to comment.