Hare by Example: Sorting

Hare comes with sorting as part of its standard library for sorting slices comprised of built-in types. However, we can specify our own comparison functions to sort our own custom types if we need to.

use fmt;
use sort;
use sort::cmp;

// Our fighter struct with various fields that encompass a fighter.
type fighter = struct {
	first_name: str,
	last_name: str,
	height: u32,
	weight: u32,
	epithet: str,
};

// Custom sort::cmpfunc to sort by a fighter's last name
fn cmplastname(a: const *opaque, b: const *opaque) int = {
	// First we need to cast the *opaque arguments to a pointer
	// of our righter struct so we can access the last_name field.
	const a = *(a: *fighter);
	const b = *(b: *fighter);

	// Now we can use the standard library function for comparing
	// strings and pass it pointers to the fighter's last name.
	return cmp::strs(&a.last_name, &b.last_name);
};

// Similar to last name, but this time we use epithet
fn cmpepithet(a: const *opaque, b: const *opaque) int = {
	const a = *(a: *fighter);
	const b = *(b: *fighter);

	return cmp::strs(&a.epithet, &b.epithet);
};

// A little more complicating sorting methof of first comparing the
// fighter's height and weight separately and then lastly we compare
// the results of those comparisons.
fn cmpstats(a: const *opaque, b: const *opaque) int = {
	const a = *(a: *fighter);
	const b = *(b: *fighter);

	const h = cmp::u32s(&a.height, &b.height);
	const w = cmp::u32s(&a.weight, &b.weight);

	return cmp::ints(&h, &w);
};

fn print(fighters: []fighter) void = {
	for (let f .. fighters) {
		fmt::printf("\t{} {} ({}cm, {}kg) aka {}\n", f.first_name,
			f.last_name, f.height, f.weight, f.epithet)!;
	};
	fmt::println()!;
};

export fn main() void = {
	let ints: []int = [4, 2, 10, 100, 2, -1];

	// sort takes our slice of ints, but since it expects and
	// `opaque` slice we need to tell it the size of an int in
	// memory and which sort::cmpfunc to use to properly sort the
	// data.
	sort::sort(ints, size(int), &cmp::ints)!;

	for (let i .. ints) {
		fmt::print(i, " ")!;
	};
	fmt::println()!;
	fmt::println()!;

	// Now we have a slice of our custom fighter structs with some
	// fighters initialized and we want to sort them different ways.
	let fighters: []fighter = [
		fighter {
			first_name = "Tokita",
			last_name = "Ōma",
			height = 182,
			weight = 85,
			epithet = "The Asura",
		},
		fighter {
			first_name = "Kuroki",
			last_name = "Gensai",
			height = 185,
			weight = 96,
			epithet = "The Devil Lance",
		},
		fighter {
			first_name = "Kanō",
			last_name = "Agito",
			height = 201,
			weight = 128,
			epithet = "The Fifth Fang of Metsudo",
		},
		fighter {
			first_name = "Kure",
			last_name = "Raian",
			height = 188,
			weight = 94,
			epithet = "The Devil",
		},
	];

	// We use the same sort function, but this time pass in the
	// size of the fighter struct and a custom sort::cmpfunc so
	// sorting uses that comparator to decide which fighter is less
	// than another.
	//
	// First we sort by last_name
	sort::sort(fighters, size(fighter), &cmplastname)!;
	fmt::println("Sorted by last_name:")!;
	print(fighters);

	// Next we sort the same slice by epithet
	sort::sort(fighters, size(fighter), &cmpepithet)!;
	fmt::println("Sorted by epithet:")!;
	print(fighters);

	// Lastly, we sort by combined stats
	sort::sort(fighters, size(fighter), &cmpstats)!;
	fmt::println("Sorted by combined stats:")!;
	print(fighters);
};
$ hare run sorting.ha
-1  2  2  4  10  100  

Sorted by last_name:
        Kanō Agito (201cm, 128kg) aka The Fifth Fang of Metsudo
        Kuroki Gensai (185cm, 96kg) aka The Devil Lance
        Kure Raian (188cm, 94kg) aka The Devil
        Tokita Ōma (182cm, 85kg) aka The Asura

Sorted by epithet:
        Tokita Ōma (182cm, 85kg) aka The Asura
        Kure Raian (188cm, 94kg) aka The Devil
        Kuroki Gensai (185cm, 96kg) aka The Devil Lance
        Kanō Agito (201cm, 128kg) aka The Fifth Fang of Metsudo

Sorted by combined stats:
        Tokita Ōma (182cm, 85kg) aka The Asura
        Kuroki Gensai (185cm, 96kg) aka The Devil Lance
        Kure Raian (188cm, 94kg) aka The Devil
        Kanō Agito (201cm, 128kg) aka The Fifth Fang of Metsudo