Go language Bible - Chapter 7 interface - 7.6 sort Interface interface

Posted by Exoon on Wed, 05 Jan 2022 08:49:05 +0100

Chapter 7 interface

Interface types are abstractions and generalizations of other types of behavior Interface types will not be bound with specific implementation details. This abstract way can make our functions more flexible and adaptable

The interface of Go language is special because it meets the implicit implementation. In other words, we do not need to define all interface types that meet the requirements for a specific type, but just let the type have some simple and necessary methods. In this way, we create a new interface type to meet the specific types, and we do not need to change the definitions of these types. This mechanism is useful when the types we use come from packages that are not under our control

7.6 sort.Interface interface

Sorting and string formatting are operations we often use

The sort package in Go language has built-in functions that can sort any sequence. It is a little special because sort The sort function does not make any assumptions about any specific sequence or its elements. It uses an interface type sort Interface to specify the Convention between the general sorting algorithm and the sequence types that may need to participate in sorting

The implementation of the above interface is determined by the specific representation of the sequence and the elements it wants to sort. The representation of the sequence is usually a slice

A built-in sorting algorithm needs to know three elements: sequence length, comparison results of two elements, and exchange mode of two (elements)

package sort

type Interface interface {
	Len() int
	Less(i,j int)bool //i,j are indices of sequence elements
	Swap(i,j int)
}

In order to sort the sequence, we also have to define the specific types that implement the above methods. Let's take the string slice as an example

type StringSlice []string

func (p StringSlice) Len() int         {return len(p)}
func (p StringSlice) Less(i,j int)bool {return p[i]<p[j]}
func (p StringSlice) Swap(i,j int)     {p[i],p[j]= p[j],p[i]}

Now we convert a slice to a StringSlice type for sorting:

func Sort(data Interface) {
    n := data.Len()
    quickSort(data, 0, n, maxDepth(n))
}

sort.Sort(StringSlice(names))

OK, now that the sorting code has been built, let's actually use it. Let's sort the music playlists in a table. Each element is not itself but a pointer, which will be faster

type Track struct {
	Title  string
	Artist string
	Album  string
	Year   int
	Length time.Duration
}

var tracks = []*Track{
	{"Go", "Delilah", "From the Roots Up", 2012, length("3m38s")},
	{"Go", "Moby", "Moby", 1992, length("3m37s")},
	{"Go Ahead", "Alicia Keys", "As I Am", 2007, length("4m36s")},
	{"Read 2 Go", "Martin Solveig", "Smash", 2011, length("4m24s")},
}

func length(s string) time.Duration {
	d, err := time.ParseDuration(s)
	if err != nil {
		panic(s)
	}
	return d
}

We use the printTracks function to print the playlist into a table. A graphical presentation may be better. We use the text/tabwriter package to generate a table with neatly aligned and separated columns, as follows:

func printTracks(tracks []*Track) {
	const format = "%v\t%v\t%v\t%v\t%v\t%v\t\n"
	tw := new(tabwriter.Writer).Init(os.Stdout, 0, 8, 2, ' ', 0)
	fmt.Fprintf(tw, format, "Title", "Artist", "Album", "Year", "Length")
	fmt.Fprintf(tw, format, "-----", "-----", "-----", "-----", "----", "------")
	for _, t := range tracks {
		fmt.Fprintf(tw, format, t.Title, t.Artist, t.Album, t.Year, t.Length)
	}
	tw.Flush() //calculate column widths and print table
}

Note: * tabwriter Writer is to meet io The writer interface collects every piece of data written to it: its flush method formats the entire table and writes it to OS Stdout (standard output)

In order to sort the playlist according to the Artist field, we will define a new slice type with the required Len, Less and Swap methods like StringSlice

type byArtist []*Track

func (x byArtist) Len() int           { return len(x) }
func (x byArtist) Less(i, j int) bool { return x[i].Artist < x[j].Artist }
func (x byArtist) Swap(i, j int)      { x[i], x[j] = x[j], x[i] }

In order to call the sorter, we must first convert Tracks to the new byArtist type, which defines the specific sort, and we can continue to call the sort function

