Sorting

We can use std.sort to sort the items in a slice or ArrayList:

const std = @import("std");
const print = std.debug.print;

// Sorting always uses a callback function that compares
// two values and returns true if the first is less than
// the second. Let’s define one that compares two
// strings by length.
fn byLen(
    _: void,
    lhs: []const u8,
    rhs: []const u8,
) bool {
    return lhs.len < rhs.len;
}

// The first argument is a “context”, which can be used
// to parameterize the function.
fn byBytes(
    ascending: bool,
    lhs: []const u8,
    rhs: []const u8,
) bool {
    // std.mem.lessThan compares two slices
    // byte-by-byte. For simple cases, that gives us
    // alphabetical order.
    return if (ascending)
        std.mem.lessThan(u8, lhs, rhs)
    else
        std.mem.lessThan(u8, rhs, lhs);
}

pub fn main() void {
    var names: [10][]const u8 = .{
        "Olivia",
        "Amelia",
        "Isla",
        "Poppy",
        "Freya",
        "Ivy",
        "Bonnie",
        "Lottie",
        "Harper",
        "Lily",
    };
    const ns: [][]const u8 = &names;

    // std.sort.pdq implements pattern-defeating
    // quicksort. Its arguments are the element type,
    // the slice, the context, and the callback
    // function.
    //
    // {} is a value of type 'void'.
    printNames("original:", ns);
    std.sort.pdq([]const u8, ns, true, byBytes);
    printNames("sorted ascending:", ns);
    std.sort.pdq([]const u8, ns, false, byBytes);
    printNames("sorted descending:", ns);
    std.sort.pdq([]const u8, ns, {}, byLen);
    printNames("sorted by length:", ns);
}

fn printNames(
    label: []const u8,
    ns: [][]const u8,
) void {
    print("{s}", .{label});
    for (ns) |n|
        print("\n    {s}", .{n});
    print("\n", .{});
}
$ zig run sorting.zig 
original:
    Olivia
    Amelia
    Isla
    Poppy
    Freya
    Ivy
    Bonnie
    Lottie
    Harper
    Lily
sorted ascending:
    Amelia
    Bonnie
    Freya
    Harper
    Isla
    Ivy
    Lily
    Lottie
    Olivia
    Poppy
sorted descending:
    Poppy
    Olivia
    Lottie
    Lily
    Ivy
    Isla
    Harper
    Freya
    Bonnie
    Amelia
sorted by length:
    Ivy
    Lily
    Isla
    Poppy
    Freya
    Olivia
    Lottie
    Harper
    Bonnie
    Amelia

Sorting numbers

Sorting numbers is a bit simpler:

const std = @import("std");
const print = std.debug.print;

pub fn main() void {
    var integers: [10]u64 = .{
        1, 5, 3, 9, 8, 0, 2, 4, 7, 6,
    };
    const is: []u64 = &integers;

    // We can use std.sort.asc and std.sort.desc to
    // create callback functions.
    printIntegers("original:     ", is);
    std.sort.pdq(u64, is, {}, std.sort.asc(u64));
    printIntegers("sorted (asc): ", is);
    std.sort.pdq(u64, is, {}, std.sort.desc(u64));
    printIntegers("sorted (desc):", is);
}

fn printIntegers(label: []const u8, is: []u64) void {
    print("{s}", .{label});
    for (is) |i|
        print(" {}", .{i});
    print("\n", .{});
}
$ zig run sorting-2.zig 
original:      1 5 3 9 8 0 2 4 7 6
sorted (asc):  0 1 2 3 4 5 6 7 8 9
sorted (desc): 9 8 7 6 5 4 3 2 1 0

Custom types

Let’s take a look at sorting values of a custom type:

const std = @import("std");
const print = std.debug.print;

const User = struct {
    name: []const u8,
    is_admin: bool,

    fn regular(name: []const u8) User {
        return .{
            .name = name,
            .is_admin = false,
        };
    }
    fn admin(name: []const u8) User {
        return .{
            .name = name,
            .is_admin = true,
        };
    }

    // We can add comparison functions to the type
    // itself.
    fn byName(_: void, lhs: User, rhs: User) bool {
        return std.mem.lessThan(u8, lhs.name, rhs.name);
    }
    fn adminsFirst(_: void, lhs: User, rhs: User) bool {
        return lhs.is_admin and !rhs.is_admin;
    }
};

pub fn main() void {
    // We’ll create four users and sort them by name,
    // then sort again to put admin users first.
    var users: [4]User = .{
        User.regular("olivia"),
        User.regular("amelia"),
        User.regular("isla"),
        User.admin("poppy"),
    };
    const us: []User = &users;

    // Notice in the output that sorting with
    // adminsFirst left the non-admin users sorted
    // alphabetically -- that’s because we’re using
    // std.sort.block, which is a “stable sort”.
    //
    // std.sort.pdq is not guaranteed to be stable.
    printUsers("original:      ", us);
    std.sort.block(User, us, {}, User.byName);
    printUsers("sorted by name:", us);
    std.sort.block(User, us, {}, User.adminsFirst);
    printUsers("admins first:  ", us);
}

fn printUsers(
    label: []const u8,
    us: []User,
) void {
    print("{s}", .{label});
    for (us) |u|
        print(" {s}", .{u.name});
    print("\n", .{});
}
$ zig run sorting-3.zig 
original:       olivia amelia isla poppy
sorted by name: amelia isla olivia poppy
admins first:   poppy amelia isla olivia

std.sort

Let’s take a look at some other functions in std.sort:

const std = @import("std");
const print = std.debug.print;
const expect = std.testing.expect;
const expectEqual = std.testing.expectEqual;

const asc = std.sort.asc(f64);

// std.sort.isSorted returns true if a slice is sorted.
test "isSorted" {
    var floats: [10]f64 = .{
        0.5, 2.5, 1.5, 4.5, 4, 0, 1, 2, 3.5, 3,
    };
    const fs: []f64 = &floats;
    try expect(!std.sort.isSorted(f64, fs, {}, asc));
    std.sort.pdq(f64, fs, {}, asc);
    try expect(std.sort.isSorted(f64, fs, {}, asc));
}

// std.sort.min and std.sort.max find the minimum and
// maximum of a list.
test "min and max" {
    const min = std.sort.min;
    const max = std.sort.max;

    var floats: [10]f64 = .{
        0.5, 2.5, 1.5, 4.5, 4, 0, 1, 2, 3.5, 3,
    };
    const fs: []f64 = &floats;
    try expect(min(f64, fs, {}, asc) == 0);
    try expect(max(f64, fs, {}, asc) == 4.5);

    // The result is an [optional](optionals.html) value.
    const empty: []f64 = floats[0..0];
    try expect(min(f64, empty, {}, asc) == null);
    try expect(max(f64, empty, {}, asc) == null);
}

// std.sort.binarySearch searches for an element in a
// sorted slice.
test "binarySearch" {
    const binarySearch = std.sort.binarySearch;
    var floats: [10]f64 = .{
        0, 0.5, 1, 1.5, 2, 2.5, 3, 3.5, 4, 4.5,
    };
    const fs: []f64 = &floats;

    var target: f64 = 2.5;
    var index = binarySearch(f64, fs, target, order);
    try expect(index != null and fs[index.?] == 2.5);

    target = 5.5;
    index = binarySearch(f64, fs, target, order);
    try expect(index == null);
}

// The callback for binarySearch returns std.math.Order.
// For numbers, we can use the std.math.order function.
fn order(context: f64, item: f64) std.math.Order {
    return std.math.order(context, item);
}
$ zig test sorting-4.zig 
All 3 tests passed.

Next example: Random.