Skip to content

Commit

Permalink
Create util_random_test.go
Browse files Browse the repository at this point in the history
  • Loading branch information
MaxHalford committed Jun 26, 2017
1 parent b08b8ac commit d8a0740
Show file tree
Hide file tree
Showing 6 changed files with 135 additions and 82 deletions.
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
## Ideas

- Enhance TS tests
- Add more context to errors (at least the method/struct name)
- Add more context to errors (at least the method/struct name) (https://dave.cheney.net/2016/04/27/dont-just-check-errors-handle-them-gracefully)
- Add more example usage
- Make it easier to test models
- Implement operators described in http://www.ppgia.pucpr.br/~alceu/mestrado/aula3/IJBB-41.pdf
Expand Down
8 changes: 4 additions & 4 deletions distance.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,21 +67,21 @@ func calcAvgDistances(indis Individuals, dm DistanceMemoizer) map[string]float64
return avgDistances
}

func rebalanceClusters(clusters []Individuals, dm DistanceMemoizer, minPerCluster int) ([]Individuals, error) {
func rebalanceClusters(clusters []Individuals, dm DistanceMemoizer, minPerCluster int) error {
// Calculate the number of missing Individuals per cluster for each cluster
// to reach at least minPerCluster Individuals.
var missing = make([]int, len(clusters))
for i, cluster := range clusters {
// Check that the cluster has at least on Individual
if len(cluster) == 0 {
return nil, fmt.Errorf("Cluster %d has 0 individuals", i)
return fmt.Errorf("Cluster %d has 0 individuals", i)
}
// Calculate the number of missing Individual in the cluster to reach minPerCluster
missing[i] = minPerCluster - len(cluster)
}
// Check if there are enough Individuals to rebalance the clusters.
if sumInts(missing) >= 0 {
return nil, fmt.Errorf("Missing %d individuals to be able to rebalance the clusters",
return fmt.Errorf("Missing %d individuals to be able to rebalance the clusters",
sumInts(missing))
}
// Loop through the clusters that are missing Individuals
Expand Down Expand Up @@ -124,5 +124,5 @@ func rebalanceClusters(clusters []Individuals, dm DistanceMemoizer, minPerCluste
missing[i]--
}
}
return clusters, nil
return nil
}
50 changes: 49 additions & 1 deletion distance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,11 @@ func TestRebalanceClusters(t *testing.T) {
}
dm = newDistanceMemoizer(l1Distance)
)
rebalanceClusters(clusters, dm, 2)
var err = rebalanceClusters(clusters, dm, 2)
// Check there is not error
if err != nil {
t.Error("rebalanceClusters should not have returned an error")
}
// Check the second cluster
if len(clusters[1]) != 2 || clusters[1][1].ID != "4" {
t.Error("rebalanceClusters didn't work as expected")
Expand All @@ -95,3 +99,47 @@ func TestRebalanceClusters(t *testing.T) {
t.Error("rebalanceClusters didn't work as expected")
}
}

// If a cluster is empty then rebalancing is impossible
func TestRebalanceClustersEmptyCluster(t *testing.T) {
var (
clusters = []Individuals{
Individuals{
Individual{Genome: Vector{1, 1, 1}, ID: "1"},
Individual{Genome: Vector{1, 1, 1}, ID: "2"},
Individual{Genome: Vector{1, 1, 1}, ID: "3"},
},
Individuals{},
}
dm = newDistanceMemoizer(l1Distance)
)
var err = rebalanceClusters(clusters, dm, 2)
if err == nil {
t.Error("rebalanceClusters should have returned an error")
}
}

// It's impossible to put 2 Individuals inside each cluster if there are 3
// clusters and 5 individuals in total
func TestRebalanceClustersTooManyMissing(t *testing.T) {
var (
clusters = []Individuals{
Individuals{
Individual{Genome: Vector{1, 1, 1}, ID: "1"},
Individual{Genome: Vector{1, 1, 1}, ID: "2"},
Individual{Genome: Vector{1, 1, 1}, ID: "3"},
},
Individuals{
Individual{Genome: Vector{2, 2, 2}, ID: "6"},
},
Individuals{
Individual{Genome: Vector{3, 3, 3}, ID: "7"},
},
}
dm = newDistanceMemoizer(l1Distance)
)
var err = rebalanceClusters(clusters, dm, 2)
if err == nil {
t.Error("rebalanceClusters should have returned an error")
}
}
32 changes: 16 additions & 16 deletions util_random.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,22 @@ func newRandomNumberGenerator() *rand.Rand {
return rand.New(rand.NewSource(time.Now().UnixNano()))
}

