1. 程式人生 > 其它 >go 結構體多欄位多因素排序

go 結構體多欄位多因素排序

前言

有時候我們需要處理一份要根據多個欄位進行排序的資料。

程式碼

package main

import (
	"fmt"
	"sort"
)

// 資料型別
type Change struct {
	user     string
	language string
	lines    int
}

// sort介面方法之一(Less)
type lessFunc func(p1, p2 *Change) bool

// 資料集型別, 與排序(多欄位單獨排序)比較, less欄位的資料型別不再是 func(p1, p2 *Change) bool
// 而是 []func(p1, p2 *Change) bool 因為在第一個比較的值相等的情況下, 還要比較第二個值, 所以這裡需要多個比較函式
type multiSorter struct {
	changes []Change
	less    []lessFunc
}

type OrderedBy []lessFunc

func (ob OrderedBy) Sort(changes []Change) {
	ms := &multiSorter{
		changes: changes,
		less:    ob,
	}
	sort.Sort(ms)
}

// Len 為sort介面方法之一
func (ms *multiSorter) Len() int {
	return len(ms.changes)
}

// Swap 為sort介面方法之一
func (ms *multiSorter) Swap(i, j int) {
	ms.changes[i], ms.changes[j] = ms.changes[j], ms.changes[i]
}

// Less 為sort介面方法之一
func (ms *multiSorter) Less(i, j int) bool {
	// 為了後面編寫簡便, 這裡將需要比較的兩個元素賦值給兩個單獨的變數
	p, q := &ms.changes[i], &ms.changes[j]
	// Try all but the last comparison.
	var k int
	// 由於可能有多個需要排序的欄位, 也就對應了多個less函式, 當第一個欄位的值相等時,
	// 需要依次嘗試比對後續其他欄位的值得大小, 所以這裡需要獲取比較函式的長度, 以便遍歷比較
	for k = 0; k < len(ms.less)-1; k++ {
		// 提取比較函式, 將函式賦值到新的變數中以便呼叫
		less := ms.less[k]
		switch {
		case less(p, q):
			// 如果 p < q, 返回值為true, 不存在兩個值相等需要比較後續欄位的情況, 所以這裡直接返回
			// 如果 p > q, 返回值為false, 則調到下一個case中處理
			return true
		case less(q, p):
			// 如果 p > q, 返回值為false, 不存在兩個值相等需要比較後續欄位的情況, 所以這裡直接返回
			return false
		}
		// 如果程式碼走到這裡, 說明ms.less[k]函式比較後 p == q; 重新開始下一次迴圈, 更換到下一個比較函式處理
		continue
	}
	// 如果程式碼走到這裡, 說明所有的比較函式執行過後, 所有比較的值都相等
	// 直接返回最後一次的比較結果資料即可
	return ms.less[k](p, q)
}

var changes = []Change{
	{"gri", "Go", 100},
	{"ken", "C", 150},
	{"glenda", "Go", 200},
	{"rsc", "Go", 200},
	{"r", "Go", 100},
	{"ken", "Go", 200},
	{"dmr", "C", 100},
	{"r", "C", 150},
	{"gri", "Smalltalk", 80},
}

func main() {
	// 預定義排序函式: 按照姓名升序排列
	user := func(c1, c2 *Change) bool {
		return c1.user < c2.user
	}
	// 預定義排序函式: 按照語言升序排列
	language := func(c1, c2 *Change) bool {
		return c1.language < c2.language
	}
	// 預定義排序函式: 按照行數升序排列
	increasingLines := func(c1, c2 *Change) bool {
		return c1.lines < c2.lines
	}
	//預定義排序函式: 按照行數降序排列
	decreasingLines := func(c1, c2 *Change) bool {
		return c1.lines > c2.lines
	}

	// 按照姓名升序排列
	OrderedBy([]lessFunc{user}).Sort(changes)
	fmt.Println("By user:\t\t", changes)

	// 按照姓名升序排列, 姓名相同的按行數升序排列
	OrderedBy([]lessFunc{user, increasingLines}).Sort(changes)
	fmt.Println("By user,<lines:\t\t", changes)

	// 按姓名升序排列, 姓名相同的按行數降序排列
	OrderedBy([]lessFunc{user, decreasingLines}).Sort(changes)
	fmt.Println("By user,>lines:\t\t", changes)

	// 按語言升序排列, 語言相同按行數升序排列
	OrderedBy([]lessFunc{language, increasingLines}).Sort(changes)
	fmt.Println("By language,<lines:\t", changes)

	// 按語言升序排列, 語言相同按行數升序排列, 行數也相同的, 按姓名升序排列
	OrderedBy([]lessFunc{language, increasingLines, user}).Sort(changes)
	fmt.Println("By language,<lines,user:", changes)
}

執行結果

By user:                 [{dmr C 100} {glenda Go 200} {gri Go 100} {gri Smalltalk 80} {ken C 150} {ken Go 200} {r Go 100} {r C 150} {rsc Go 200}]
By user,<lines:          [{dmr C 100} {glenda Go 200} {gri Smalltalk 80} {gri Go 100} {ken C 150} {ken Go 200} {r Go 100} {r C 150} {rsc Go 200}]
By user,>lines:          [{dmr C 100} {glenda Go 200} {gri Go 100} {gri Smalltalk 80} {ken Go 200} {ken C 150} {r C 150} {r Go 100} {rsc Go 200}]
By language,<lines:      [{dmr C 100} {ken C 150} {r C 150} {r Go 100} {gri Go 100} {ken Go 200} {glenda Go 200} {rsc Go 200} {gri Smalltalk 80}]
By language,<lines,user: [{dmr C 100} {ken C 150} {r C 150} {gri Go 100} {r Go 100} {glenda Go 200} {ken Go 200} {rsc Go 200} {gri Smalltalk 80}]