Semver sort

1 min read Tweet this post

  • 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

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
package main

import (
	"fmt"
	"sort"
	"strconv"
	"strings"
)

// Semver represents a semantic version in the format x.y.z
type Semver struct {
	Major int
	Minor int
	Patch int
}

func (s Semver) ToString() string {
	return fmt.Sprintf("%d.%d.%d", s.Major, s.Minor, s.Patch)
}

// NewSemver returns a new Semver instance with the specified major, minor, and patch values.
func NewSemver(major int, minor int, patch int) Semver {
	return Semver{
		Major: major,
		Minor: minor,
		Patch: patch,
	}
}

// ParseSemver takes a string representation of a semantic version in the format x.y.z and returns a new Semver instance.
func ParseSemver(version string) (Semver, error) {
	parts := strings.Split(version, ".")
	if len(parts) != 3 {
		return Semver{}, fmt.Errorf("invalid version format: %s", version)
	}

	major, minor, patch := 0, 0, 0
	var err error
	if major, err = strconv.Atoi(parts[0]); err != nil || major < 0 {
		return Semver{}, fmt.Errorf("invalid major version: %s", parts[0])
	}
	if minor, err = strconv.Atoi(parts[1]); err != nil || minor < 0 {
		return Semver{}, fmt.Errorf("invalid minor version: %s", parts[1])
	}
	if patch, err = strconv.Atoi(parts[2]); err != nil || patch < 0 {
		return Semver{}, fmt.Errorf("invalid patch version: %s", parts[2])
	}

	return NewSemver(major, minor, patch), nil
}

// Versions is a slice of Semver instances
type Versions []Semver

func (v Versions) Len() int {
	return len(v)
}

func (v Versions) Less(i, j int) bool {
	if v[i].Major != v[j].Major {
		return v[i].Major < v[j].Major
	}
	if v[i].Minor != v[j].Minor {
		return v[i].Minor < v[j].Minor
	}
	return v[i].Patch < v[j].Patch
}

func (v Versions) Swap(i, j int) {
	v[i], v[j] = v[j], v[i]
}

func solution(input []string) []string {
	var versions Versions
	for _, v := range input {
		semver, err := ParseSemver(v)
		if err == nil {
			versions = append(versions, semver)
		}
	}

	sort.Sort(sort.Reverse(versions))
	result := []string{}
	for _, v := range versions {
		result = append(result, v.ToString())
	}
	return result
}

func main() {
	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"}

	result := solution(input)
	fmt.Println(result)
}

go puzzle snippets