func main() {
	sort.Sort(byArtist(tracks))
	printTracks(tracks)
	sort.Sort(sort.Reverse(byArtist(tracks)))
	printTracks(tracks)
}
Title      Artist          Album              Year  Length  
-----      -----           -----              ----  ------  
Go Ahead   Alicia Keys     As I Am            2007  4m36s   
Go         Delilah         From the Roots Up  2012  3m38s   
Read 2 Go  Martin Solveig  Smash              2011  4m24s   
Go         Moby            Moby               1992  3m37s   
Title      Artist          Album              Year  Length  
-----      -----           -----              ----  ------  
Go         Moby            Moby               1992  3m37s   
Read 2 Go  Martin Solveig  Smash              2011  4m24s   
Go         Delilah         From the Roots Up  2012  3m38s   
Go Ahead   Alicia Keys     As I Am            2007  4m36s   

Sort. Is used on it Reverse function, let's take a closer look

func Reverse(data Interface) Interface {
	return &reverse{data}
}	

func (r reverse) Less(i, j int) bool {
	return r.Interface.Less(j, i)
}

type reverse struct {
	// This embedded Interface permits Reverse to use the methods of
	// another Interface implementation.
	Interface
}

The other two methods of reverse, Len and Swap, are implicitly composed of the original embedded sort Interface is provided. Because reverse is an undisclosed type, the export function reverse returns a message containing the original sort Reverse instance of interface value

Let's redefine a new type byYear

type byYear []*Track

func (x byYear) Len() int           { return len(x) }
func (x byYear) Less(i, j int) bool { return x[i].Year < x[j].Year }
func (x byYear) Swap(i, j int)      { x[i], x[j] = x[j], x[i] }
func main() {
	sort.Sort(byYear(tracks))
	printTracks(tracks)
}

Print out the results. It's perfect

Title      Artist          Album              Year  Length  
-----      -----           -----              ----  ------  
Go         Moby            Moby               1992  3m37s   
Go Ahead   Alicia Keys     As I Am            2007  4m36s   
Read 2 Go  Martin Solveig  Smash              2011  4m24s   
Go         Delilah         From the Roots Up  2012  3m38s   

Let's continue to look at a nested sort (multi condition) and print the results

type customSort struct {
	t    []*Track
	less func(x, y *Track) bool
}

func (x customSort) Len() int           { return len(x.t) }
func (x customSort) Less(i, j int) bool { return x.less(x.t[i], x.t[j]) }
func (x customSort) Swap(i, j int)      { x.t[i], x.t[j] = x.t[j], x.t[i] }

func main() {
	sort.Sort(customSort{tracks, func(x, y *Track) bool {
		if x.Title != y.Title {
			return x.Title < y.Title
		}
		if x.Year != y.Year {
			return x.Year < y.Year
		}
		if x.Length != y.Length {
			return x.Length < y.Length
		}
		return false
	}})

	printTracks(tracks)
}
Title      Artist          Album              Year  Length  
-----      -----           -----              ----  ------  
Go         Moby            Moby               1992  3m37s   
Go         Delilah         From the Roots Up  2012  3m38s   
Go Ahead   Alicia Keys     As I Am            2007  4m36s   
Read 2 Go  Martin Solveig  Smash              2011  4m24s   

As we can see, to achieve this sort, That means that the time complexity is O (n logn). Checking whether a sequence is ordered requires at least n-1 comparisons. The IsSort function in the sort package helps us do this check. Like sort.Sort, it also abstracts the sequence and its sorting function using sort.Interface, but it never calls the Swap method: the following is a code example:

values := []int{3, 1, 4, 1}
	fmt.Println(sort.IntsAreSorted(values))
	sort.Ints(values)
	fmt.Println(values)
	fmt.Println(sort.IntsAreSorted(values))
	sort.Sort(sort.Reverse(sort.IntSlice(values)))
	fmt.Println(values)
	fmt.Println(sort.IntsAreSorted(values))
false
[1 1 3 4]
true
[4 3 1 1]
false

For ease of use, the sort package provides specific versions and function types for the normal sorting of [] int \ []string[]float64. For other types, such as [] int64 \ or [] uint, although the path is very simple, we need to implement it manually

Topics: Go Algorithm