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/

https://golang.org/pkg/sort/#example__sortKeys

http://www.davekoelle.com/alphanum.html





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