【程式語言 - 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又有些差異,但相信我們只要掌握核心就能夠得心應手。

avatar-img
119會員
268內容數
哈囉,我是阿Han,是一位 👩‍💻 軟體研發工程師,喜歡閱讀、學習、撰寫文章及教學,擅長以圖代文,化繁為簡,除了幫助自己釐清思路之外,也希望藉由圖解的方式幫助大家共同學習,甚至手把手帶您設計出高品質的軟體產品。
留言0
查看全部
avatar-img
發表第一個留言支持創作者!
阿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
嘿,大家新年快樂~ 新年大家都在做什麼呢? 跨年夜的我趕工製作某個外包設計案,在工作告一段落時趕上倒數。 然後和兩個小孩過了一個忙亂的元旦。在深夜時刻,看到朋友傳來的解籤網站,興致勃勃熬夜體驗了一下,覺得非常好玩,或許有人玩過了,但還是想寫上來分享紀錄一下~
Thumbnail
本文介紹了選擇排序演算法的基本邏輯與實作過程,透過範例分析陣列排序的交換步驟,以及相關的程式碼範例,幫助讀者理解選擇排序的時間與空間複雜度。選擇排序是一個簡單易懂的演算法,對於初學者來說是學習排序演算法的良好基礎。
Thumbnail
上回提到,演算法是一種解決問題的方法。光是簡單的將數字有小排到大就有很多種不同的排序演算法可以選擇。這次,我們來介紹幾個常見的排序演算法,看看它們是怎麼運作的。
Thumbnail
本文探討了不同情境下優化代碼的建議。透過實際的代碼示例,幫助程式設計師提升程式碼的可讀性和可維護性,適合各類型開發人員參考與應用。
Thumbnail
※ 什麼是ORDER BY? 可以讓SELECT出來的結果,根據你想要的方式排序。簡單說,用於對查詢結果進行排序。 ※ 語法: SELECT select_list FROM table_name ORDER BY column1 [ASC|DESC], column2 [ASC|DESC]
數學系的訓練,與上面閱讀原始碼的優先順序,本質上是反過來的。在數學的訓練中,是先把函數定義的非常清楚,再進一步去看函數應用在具體的數據上會發生什麼行為,然後就到此為止,不太會再有進一步的討論。但如上面西尾泰和所述,工程師看事情的角度,是先掌握全局,然後再進一步細化每一層的細節。
Thumbnail
在進行SQL查詢邏輯更改時,需要適當地使用SubQuery和join來達到新的排序需求。本文將介紹原本的撈取邏輯、需求以及如何使用SubQuery來解決新的排序需求。
Thumbnail
題目敘述 題目會給定我們兩個字串。 第一個是指定順序的字串order。 第二個是輸入字串s。 要求我們依據order給定的順序,重新排列s。 如果出現order中沒有出現的字母,任意位置皆可。 合法答案可能不只一組,輸出其中一種即可。 題目的原文敘述 測試範例 Example
Thumbnail
列出一套完整的程式 程式設計有許多種方法,不過通常會先列出清單的再逐一執行,這樣會加快程式設計的速度。設計通常會採取順推的辦法。所以順推的程式設計方式就是經歷觀念溝通、系統分析、資料統合、權限管理、頻率與時間、後台管理、畫面設計等等階段後,將框架設計完了以後,先列出一套完整的程式,將所有使用者都確
這個系列的文章主要專注於物件導向到函數式編程的差異與分析,並針對概念與機制上的不同進行比較。很多人說物件導向和函數式編程沒有哪個比較好的問題,只有哪個比較適合的問題,然而我並不這麼認為,我透過這一系列的文章從各個角度討論它們之間的優缺點就是為了闡述我的觀點。物件導向錯在沒有理論基礎,但它贏在熟悉性,
描述順序的方式有很多種,可以用數字標示文字的方式,如下 文字 文字 文字 用數字標示文字的方式我覺得有個特質是,文字重要的程度是遞減的,也就是數字從一往後會越來越不重要。這個現象是我在寫這篇文的當下感覺到的,可能是我在過去經驗聽過有人說越重要的東西要先擺在前面;又或者是我在高中以前的
Thumbnail
嘿,大家新年快樂~ 新年大家都在做什麼呢? 跨年夜的我趕工製作某個外包設計案,在工作告一段落時趕上倒數。 然後和兩個小孩過了一個忙亂的元旦。在深夜時刻,看到朋友傳來的解籤網站,興致勃勃熬夜體驗了一下,覺得非常好玩,或許有人玩過了,但還是想寫上來分享紀錄一下~
Thumbnail
本文介紹了選擇排序演算法的基本邏輯與實作過程,透過範例分析陣列排序的交換步驟,以及相關的程式碼範例,幫助讀者理解選擇排序的時間與空間複雜度。選擇排序是一個簡單易懂的演算法,對於初學者來說是學習排序演算法的良好基礎。
Thumbnail
上回提到,演算法是一種解決問題的方法。光是簡單的將數字有小排到大就有很多種不同的排序演算法可以選擇。這次,我們來介紹幾個常見的排序演算法,看看它們是怎麼運作的。
Thumbnail
本文探討了不同情境下優化代碼的建議。透過實際的代碼示例,幫助程式設計師提升程式碼的可讀性和可維護性,適合各類型開發人員參考與應用。
Thumbnail
※ 什麼是ORDER BY? 可以讓SELECT出來的結果,根據你想要的方式排序。簡單說,用於對查詢結果進行排序。 ※ 語法: SELECT select_list FROM table_name ORDER BY column1 [ASC|DESC], column2 [ASC|DESC]
數學系的訓練,與上面閱讀原始碼的優先順序,本質上是反過來的。在數學的訓練中,是先把函數定義的非常清楚,再進一步去看函數應用在具體的數據上會發生什麼行為,然後就到此為止,不太會再有進一步的討論。但如上面西尾泰和所述,工程師看事情的角度,是先掌握全局,然後再進一步細化每一層的細節。
Thumbnail
在進行SQL查詢邏輯更改時,需要適當地使用SubQuery和join來達到新的排序需求。本文將介紹原本的撈取邏輯、需求以及如何使用SubQuery來解決新的排序需求。
Thumbnail
題目敘述 題目會給定我們兩個字串。 第一個是指定順序的字串order。 第二個是輸入字串s。 要求我們依據order給定的順序,重新排列s。 如果出現order中沒有出現的字母,任意位置皆可。 合法答案可能不只一組,輸出其中一種即可。 題目的原文敘述 測試範例 Example
Thumbnail
列出一套完整的程式 程式設計有許多種方法,不過通常會先列出清單的再逐一執行,這樣會加快程式設計的速度。設計通常會採取順推的辦法。所以順推的程式設計方式就是經歷觀念溝通、系統分析、資料統合、權限管理、頻率與時間、後台管理、畫面設計等等階段後,將框架設計完了以後,先列出一套完整的程式,將所有使用者都確
這個系列的文章主要專注於物件導向到函數式編程的差異與分析,並針對概念與機制上的不同進行比較。很多人說物件導向和函數式編程沒有哪個比較好的問題,只有哪個比較適合的問題,然而我並不這麼認為,我透過這一系列的文章從各個角度討論它們之間的優缺點就是為了闡述我的觀點。物件導向錯在沒有理論基礎,但它贏在熟悉性,
描述順序的方式有很多種,可以用數字標示文字的方式,如下 文字 文字 文字 用數字標示文字的方式我覺得有個特質是,文字重要的程度是遞減的,也就是數字從一往後會越來越不重要。這個現象是我在寫這篇文的當下感覺到的,可能是我在過去經驗聽過有人說越重要的東西要先擺在前面;又或者是我在高中以前的