// Sample k unique integers in range [min, max) using reservoir sampling,
// specifically Vitter's Algorithm R.
func randomInts(k, min, max int, rng *rand.Rand) (ints []int) {
ints = make([]int, k)
for i := 0; i < k; i++ {
ints[i] = i + min
}
for i := k; i < max-min; i++ {
var j = rng.Intn(i + 1)
if j < k {
ints[j] = i + min
}
}
return
}

// Sample k unique integers from a slice of n integers without replacement.
func sampleInts(ints []int, k int, rng *rand.Rand) ([]int, []int, error) {
if k > len(ints) {
Expand Down Expand Up @@ -42,22 +58,6 @@ func randomWeights(size int) []float64 {
return normalized
}

// Sample k unique integers in range [min, max) using reservoir sampling,
// specifically Vitter's Algorithm R.
func randomInts(k, min, max int, rng *rand.Rand) (ints []int) {
ints = make([]int, k)
for i := 0; i < k; i++ {
ints[i] = i + min
}
for i := k; i < max-min; i++ {
var j = rng.Intn(i + 1)
if j < k {
ints[j] = i + min
}
}
return
}

const (
letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
letterIdxBits = 6 // 6 bits to represent a letter index
Expand Down
65 changes: 65 additions & 0 deletions util_random_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package gago

import (
"math"
"math/rand"
"testing"
"time"
)

func TestRandomInts(t *testing.T) {
var (
src = rand.NewSource(time.Now().UnixNano())
rng = rand.New(src)
testCases = []struct {
k, min, max int
}{
{1, 0, 1},
{1, 0, 2},
{2, 0, 2},
}
)
for _, test := range testCases {
var ints = randomInts(test.k, test.min, test.max, rng)
// Check the number of generated integers
if len(ints) != test.k {
t.Error("randomInts didn't generate the right number of integers")
}
// Check the bounds of each generated integer
for _, integer := range ints {
if integer < test.min || integer >= test.max {
t.Error("randomInts didn't generate integers in the desired range")
}
}
// Check the generated integers are unique
for i, a := range ints {
for j, b := range ints {
if i != j && a == b {
t.Error("randomInts didn't generate unique integers")
}
}
}
}
}

func TestRandomWeights(t *testing.T) {
var (
sizes = []int{1, 30, 10000}
limit = math.Pow(1, -10)
)
for _, size := range sizes {
var weights = randomWeights(size)
// Test the length of the resulting slice
if len(weights) != size {
t.Error("Size problem with randomWeights")
}
// Test the elements in the slice sum up to 1
var sum float64
for _, weight := range weights {
sum += weight
}
if math.Abs(sum-1.0) > limit {
t.Error("Sum problem with randomWeights")
}
}
}
60 changes: 0 additions & 60 deletions util_test.go
Original file line number Diff line number Diff line change
@@ -1,34 +1,9 @@
package gago

import (
"math"
"math/rand"
"testing"
"time"
)

func TestGenerateWeights(t *testing.T) {
var (
sizes = []int{1, 30, 10000}
limit = math.Pow(1, -10)
)
for _, size := range sizes {
var weights = randomWeights(size)
// Test the length of the resulting slice
if len(weights) != size {
t.Error("Size problem with randomWeights")
}
// Test the elements in the slice sum up to 1
var sum float64
for _, weight := range weights {
sum += weight
}
if math.Abs(sum-1.0) > limit {
t.Error("Sum problem with randomWeights")
}
}
}

func TestMin(t *testing.T) {
var testCases = [][]int{
[]int{1, 2},
Expand Down Expand Up @@ -160,41 +135,6 @@ func TestCumsum(t *testing.T) {
}
}

func TestRandomInts(t *testing.T) {
var (
src = rand.NewSource(time.Now().UnixNano())
rng = rand.New(src)
testCases = []struct {
k, min, max int
}{
{1, 0, 1},
{1, 0, 2},
{2, 0, 2},
}
)
for _, test := range testCases {
var ints = randomInts(test.k, test.min, test.max, rng)
// Check the number of generated integers
if len(ints) != test.k {
t.Error("randomInts didn't generate the right number of integers")
}
// Check the bounds of each generated integer
for _, integer := range ints {
if integer < test.min || integer >= test.max {
t.Error("randomInts didn't generate integers in the desired range")
}
}
// Check the generated integers are unique
for i, a := range ints {
for j, b := range ints {
if i != j && a == b {
t.Error("randomInts didn't generate unique integers")
}
}
}
}
}

func TestUnion(t *testing.T) {
var testCases = []struct {
x set
Expand Down

0 comments on commit d8a0740

Please sign in to comment.