Skip to content

Commit

Permalink
refactored functions into their own files, improved docs.
Browse files Browse the repository at this point in the history
  • Loading branch information
datosh committed Feb 5, 2022
1 parent 86ecbc3 commit 4d37964
Show file tree
Hide file tree
Showing 10 changed files with 204 additions and 109 deletions.
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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)

Expand Down
15 changes: 15 additions & 0 deletions each.go
Original file line number Diff line number Diff line change
@@ -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)
}
File renamed without changes.
13 changes: 13 additions & 0 deletions examples/sum/main.go
Original file line number Diff line number Diff line change
@@ -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
}
19 changes: 19 additions & 0 deletions fold.go
Original file line number Diff line number Diff line change
@@ -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)
}
28 changes: 28 additions & 0 deletions fold_test.go
Original file line number Diff line number Diff line change
@@ -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)
})
}
89 changes: 0 additions & 89 deletions iteration.go

This file was deleted.

3 changes: 3 additions & 0 deletions map.go
Original file line number Diff line number Diff line change
@@ -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))
Expand All @@ -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)
}
69 changes: 69 additions & 0 deletions permutations.go
Original file line number Diff line number Diff line change
@@ -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
}
58 changes: 38 additions & 20 deletions iteration_test.go → permutations_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand All @@ -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)
})
}

0 comments on commit 4d37964

Please sign in to comment.