From 4d37964ee5d1ec935ffe118d5fb32b4dd2190acb Mon Sep 17 00:00:00 2001 From: Fabian Kammel Date: Sat, 5 Feb 2022 18:02:13 +0000 Subject: [PATCH] refactored functions into their own files, improved docs. --- README.md | 19 +++++ each.go | 15 ++++ examples/{ => stringSlice}/main.go | 0 examples/sum/main.go | 13 ++++ fold.go | 19 +++++ fold_test.go | 28 +++++++ iteration.go | 89 ----------------------- map.go | 3 + permutations.go | 69 ++++++++++++++++++ iteration_test.go => permutations_test.go | 58 ++++++++++----- 10 files changed, 204 insertions(+), 109 deletions(-) create mode 100644 each.go rename examples/{ => stringSlice}/main.go (100%) create mode 100644 examples/sum/main.go create mode 100644 fold.go create mode 100644 fold_test.go delete mode 100644 iteration.go create mode 100644 permutations.go rename iteration_test.go => permutations_test.go (70%) diff --git a/README.md b/README.md index 992f65b..3fad53d 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,23 @@ go1.18beta2 get github.com/datosh/algo ```go package main +import ( + "fmt" + + "github.com/datosh/algo" +) + +func main() { + sum := algo.Fold(func(a, b int) int { return a + b }) + numbers := []int{1, 2, 3, 4, 5, 6, 7, 8, 9} + fmt.Println(sum(numbers)) // 45 +} + +``` + +```go +package main + import ( "fmt" "strings" @@ -52,6 +69,8 @@ To get an idea what is usually implemented in such a library I looked at ## Roadmap / Ideas +* "Type cast" a slice of build-in types +* "Type cast" a slice of complex types * Iterator based approach to support any data structure * [On Iteration - Andrei Alexandrescu](https://www.informit.com/articles/printerfriendly/1407357) diff --git a/each.go b/each.go new file mode 100644 index 0000000..8de74cb --- /dev/null +++ b/each.go @@ -0,0 +1,15 @@ +package algo + +// Each applies the provided function to each element of slice. +func Each[T any](function func(T)) func([]T) { + return func(elements []T) { + for i := range elements { + function(elements[i]) + } + } +} + +// Each2 is the same as Each, but with single function call. +func Each2[T any](elements []T, function func(T)) { + Each(function)(elements) +} diff --git a/examples/main.go b/examples/stringSlice/main.go similarity index 100% rename from examples/main.go rename to examples/stringSlice/main.go diff --git a/examples/sum/main.go b/examples/sum/main.go new file mode 100644 index 0000000..6d4efd1 --- /dev/null +++ b/examples/sum/main.go @@ -0,0 +1,13 @@ +package main + +import ( + "fmt" + + "github.com/datosh/algo" +) + +func main() { + sum := algo.Fold(func(a, b int) int { return a + b }) + numbers := []int{1, 2, 3, 4, 5, 6, 7, 8, 9} + fmt.Println(sum(numbers)) // 45 +} diff --git a/fold.go b/fold.go new file mode 100644 index 0000000..0a9faca --- /dev/null +++ b/fold.go @@ -0,0 +1,19 @@ +package algo + +// Fold (also called Reduce) applies a combining operator to each +// element of the slice. +// Also see: https://en.wikipedia.org/wiki/Fold_(higher-order_function) +func Fold[T any](combiner func(T, T) T) func([]T) T { + return func(elements []T) T { + result := elements[0] + for i := 1; i < len(elements); i++ { + result = combiner(result, elements[i]) + } + return result + } +} + +// Fold2 is the same as Fold, but with single function call. +func Fold2[T any](elements []T, combiner func(T, T) T) T { + return Fold(combiner)(elements) +} diff --git a/fold_test.go b/fold_test.go new file mode 100644 index 0000000..3fa1774 --- /dev/null +++ b/fold_test.go @@ -0,0 +1,28 @@ +package algo_test + +import ( + "testing" + + "github.com/datosh/algo" + "github.com/stretchr/testify/assert" +) + +func Test_Fold(t *testing.T) { + t.Run("summing slice of int", func(t *testing.T) { + numbers := []int{2, 3, 4, 5} + sumOp := func(a, b int) int { return a + b } + + sum := algo.Fold(sumOp)(numbers) + + assert.Equal(t, 14, sum) + }) + + t.Run("concatenate strings", func(t *testing.T) { + words := []string{"this", "is", "freakin", "awesome"} + cat := func(s1, s2 string) string { return s1 + " " + s2 } + + sentence := algo.Fold(cat)(words) + + assert.Equal(t, "this is freakin awesome", sentence) + }) +} diff --git a/iteration.go b/iteration.go deleted file mode 100644 index 59c3fc5..0000000 --- a/iteration.go +++ /dev/null @@ -1,89 +0,0 @@ -package algo - -func Each[T any](function func(T)) func([]T) { - return func(elements []T) { - for i := range elements { - function(elements[i]) - } - } -} - -func Each2[T any](elements []T, function func(T)) { - Each(function)(elements) -} - -// There is no need to implement Swap in go, a simple -// a, b = b, a -// can be written in line -// func Swap[T any](a, b T) (T, T) { -// return b, a -// } - -func factorial(n int) int { - // TODO: Test for n < 0 - // TODO: What is the upper limit for int? 20? - // https://en.wikipedia.org/wiki/Factorial - result := 1 - for i := 1; i <= n; i++ { - result *= i - } - return result -} - -func Permutations[T any](elements []T) [][]T { - localElements := make([]T, len(elements)) - copy(localElements, elements) - - permutations := make([][]T, factorial(len(localElements))) - for i := 0; i < len(permutations); i++ { - permutations[i] = make([]T, len(localElements)) - } - permutationIdx := 0 - counters := make([]int, len(localElements)) - - copy(permutations[permutationIdx], localElements) - permutationIdx++ - - // i acts similarly to a stack pointer - i := 0 - for i < len(localElements) { - if counters[i] < i { - if i%2 == 0 { - localElements[0], localElements[i] = localElements[i], localElements[0] - } else { - localElements[counters[i]], localElements[i] = localElements[i], localElements[counters[i]] - } - - copy(permutations[permutationIdx], localElements) - permutationIdx++ - counters[i] += 1 - - i = 0 - } else { - counters[i] = 0 - i++ - } - } - return permutations -} - -// Fold (also called Reduce) applies a combining operator to each -// element of the slice. -// Also see: https://en.wikipedia.org/wiki/Fold_(higher-order_function) -func Fold[T any](combiner func(T, T) T) func([]T) T { - return func(elements []T) T { - result := elements[0] - for i := 1; i < len(elements); i++ { - result = combiner(result, elements[i]) - } - return result - } -} - -func Fold2[T any](elements []T, combiner func(T, T) T) T { - result := elements[0] - for i := 1; i < len(elements); i++ { - result = combiner(result, elements[i]) - } - return result -} diff --git a/map.go b/map.go index 14d8077..7d41b46 100644 --- a/map.go +++ b/map.go @@ -1,5 +1,7 @@ package algo +// Map takes a mapping function and applies it to each element of the slice, +// returning a new slice with the mapped elements. func Map[FromType, ToType any](mapper func(t FromType) ToType) func([]FromType) []ToType { return func(elements []FromType) []ToType { result := make([]ToType, len(elements)) @@ -10,6 +12,7 @@ func Map[FromType, ToType any](mapper func(t FromType) ToType) func([]FromType) } } +// Map2 is the same as Map, but with single function call. func Map2[FromType, ToType any](elements []FromType, mapper func(t FromType) ToType) []ToType { return Map(mapper)(elements) } diff --git a/permutations.go b/permutations.go new file mode 100644 index 0000000..a654f79 --- /dev/null +++ b/permutations.go @@ -0,0 +1,69 @@ +package algo + +// Permutations uses non-recursive Heap's algorithm to calculate all +// permutations of the provided input slice **eagerly**. +// The argument will not be manipulated. +// The result will be of size: factorial of number of input elements. +// See also: https://en.wikipedia.org/wiki/Heap%27s_algorithm +func Permutations[T any](elements []T) [][]T { + localElements := makeCopy(elements) + + permutations := make([][]T, factorial(len(localElements))) + for i := 0; i < len(permutations); i++ { + permutations[i] = make([]T, len(localElements)) + } + permutationIdx := 0 + counters := make([]int, len(localElements)) + + copy(permutations[permutationIdx], localElements) + permutationIdx++ + + // i acts similarly to a stack pointer + i := 0 + for i < len(localElements) { + if counters[i] < i { + if i%2 == 0 { + Swap(&localElements[0], &localElements[i]) + } else { + Swap(&localElements[counters[i]], &localElements[i]) + } + + copy(permutations[permutationIdx], localElements) + permutationIdx++ + counters[i] += 1 + + i = 0 + } else { + counters[i] = 0 + i++ + } + } + return permutations +} + +// Should Swap be provided? Swapping is a simple as +// a, b = b, a +// but having a speaking function name might be easier to read? +func Swap[T any](a, b *T) { + *b, *a = *a, *b +} + +// factorial calculates n! +// See also: https://en.wikipedia.org/wiki/Factorial +func factorial(n int) int { + // TODO: Test for n < 0 + // TODO: What is the upper limit for int? 20? + result := 1 + for i := 1; i <= n; i++ { + result *= i + } + return result +} + +// makeCopy extends go's build-in copy by creating a slice (of correct size) +// as copy target and returns it. +func makeCopy[T any](src []T) []T { + dest := make([]T, len(src)) + copy(dest, src) + return dest +} diff --git a/iteration_test.go b/permutations_test.go similarity index 70% rename from iteration_test.go rename to permutations_test.go index 461b3b3..0e53bea 100644 --- a/iteration_test.go +++ b/permutations_test.go @@ -37,6 +37,44 @@ func Test_factorial(t *testing.T) { }) } +func Test_Swap(t *testing.T) { + t.Run("swap int", func(t *testing.T) { + a := 1337 + b := 42 + + Swap(&a, &b) + + assert.Equal(t, 42, a) + assert.Equal(t, 1337, b) + }) + + t.Run("swap string", func(t *testing.T) { + a := "abc" + b := "def" + + Swap(&a, &b) + + assert.Equal(t, "def", a) + assert.Equal(t, "abc", b) + }) + + t.Run("swap complex types", func(t *testing.T) { + type foo struct { + bar string + baz int + } + a := foo{"abc", 1337} + b := foo{"def", 42} + + Swap(&a, &b) + + assert.Equal(t, 42, a.baz) + assert.Equal(t, "def", a.bar) + assert.Equal(t, 1337, b.baz) + assert.Equal(t, "abc", b.bar) + }) +} + func Test_Permutations(t *testing.T) { t.Run("permutate slice of 3 ints", func(t *testing.T) { numbers := []int{2, 3, 4} @@ -63,23 +101,3 @@ func Test_Permutations(t *testing.T) { assert.Equal(t, "hello", s) }) } - -func Test_Fold(t *testing.T) { - t.Run("summing slice of int", func(t *testing.T) { - numbers := []int{2, 3, 4, 5} - sumOp := func(a, b int) int { return a + b } - - sum := Fold(sumOp)(numbers) - - assert.Equal(t, 14, sum) - }) - - t.Run("concatenate strings", func(t *testing.T) { - words := []string{"this", "is", "freakin", "awesome"} - cat := func(s1, s2 string) string { return s1 + " " + s2 } - - sentence := Fold(cat)(words) - - assert.Equal(t, "this is freakin awesome", sentence) - }) -}