Published at 2023-04-09T22:31:42+03:00

,_---~~~~~----._ _,,_,*^____ _____``*g*\"*, / __/ /' ^. / \ ^@q f [ @f | @)) | | @)) l 0 _/ \`/ \~____ / __ \_____/ \ | _l__l_ I } [______] I ] | | | | ] ~ ~ | | | | |

This is the first blog post about my Algorithms and Data Structures in Go series. I am not a Software Developer in my day job. In my current role, programming and scripting skills are desirable but not mandatory. I have been learning about Data Structures and Algorithms many years ago at University. I thought it would be fun to revisit/refresh my knowledge here and implement many of the algorithms in Go.

2023-04-09 Algorithms and Data Structures in Go - Part 1 (You are currently reading this)

This post is about setting up some basic data structures and methods for this blog series. I promise, everything will be easy to follow in this post. It will become more interesting later in this series.

First, the package ds (data structures) defines the types.go. All examples will either operate on the Integer or Number type:

packagedsimport( "golang.org/x/exp/constraints" )typeIntegerinterface{ constraints.Integer }typeNumberinterface{ constraints.Integer | constraints.Float }

Next comes the arraylist.go, which defines the underlying data structure all the algorithms of this series will use. ArrayList is just a type alias of a Go array (or slice) with custom methods on it:

packagedsimport( "fmt" "math/rand" "strings" )typeArrayList[V Number] []VfuncNewArrayList[V Number](l int) ArrayList[V] {returnmake(ArrayList[V], l) }

As you can see, the code uses Go generics, which I refactored recently. Besides the default constructor (which only returns an empty ArrayList with a given capacity), there are also a bunch of special constructors. NewRandomArrayList is returning an ArrayList with random numbers, NewAscendingArrayList and NewDescendingArrayList are returning ArrayLists in either ascending or descending order. They all will be used later on for testing and benchmarking the algorithms.

funcNewRandomArrayList[V Number](l, max int) ArrayList[V] { a :=make(ArrayList[V], l)fori := 0; i < l; i++ {ifmax > 0 { a[i] =V(rand.Intn(max))continue} a[i] =V(rand.Int()) }returna }funcNewAscendingArrayList[V Number](l int) ArrayList[V] { a :=make(ArrayList[V], l)fori := 0; i < l; i++ { a[i] =V(i) }returna }funcNewDescendingArrayList[V Number](l int) ArrayList[V] { a :=make(ArrayList[V], l) j := l - 1fori := 0; i < l; i++ { a[i] =V(j) j-- }returna }

The FirstN method only returns the first N elements of the ArrayList. This is useful for printing out only parts of the data structure:

func(a ArrayList[V])FirstN(n int) string {varsb strings.Builder j := n l :=len(a)ifj > l { j = l }fori := 0; i < j; i++ { fmt.Fprintf(&sb, "%v ", a[i]) }ifj < l { fmt.Fprintf(&sb, "... ") }returnsb.String() }

The Sorted method checks whether the ArrayList is sorted. This will be used by the unit tests later on:

func(a ArrayList[V])Sorted() bool {fori :=len(a) - 1; i > 0; i-- {ifa[i] < a[i-1] {returnfalse } }returntrue }

And the last utility method used is Swap, which allows swapping the values of two indices in the ArrayList:

func(a ArrayList[V])Swap(i, j int) { aux := a[i] a[i] = a[j] a[j] = aux }

Let's implement our first algorithm, sleep sort. Sleep sort is a non-traditional and unconventional sorting algorithm based on the idea of waiting a certain amount of time corresponding to the value of each element in the input ArrayList. It's more of a fun, creative concept rather than an efficient or practical sorting technique. This is not a sorting algorithm you would use in any production code. As you can imagine, it is quite an inefficient sorting algorithm (it's only listed here as a warm-up exercise). This sorting method may also return false results depending on how the Goroutines are scheduled by the Go runtime.

packagesortimport( "codeberg.org/snonux/algorithms/ds" "sync" "time" )funcSleep[V ds.Integer](a ds.ArrayList[V]) ds.ArrayList[V] { sorted := ds.NewArrayList[V](len(a)) numCh :=make(chanV)varwg sync.WaitGroup wg.Add(len(a))gofunc() { wg.Wait()close(numCh) }()for_, num :=rangea {gofunc(num V) {deferwg.Done() time.Sleep(time.Duration(num) * time.Second) numCh <- num }(num) }fornum :=rangenumCh { sorted =append(sorted, num) }returnsorted }

This Go code implements the sleep sort algorithm using generics and goroutines. The main function Sleep[V ds.Integer](a ds.ArrayList[V]) ds.ArrayList[V] takes a generic ArrayList as input and returns a sorted ArrayList. The code creates a separate goroutine for each element in the input array, sleeps for a duration proportional to the element's value, and then sends the element to a channel. Another goroutine waits for all the sleeping goroutines to finish and then closes the channel. The sorted result ArrayList is constructed by appending the elements received from the channel in the order they arrive. The sync.WaitGroup is used to synchronize goroutines and ensure that all of them have completed before closing the channel.

For testing, we only allow values up to 10, as otherwise, it would take too long to finish:

packagesortimport( "fmt" "testing" "codeberg.org/snonux/algorithms/ds" )funcTestSleepSort(t *testing.T) { a := ds.NewRandomArrayList[int](10, 10) a =Sleep(a)if!a.Sorted() { t.Errorf("Array not sorted: %v", a) } }

As you can see, it takes 9s here for the algorithm to finish (which is the highest value in the ArrayList):

❯ gotest./sort -v -run SleepSort === RUN TestSleepSort --- PASS: TestSleepSort (9.00s) PASS ok codeberg.org/snonux/algorithms/sort 9.002s

I won't write any benchmark for sleep sort; that will be done for the algorithms to come in this series :-).

E-Mail your comments to paul at buetow.org :-)

Back to the main site