amtoaer

晓风残月

叹息似的渺茫,你仍要保存着那真!
github
x
telegram
steam
nintendo switch
email

Sorting package based on Go generics

In the previous article, I discovered a repository that provides a deep explanation of generics: go-generics-the-hard-way. After reading it briefly, I finally managed to implement sorting for slices of multiple types. (I'm so bad at this!)

Theoretical Basis#

1. Generic constraints can be defined by specifying a collection of types.

// Numeric expresses a type constraint satisfied by any numeric type.
type Numeric interface {
	uint | uint8 | uint16 | uint32 | uint64 |
		int | int8 | int16 | int32 | int64 |
		float32 | float64 |
		complex64 | complex128
}

// Sum returns the sum of the provided arguments.
func Sum[T Numeric](args ...T) T {
	var sum T
	for i := 0; i < len(args); i++ {
		sum += args[i]
	}
	return sum
}

2. The tilde (~) prefix in a constraint indicates support for other types with the same underlying type. With the above definition, you can write the following code:

// id is a new type definition for an int64
type id int64

func main() {
	fmt.Println(Sum([]id{1, 2, 3}...))
}

This will result in a compilation error:

id does not implement Numeric (possibly missing ~ for int64 in constraint Numeric)

You need to add the ~ prefix to the int64 part of the type collection.

3. When a type contains generics, the generic symbol must be included in the function receiver. For example, if you have the following generic type:

// Ledger is an identifiable, financial record.
type Ledger[T ~string, K Numeric] struct {

	// ID identifies the ledger.
	ID T

	// Amounts is a list of monies associated with this ledger.
	Amounts []K

	// SumFn is a function that can be used to sum the amounts
	// in this ledger.
	SumFn SumFn[K]
}

When defining methods for it, you need to explicitly include the generic symbol in the function receiver:

// PrintIDAndSum emits the ID of the ledger and a sum of its amounts on a
// single line to stdout.
func (l Ledger[T, K]) PrintIDAndSum() {
	fmt.Printf("%s has a sum of %v\n", l.ID, l.SumFn(l.Amounts...))
}

Sorting Implementation#

With the understanding of the above knowledge, implementing sort.Interface for multiple types becomes simple. The complete code is as follows:

package sort

type comparable interface {
	~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 |
		~int | ~int8 | ~int16 | ~int32 | ~int64 |
		~float32 | ~float64 |
		~string
}

// Sortable is a generics slice which implements sort.Interface
type Sortable[T comparable] []T

func (s Sortable[T]) Len() int {
	return len(s)
}

func (s Sortable[T]) Swap(i, j int) {
	s[i], s[j] = s[j], s[i]
}
func (s Sortable[T]) Less(i, j int) bool {
	return s[i] < s[j]
}

To sort slices of various types, simply use the Sortable wrapper:

package main

import (
	"fmt"
	"sort"

	gsort "github.com/amtoaer/generic-sort"
)

func main() {
	intSlice := []int{1, 3, 2, 4}
	stringSlice := []string{"h", "e", "l", "l", "o"}
	byteSlice := []byte{'h', 'e', 'l', 'l', 'o'}
	sort.Sort(gsort.Sortable[int](intSlice))
	sort.Sort(gsort.Sortable[string](stringSlice))
	sort.Sort(gsort.Sortable[byte](byteSlice))
	fmt.Println(intSlice)    // [1 2 3 4]
	fmt.Println(stringSlice) // [e h l l o]
	fmt.Println(byteSlice)   // [101 104 108 108 111]
}
Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.