Published on

Semver sort

Authors

Problem

  • given input array of string contains release version that must follow x.y.z form.
  • x.y.z must be integer with positive values
  • sort by latest release

Solution

See in playground https://go.dev/play/p/_nFWNUOcDCu.

The process is straigtforward

  • Parse string into semver struct
  • Sort using custom slice and implement required sorting interface Len, Swap and Less, refers to https://pkg.go.dev/sort#example-package
  • Revert back to array of string where version string follows x.y.z format

_95
package main
_95
_95
import (
_95
"fmt"
_95
"sort"
_95
"strconv"
_95
"strings"
_95
)
_95
_95
// Semver represents a semantic version in the format x.y.z
_95
type Semver struct {
_95
Major int
_95
Minor int
_95
Patch int
_95
}
_95
_95
func (s Semver) ToString() string {
_95
return fmt.Sprintf("%d.%d.%d", s.Major, s.Minor, s.Patch)
_95
}
_95
_95
// NewSemver returns a new Semver instance with the specified major, minor, and patch values.
_95
func NewSemver(major int, minor int, patch int) Semver {
_95
return Semver{
_95
Major: major,
_95
Minor: minor,
_95
Patch: patch,
_95
}
_95
}
_95
_95
// ParseSemver takes a string representation of a semantic version in the format x.y.z and returns a new Semver instance.
_95
func ParseSemver(version string) (Semver, error) {
_95
parts := strings.Split(version, ".")
_95
if len(parts) != 3 {
_95
return Semver{}, fmt.Errorf("invalid version format: %s", version)
_95
}
_95
_95
major, minor, patch := 0, 0, 0
_95
var err error
_95
if major, err = strconv.Atoi(parts[0]); err != nil || major < 0 {
_95
return Semver{}, fmt.Errorf("invalid major version: %s", parts[0])
_95
}
_95
if minor, err = strconv.Atoi(parts[1]); err != nil || minor < 0 {
_95
return Semver{}, fmt.Errorf("invalid minor version: %s", parts[1])
_95
}
_95
if patch, err = strconv.Atoi(parts[2]); err != nil || patch < 0 {
_95
return Semver{}, fmt.Errorf("invalid patch version: %s", parts[2])
_95
}
_95
_95
return NewSemver(major, minor, patch), nil
_95
}
_95
_95
// Versions is a slice of Semver instances
_95
type Versions []Semver
_95
_95
func (v Versions) Len() int {
_95
return len(v)
_95
}
_95
_95
func (v Versions) Less(i, j int) bool {
_95
if v[i].Major != v[j].Major {
_95
return v[i].Major < v[j].Major
_95
}
_95
if v[i].Minor != v[j].Minor {
_95
return v[i].Minor < v[j].Minor
_95
}
_95
return v[i].Patch < v[j].Patch
_95
}
_95
_95
func (v Versions) Swap(i, j int) {
_95
v[i], v[j] = v[j], v[i]
_95
}
_95
_95
func solution(input []string) []string {
_95
var versions Versions
_95
for _, v := range input {
_95
semver, err := ParseSemver(v)
_95
if err == nil {
_95
versions = append(versions, semver)
_95
}
_95
}
_95
_95
sort.Sort(sort.Reverse(versions))
_95
result := []string{}
_95
for _, v := range versions {
_95
result = append(result, v.ToString())
_95
}
_95
return result
_95
}
_95
_95
func main() {
_95
input := []string{"1.0.0", "2.0.0", "0.1.0", "3.0.0", "invalid", "1.2.3", "1.2.3-rc", "1.-1.2"}
_95
_95
result := solution(input)
_95
fmt.Println(result)
_95
}