Golang : Natural string sorting example
Problem:
You want to sort a couple of strings with numbers in natural order so that the list looks nice to your eyes or simply to prevent your program users from complaining that the items or filenames are not being sorted properly. For instance:
'Team 101', 'Team 58', 'Team 30', 'Team 1'
to
'Team 1', 'Team 30', 'Team 58', 'Team 101'
How to do that?
Solution:
Below is my adaptation of [https://golang.org/pkg/sort/#example__sortKeys][1]
. It should sort most strings with numbers. However, it won't be able to sort strings with decimal values such as version number properly. Will update in future once there is a better solution.
Here you go!
package main
import (
"fmt"
"log"
"sort"
"strconv"
"unicode"
"strings"
)
type Compare func(str1, str2 string) bool
func (cmp Compare) Sort(strs []string) {
strSort := &strSorter{
strs : strs,
cmp : cmp,
}
sort.Sort(strSort)
}
type strSorter struct {
strs []string
cmp func(str1, str2 string) bool
}
func extractNumberFromString(str string, size int) (num int) {
strSlice := make([]string, 0)
for _, v := range str {
if unicode.IsDigit(v) {
strSlice = append(strSlice, string(v))
}
}
if size == 0 { // default
num, err := strconv.Atoi(strings.Join(strSlice, ""))
if err != nil {
log.Fatal(err)
}
return num
} else {
num, err := strconv.Atoi(strSlice[size-1])
if err != nil {
log.Fatal(err)
}
return num
}
}
func (s *strSorter) Len() int { return len(s.strs) }
func (s *strSorter) Swap(i, j int) { s.strs[i], s.strs[j] = s.strs[j], s.strs[i] }
func (s *strSorter) Less(i, j int) bool { return s.cmp(s.strs[i], s.strs[j]) }
func main() {
// closure order for natural string number sorting
compareStringNumber := func(str1, str2 string) bool {
return extractNumberFromString(str1,0) < extractNumberFromString(str2,0)
}
fmt.Println("Natural or 'Human' string sort results:")
naturalStringSlice := []string{"Team11", "Team3", "Team9", "Team1"}
fmt.Println("Original : " , naturalStringSlice)
Compare(compareStringNumber).Sort(naturalStringSlice)
fmt.Println("Naturally sorted : ", naturalStringSlice)
naturalStringSlice2 := []string{"9th", "3rd", "10th", "1st"}
fmt.Println("Original : " , naturalStringSlice2)
Compare(compareStringNumber).Sort(naturalStringSlice2)
fmt.Println("Naturally sorted : ", naturalStringSlice2)
naturalStringSlice3 := []string{"A3","a5", "a30", "a1", "A9", "A7"}
fmt.Println("Original : " ,naturalStringSlice3)
Compare(compareStringNumber).Sort(naturalStringSlice3)
fmt.Println("Naturally sorted : ", naturalStringSlice3)
naturalStringSlice4 := []string{"Team 101","Team 58", "a30", "a1", "A9", "A7"}
fmt.Println("Original : " ,naturalStringSlice4)
Compare(compareStringNumber).Sort(naturalStringSlice4)
fmt.Println("Naturally sorted : ", naturalStringSlice4)
// closure order for chemical elements sorting
compareChemicalElements := func(str1, str2 string) bool {
return extractNumberFromString(str1,1) < extractNumberFromString(str2,1)
}
chemicalElements := []string{"C2H6", "C1H2", "C2N", "C1H4", "C1H2", "C1H4", "C2H2", "C3H6"}
//chemicalElements := []string{"C2N", "C1H3"} -- fixed!
fmt.Println("Original : " ,chemicalElements)
Compare(compareChemicalElements).Sort(chemicalElements)
fmt.Println("Naturally sorted : ", chemicalElements)
// won't work well with decimal values...need further enhancement/debugging
// closure order for chemical elements sorting
/*compareVersion := func(str1, str2 string) bool {
// remember ver-1.2.15 has size 3 digits
// why? because counting of index starts from 0 !
return extractNumberFromString(str1,3) < extractNumberFromString(str2,3)
}
//naturalStringSlice3 := []string{"ver-1.2.15", "ver-1.3.1", "ver-1.2.3", "ver-1.3.12", "ver-1.3.3", "ver-1.2.5"}
naturalStringSlice3 := []string{"ver-1.3.3","ver-1.2.5", "ver-1.3.1","ver-1.1.3", "ver-1.3.1", }
fmt.Println("Original : " ,naturalStringSlice3)
Compare(compareVersion).Sort(naturalStringSlice3)
fmt.Println("Naturally sorted : ",naturalStringSlice3)*/
}
Output:
Natural or 'Human' string sort results:
Original : [Team11 Team3 Team9 Team1]
Naturally sorted : [Team1 Team3 Team9 Team11]
Original : [9th 3rd 10th 1st]
Naturally sorted : [1st 3rd 9th 10th]
Original : [A3 a5 a30 a1 A9 A7]
Naturally sorted : [a1 A3 a5 A7 A9 a30]
Original : [Team 101 Team 58 a30 a1 A9 A7]
Naturally sorted : [a1 A7 A9 a30 Team 58 Team 101]
Original : [C2H6 C1H2 C2N C1H4 C1H2 C1H4 C2H2 C3H6]
Naturally sorted : [C1H2 C1H4 C1H2 C1H4 C2H6 C2N C2H2 C3H6]
Happy coding!
References:
https://blog.codinghorror.com/sorting-for-humans-natural-sort-order/
By Adam Ng
IF you gain some knowledge or the information here solved your programming problem. Please consider donating to the less fortunate or some charities that you like. Apart from donation, planting trees, volunteering or reducing your carbon footprint will be great too.
Advertisement
Tutorials
+14.1k Golang : syscall.Socket example
+25.7k Golang : missing Mercurial command
+9.2k Golang : How to check if a string with spaces in between is numeric?
+21.4k Curl usage examples with Golang
+6.8k Golang : Join lines with certain suffix symbol example
+18.6k Golang : Generate thumbnails from images
+17k Golang : Get number of CPU cores
+6.5k Elasticsearch : Shutdown a local node
+11k Golang : Replace a parameter's value inside a configuration file example
+6.5k Grep : How to grep for strings inside binary data
+8.2k Golang : Oanda bot with Telegram and RSI example
+6k Golang : Measure execution time for a function