【程式語言 - Go】關於Struct List的排序

閱讀時間約 0 分鐘
raw-image


排序這個動作在軟體開發中常常會使用到, 從使用者期望所見的順序到資料處理的效能議題都與排序息息相關, 因此掌握程式語言的排序功能是非常重要的一個環節, 而我們在閱讀他人的Go專案程式碼時也會看到排序的方式有些許不同, 那究竟有何差異呢? 就讓我們繼續看下去吧…

其實在進入今天的主題之前, 我們心中只要記得排序方式不同的主因主要有兩點不同, 「快速簡易」與「高度客製化」這兩個需求的差異之下衍生了不同的排序方式, 其實軟體寫久了大家會覺得程式語言與真實需求的許多場景息息相關, 畢竟需求是「人」衍生出來的, 而我們「人」所設計的程式語言也是圍繞著「需求」而生。

前置條件

假設我們定義了一個User的struct, 將儲存姓名與年齡。

type User struct {
Name string
Age int
}

那我們建立幾個user如下:

func main() {
// 創建包含 User 結構的切片
users := []User{
{"Alice", 30},
{"Bob", 25},
{"Charlie", 35},
}
}

Q: 我們今天收到一個需求, 請對於「年齡(Age)」進行升冪(由低到高)的排序, 請問應該怎麼做?

Slice函數排序

首先我們可以先看看Slice函數, 它提供了自訂比較函式的功能, 因此我們可以基於這樣的功能來設計我們想要怎麼排, 由於內建並沒有很直接的對struct資料結構進行排序的功能, 因此我們可以利用「sort.Slice」的特性來進行, 函式的使用大致如下:

// x 欲排序的結構目標
// less 自訂比較函式
func Slice(x any, less func(i, j int) bool) {
...
}

理解了使用方式之後我們就可以這樣排:

// 使用 sort.Slice 函數對切片進行排序
sort.Slice(users, func(i, j int) bool {
return users[i].Age < users[j].Age
})

接著印出結果:

// 打印排序後的切片
fmt.Println("按年齡排序:")
for _, user := range users {
fmt.Printf("Name: %s, Age: %d\\n", user.Name, user.Age)
}
raw-image


