Skip to content

Commit

Permalink
Refactor using generics
Browse files Browse the repository at this point in the history
  • Loading branch information
fako1024 committed Jun 25, 2024
1 parent 21a6e49 commit c155acd
Show file tree
Hide file tree
Showing 13 changed files with 177 additions and 367 deletions.
50 changes: 20 additions & 30 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,32 +22,31 @@ To install it, run:
API summary
-----------------

The API of the topo package is fairly straight-forward. The following types / methods are exposed:
The API of the topo package is fairly straight-forward. The following generics-based types / methods are exposed:

```Go
// Type defines a generic data type
type Type interface{}

// Dependency represents a dependency between one Type and another
type Dependency struct {
Child Type
Parent Type
type Dependency[T comparable] struct {
Child T
Parent T
}

// Dependencies represents a list of dependencies
type Dependencies[T comparable] []Dependency[T]

// String tries to stringify a dependency. If the type of the dependency fulfills
// the Stringer interface, it will use its String() method, otherwise it will try
// to format the variable otherwise
func (d Dependency) String() string
func (d Dependency[T]) String() string

// Sort performs a topological sort on a slice using a functional approach to generalize
// the input data, constructs a directed graph (using the dependency constraints) and
// finally converts back the resulting object list to the original slice (sort in place)
func Sort(data interface{}, deps []Dependency, getter func(i int) Type, setter func(i int, val Type)) (err error)
// Sort performs a topological sort on a slice and constructs a directed graph (using the
// dependency constraints) and finally converts back the resulting object list to the
// original slice (sort in place)
func Sort[T comparable](data graph.Objects[T], deps Dependencies[T]) (err error)

```
In order to perform a dependency resolution, first a slice or array containing all elements to be sorted and a list of all dependencies have to be created.
Afterwards, a "Getter" and a "Setter" function have to be defined in order to perform the actual type conversion for the type in question.
Finally, the actual Sort() call can be performed, causing the original slice to be sorted in-place so as to satisfy all dependencies.
Afterwards, the actual Sort() call can be performed, causing the original slice to be sorted in-place so as to satisfy all dependencies.
Note: Sort() is a stable sort algorithm, hence the actual order of elements in the output will be deterministic. A detailed, yet simple example can be found below.

License
Expand All @@ -73,27 +72,18 @@ var stringsToSort = []string{
}