但這邊要特別注意的地方是官方的說明文件(https://pkg.go.dev/sort#Slice)中指出「這種排序不保證穩定性, 也就是兩個元素等值時, 每次執行後的結果不一定都相同」, 對於穩定的排序結果請使用「SliceStable」。

raw-image


// 使用 sort.Slice 函數對切片進行排序
sort.SliceStable(users, func(i, j int) bool {
return users[i].Age < users[j].Age
})

高度客製的自訂排序功能

我們都知道, 排序演算法裡面最重要的三個部份就是:

  • 長度多少?
  • 排序的規則?
  • 怎麼換?

以上三點我們可以將原本的struct進行三種功能的擴充, 而以下的程式碼是我們標準的排序方式。

type Users []User

// Len 方法返回切片的長度
func (u Users) Len() int {
return len(u)
}

// Less 方法定義了排序的規則,這裡按照 Age 升序排序
func (u Users) Less(i, j int) bool {
return u[i].Age < u[j].Age
}

// Swap 方法交換切片中兩個元素的位置
func (u Users) Swap(i, j int) {
u[i], u[j] = u[j], u[i]
}

設計完排序的長度、規則、換法之後, 實際上就可以很容易的進行Sort….

func main() {
...

// 使用 sort.Sort 函數來排序,使用自定義排序方式
sort.Sort(users)

fmt.Println("按年齡排序:")
for _, user := range users {
fmt.Printf("Name: %s, Age: %d\\n", user.Name, user.Age)
}
}

看到這邊您可能會疑惑, 為什麼要多此一舉, 不用前面所介紹的「sort.SliceStable」或者「sort.Slice」就好了呢? 因為我們總是看到事情的某個面位, 事實上面對於複雜的案例, 為了可讀性與不同的排序法則時我們就會需要這樣設計了。

假設你正在開發一個電子商務網站,你有一個商品結構體和一個購物車結構體,你希望能夠根據不同條件對購物車中的商品進行排序,例如按照價格、按照銷量、按照名稱等。

raw-image


package main

import (
"fmt"
"sort"
)

type Product struct {
Name string
Price float64
Quantity int
}

type ShoppingCart struct {
Items []Product
}

// 替購物車定義一個排序的介面
type Sorter interface {
Len() int
Swap(i, j int)
Less(i, j int) bool
}

// 設計一個按照價格排序的類別
type ByPrice ShoppingCart

func (s ByPrice) Len() int { return len(s.Items) }
func (s ByPrice) Swap(i, j int) { s.Items[i], s.Items[j] = s.Items[j], s.Items[i] }
func (s ByPrice) Less(i, j int) bool { return s.Items[i].Price < s.Items[j].Price }

// 設計一個按銷量排序的類別
type ByQuantity ShoppingCart

func (s ByQuantity) Len() int { return len(s.Items) }
func (s ByQuantity) Swap(i, j int) { s.Items[i], s.Items[j] = s.Items[j], s.Items[i] }
func (s ByQuantity) Less(i, j int) bool { return s.Items[i].Quantity < s.Items[j].Quantity }

func main() {
cart := ShoppingCart{
Items: []Product{
{"Laptop", 1000.0, 50},
{"Phone", 500.0, 100},
{"Tablet", 300.0, 30},
},
}

// 按照價格排序
sort.Sort(ByPrice(cart))
fmt.Println("按價格排序:", cart.Items)

// 按照銷量排序
sort.Sort(ByQuantity(cart))
fmt.Println("按銷量排序:", cart.Items)
}

在上面的範例中,我們為購物車定義了兩種不同的排序方式,以價格(ByPrice)和依照銷售(ByQuantity),這兩種排序方式都實作了Sorter介面的方法,然後,我們可以使用sort.Sort函數來對購物車中的商品進行排序。

結語

軟體開發的過程中常常會遇到的就是排序問題,基本上目前程式語言大部分都已經將排序功能封裝的非常易用了,我們不需要從頭到尾實作演算法,雖然如此,但我們還是得學會基本的原理以及這些API的使用方式,而每個語言的排序API又有些差異,但相信我們只要掌握核心就能夠得心應手。

116會員
257內容數
哈囉,我是阿Han,是一位 👩‍💻 軟體研發工程師,喜歡閱讀、學習、撰寫文章及教學,擅長以圖代文,化繁為簡,除了幫助自己釐清思路之外,也希望藉由圖解的方式幫助大家共同學習,甚至手把手帶您設計出高品質的軟體產品。
留言0
查看全部
發表第一個留言支持創作者!
阿Han的沙龍 的其他內容
我們在「【🔒 Python 先修班】⬆️ 培養良好的Coding Style讓專業度 Up!Up!Up!」談到了Coding Style, 在文末也分享了pylint的程式碼檢查工具, 雖然內建、簡單快速上手, 但隨著技術的演進, 我們總需要又快、又好、又簡單的工具做為預設的工具庫, Ruff
撰寫Python的朋友都知道multithread/multiprocess能為我們帶來效能的改進,減少硬體資源的閒置,但在撰寫的過程中常常會發現到我們所設計的工作池模式會需要將「待辦清單」的工作項目當成參數傳遞進去執行, 除了「待辦清單」之外, 其餘的參數基本上都是固定的, 基於這樣的需求之下
我們在「【💊 Python的解憂錦囊】如何將dict轉成json並儲存」有介紹過如何將dict型態的資料轉換成json,除了json之外, 另一個耳熟能詳的資料交換格式就是csv了, 我們常常會將csv讀進來, 並使用預先設計的@dataclass來存放, 如此一來實際運行時, 更能夠貼近於我
關於json的資料交換格式請參考「【程式語言 - Javascript】輕量資料格式 JSON」, 我們常常會在使用套件的過程中發現回傳值的型態都會有「dict」的蹤跡, 為什麼呢? 因為動態、彈性、靈活, 不需要預先定義類別來明確指定每個欄位的內容, 但缺點就是文件必須寫清楚內容物是哪些, 否
「functools.partial」是Python中的一個標準函式庫,它可以讓我們基於既有的函式封裝成多種不同用途的函式,就如同上圖所示,我們設計了一個乘法(multiply)的函數,使用了partial讓函數的參數「c」固定下來依據用途不同變化出「double」、「triple」,這樣一來我
最近正好在研究「silero-vad」這套工具, 但根據官方教學,預設的載入方式會從網路上進行下載模型的動作: model, utils = torch.hub.load(repo_or_dir='snakers4/silero-vad',
我們在「【🔒 Python 先修班】⬆️ 培養良好的Coding Style讓專業度 Up!Up!Up!」談到了Coding Style, 在文末也分享了pylint的程式碼檢查工具, 雖然內建、簡單快速上手, 但隨著技術的演進, 我們總需要又快、又好、又簡單的工具做為預設的工具庫, Ruff
撰寫Python的朋友都知道multithread/multiprocess能為我們帶來效能的改進,減少硬體資源的閒置,但在撰寫的過程中常常會發現到我們所設計的工作池模式會需要將「待辦清單」的工作項目當成參數傳遞進去執行, 除了「待辦清單」之外, 其餘的參數基本上都是固定的, 基於這樣的需求之下
我們在「【💊 Python的解憂錦囊】如何將dict轉成json並儲存」有介紹過如何將dict型態的資料轉換成json,除了json之外, 另一個耳熟能詳的資料交換格式就是csv了, 我們常常會將csv讀進來, 並使用預先設計的@dataclass來存放, 如此一來實際運行時, 更能夠貼近於我
關於json的資料交換格式請參考「【程式語言 - Javascript】輕量資料格式 JSON」, 我們常常會在使用套件的過程中發現回傳值的型態都會有「dict」的蹤跡, 為什麼呢? 因為動態、彈性、靈活, 不需要預先定義類別來明確指定每個欄位的內容, 但缺點就是文件必須寫清楚內容物是哪些, 否
「functools.partial」是Python中的一個標準函式庫,它可以讓我們基於既有的函式封裝成多種不同用途的函式,就如同上圖所示,我們設計了一個乘法(multiply)的函數,使用了partial讓函數的參數「c」固定下來依據用途不同變化出「double」、「triple」,這樣一來我
最近正好在研究「silero-vad」這套工具, 但根據官方教學,預設的載入方式會從網路上進行下載模型的動作: model, utils = torch.hub.load(repo_or_dir='snakers4/silero-vad',
你可能也想看
Google News 追蹤
Thumbnail
這個秋,Chill 嗨嗨!穿搭美美去賞楓,裝備款款去露營⋯⋯你的秋天怎麼過?秋日 To Do List 等你分享! 秋季全站徵文,我們準備了五個創作主題,參賽還有機會獲得「火烤兩用鍋」,一起來看看如何參加吧~
Thumbnail
11/20日NVDA即將公布最新一期的財報, 今天Sell Side的分析師, 開始調高目標價, 市場的股價也開始反應, 未來一週NVDA將重新回到美股市場的焦點, 今天我們要分析NVDA Sell Side怎麼看待這次NVDA的財報預測, 以及實際上Buy Side的倉位及操作, 從
Thumbnail
Hi 大家好,我是Ethan😊 相近大家都知道保濕是皮膚保養中最基本,也是最重要的一步。無論是在畫室裡長時間對著畫布,還是在旅途中面對各種氣候變化,保持皮膚的水分平衡對我來說至關重要。保濕化妝水不僅能迅速為皮膚補水,還能提升後續保養品的吸收效率。 曾經,我的保養程序簡單到只包括清潔和隨意上乳液
在一次五天的年假中,我決定開始學習Python編程。雖然最初進展順利,但後來發現自己在解題和邏輯方面遇到了困難。經過思考後,我決定轉而學習C/C++,並且制定了一個計畫,希望成為一名後端工程師。這將需要挑戰我的時間管理能力。
Thumbnail
學習程式語言是一個不容易的過程,但有效的學習方法可以幫助你克服挫折,這篇文章分享了一個程式設計師的學習心得以及一些建議,包括課後實作、短期學習、跟別人比較等注意事項,同時提供了一些相關的教學資源。
Thumbnail
PHP(全名為「PHP: Hypertext Preprocessor」)是一門開源的伺服器端程式語言,為動態網頁開發設計。閱讀本文將讓您瞭解PHP的特點、廣泛支援、易學性、資源豐富以及跨平臺性。
Thumbnail
關於程式語言的學習,只要掌握住幾個基本特性要熟悉幾種程式語言也不困難,這三個基本特性就是…
Thumbnail
咦!奇怪, 怎麼網頁用一用就出現以下的畫面了, 到底是怎麼回事啊? 不用慌張, 讓我們仔細看看中間的「HTTP ERROR 431」,看到這樣的錯誤代碼, 相信心中隱約已經知曉我們的請求標頭內容太大了, 超過限制導致這樣的問題發生, 讓我們先詳細查閱這樣的錯誤訊息吧! 📚 https://
Thumbnail
這個秋,Chill 嗨嗨!穿搭美美去賞楓,裝備款款去露營⋯⋯你的秋天怎麼過?秋日 To Do List 等你分享! 秋季全站徵文,我們準備了五個創作主題,參賽還有機會獲得「火烤兩用鍋」,一起來看看如何參加吧~
Thumbnail
11/20日NVDA即將公布最新一期的財報, 今天Sell Side的分析師, 開始調高目標價, 市場的股價也開始反應, 未來一週NVDA將重新回到美股市場的焦點, 今天我們要分析NVDA Sell Side怎麼看待這次NVDA的財報預測, 以及實際上Buy Side的倉位及操作, 從
Thumbnail
Hi 大家好,我是Ethan😊 相近大家都知道保濕是皮膚保養中最基本,也是最重要的一步。無論是在畫室裡長時間對著畫布,還是在旅途中面對各種氣候變化,保持皮膚的水分平衡對我來說至關重要。保濕化妝水不僅能迅速為皮膚補水,還能提升後續保養品的吸收效率。 曾經,我的保養程序簡單到只包括清潔和隨意上乳液
在一次五天的年假中,我決定開始學習Python編程。雖然最初進展順利,但後來發現自己在解題和邏輯方面遇到了困難。經過思考後,我決定轉而學習C/C++,並且制定了一個計畫,希望成為一名後端工程師。這將需要挑戰我的時間管理能力。
Thumbnail
學習程式語言是一個不容易的過程,但有效的學習方法可以幫助你克服挫折,這篇文章分享了一個程式設計師的學習心得以及一些建議,包括課後實作、短期學習、跟別人比較等注意事項,同時提供了一些相關的教學資源。
Thumbnail
PHP(全名為「PHP: Hypertext Preprocessor」)是一門開源的伺服器端程式語言,為動態網頁開發設計。閱讀本文將讓您瞭解PHP的特點、廣泛支援、易學性、資源豐富以及跨平臺性。
Thumbnail
關於程式語言的學習,只要掌握住幾個基本特性要熟悉幾種程式語言也不困難,這三個基本特性就是…
Thumbnail
咦!奇怪, 怎麼網頁用一用就出現以下的畫面了, 到底是怎麼回事啊? 不用慌張, 讓我們仔細看看中間的「HTTP ERROR 431」,看到這樣的錯誤代碼, 相信心中隱約已經知曉我們的請求標頭內容太大了, 超過限制導致這樣的問題發生, 讓我們先詳細查閱這樣的錯誤訊息吧! 📚 https://