// List of dependencies
var stringDependencies = []topo.Dependency{
topo.Dependency{Child: "B", Parent: "A"},
topo.Dependency{Child: "B", Parent: "C"},
topo.Dependency{Child: "B", Parent: "D"},
topo.Dependency{Child: "A", Parent: "E"},
topo.Dependency{Child: "D", Parent: "C"},
var stringDependencies = []topo.Dependency[string]{
{Child: "B", Parent: "A"},
{Child: "B", Parent: "C"},
{Child: "B", Parent: "D"},
{Child: "A", Parent: "E"},
{Child: "D", Parent: "C"},
}

func main() {
// Getter function to convert original elements to a generic type
getter := func(i int) topo.Type {
return stringsToSort[i]
}

// Setter function to restore the original type of the data
setter := func(i int, val topo.Type) {
stringsToSort[i] = val.(string)
}

// Perform topological sort
if err := topo.Sort(stringsToSort, stringDependencies, getter, setter); err != nil {
if err := topo.Sort(stringsToSort, stringDependencies); err != nil {
fmt.Printf("Error performing topological sort on slice of strings: %s\n", err)
os.Exit(1)
}
Expand Down
49 changes: 6 additions & 43 deletions example_iota_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ package topo
import (
"fmt"
"testing"

"github.com/stretchr/testify/require"
)

// PluginType is a dummy type for the iota below
Expand Down Expand Up @@ -46,28 +48,16 @@ func testIotaType(t *testing.T) []PluginType {
}

// All plugin dependencies
var pluginDependencies = []Dependency{
var pluginDependencies = []Dependency[PluginType]{
{Child: B, Parent: A},
{Child: B, Parent: C},
{Child: B, Parent: D},
{Child: A, Parent: E},
{Child: D, Parent: C},
}

// Getter function to convert original elements to a generic type
getter := func(i int) Type {
return allPlugins[i]
}

// Setter function to restore the original type of the data
setter := func(i int, val Type) {
allPlugins[i] = val.(PluginType)
}

// Perform topological sort
if err := Sort(allPlugins, pluginDependencies, getter, setter); err != nil {
t.Fatal(err)
}
require.Nil(t, Sort(allPlugins, pluginDependencies))

// Check if all dependencies are fulfilled
for _, dependency := range pluginDependencies {
Expand All @@ -81,9 +71,7 @@ func testIotaType(t *testing.T) []PluginType {
}
}

if posTo >= posFrom {
t.Fatalf("Unexpected order, want pos(%v) < pos(%v) for %v / %v", posTo, posFrom, dependency.Child, dependency.Parent)
}
require.Less(t, posTo, posFrom)
}

return allPlugins
Expand All @@ -97,31 +85,6 @@ func TestIotaTypeStability(t *testing.T) {
expected := testIotaType(t)

for run := 0; run < nRunsConsistency; run++ {
if res := testIotaType(t); !testEqIota(res, expected) {
t.Fatalf("API stability violation, want %s, have %s", expected, res)
}
}
}

func testEqIota(a, b []PluginType) bool {

if a == nil && b == nil {
return true
}

if a == nil || b == nil {
return false
}

if len(a) != len(b) {
return false
require.EqualValues(t, expected, testIotaType(t))
}

for i := range a {
if a[i] != b[i] {
return false
}
}

return true
}
54 changes: 9 additions & 45 deletions example_simple_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@

package topo

import "testing"
import (
"testing"

"github.com/stretchr/testify/require"
)

func testStringType(t *testing.T) []string {

Expand All @@ -24,28 +28,16 @@ func testStringType(t *testing.T) []string {
}

// All string dependencies
var stringDependencies = []Dependency{
var stringDependencies = []Dependency[string]{
{Child: "B", Parent: "A"},
{Child: "B", Parent: "C"},
{Child: "B", Parent: "D"},
{Child: "A", Parent: "E"},
{Child: "D", Parent: "C"},
}

// Getter function to convert original elements to a generic type
getter := func(i int) Type {
return allStrings[i]
}

// Setter function to restore the original type of the data
setter := func(i int, val Type) {
allStrings[i] = val.(string)
}

// Perform topological sort
if err := Sort(allStrings, stringDependencies, getter, setter); err != nil {
t.Fatal(err)
}
require.Nil(t, Sort(allStrings, stringDependencies))

// Check if all StrDependencies are fulfilled
for _, dependency := range stringDependencies {
Expand All @@ -59,9 +51,7 @@ func testStringType(t *testing.T) []string {
}
}

if posTo >= posFrom {
t.Fatalf("Unexpected order, want pos(%v) < pos(%v) for %v / %v", posTo, posFrom, dependency.Child, dependency.Parent)
}
require.Less(t, posTo, posFrom)
}

return allStrings
Expand All @@ -73,33 +63,7 @@ func TestStringType(t *testing.T) {

func TestStringTypeStability(t *testing.T) {
expected := testStringType(t)

for run := 0; run < nRunsConsistency; run++ {
if res := testStringType(t); !testEqString(res, expected) {
t.Fatalf("API stability violation, want %s, have %s", expected, res)
}
require.EqualValues(t, expected, testStringType(t))
}
}

func testEqString(a, b []string) bool {

if a == nil && b == nil {
return true
}

if a == nil || b == nil {
return false
}

if len(a) != len(b) {
return false
}

for i := range a {
if a[i] != b[i] {
return false
}
}

return true
}
54 changes: 9 additions & 45 deletions example_struct_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@

package topo

import "testing"
import (
"testing"

"github.com/stretchr/testify/require"
)

// StructType is a struct type
type StructType struct {
Expand All @@ -28,25 +32,13 @@ func testStructType(t *testing.T) []StructType {
}

// List of all struct dependencies
var structDependencies = []Dependency{
var structDependencies = []Dependency[StructType]{
{Child: StructType{"A", 1, 1.0}, Parent: StructType{"C", 3, 3.0}},
{Child: StructType{"D", 4, 4.0}, Parent: StructType{"E", 5, 5.0}},
}

// Getter function to convert original elements to a generic type
getter := func(i int) Type {
return allStructs[i]
}

// Setter function to restore the original type of the data
setter := func(i int, val Type) {
allStructs[i] = val.(StructType)
}

// Perform topological sort
if err := Sort(allStructs, structDependencies, getter, setter); err != nil {
t.Fatal(err)
}
require.Nil(t, Sort(allStructs, structDependencies))

// Check if all StrDependencies are fulfilled
for _, dependency := range structDependencies {
Expand All @@ -60,9 +52,7 @@ func testStructType(t *testing.T) []StructType {
}
}

if posTo >= posFrom {
t.Fatalf("Unexpected order, want pos(%v) < pos(%v) for %v / %v", posTo, posFrom, dependency.Child, dependency.Parent)
}
require.Less(t, posTo, posFrom)
}

return allStructs
Expand All @@ -74,33 +64,7 @@ func TestStructType(t *testing.T) {

func TestStructTypeStability(t *testing.T) {
expected := testStructType(t)

for run := 0; run < nRunsConsistency; run++ {
if res := testStructType(t); !testEqStruct(res, expected) {
t.Fatalf("API stability violation, want %v, have %v", expected, res)
}
require.EqualValues(t, expected, testStructType(t))
}
}

func testEqStruct(a, b []StructType) bool {

if a == nil && b == nil {
return true
}

if a == nil || b == nil {
return false
}

if len(a) != len(b) {
return false
}

for i := range a {
if a[i] != b[i] {
return false
}
}

return true
}
9 changes: 8 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
module github.com/fako1024/topo

go 1.15
go 1.22

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/stretchr/testify v1.9.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
9 changes: 9 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
Loading

0 comments on commit c155acd

Please sign in to comment.