Rename random to shuffle. Remove count parameteter to simplify its role. Add tests for randomising.

This commit is contained in:
Antti Järvinen 2015-12-07 07:27:37 +02:00 committed by Steve Francia
parent 302a6ac701
commit 9008ac0b55
2 changed files with 55 additions and 40 deletions

View file

@ -496,24 +496,14 @@ func After(index interface{}, seq interface{}) (interface{}, error) {
return seqv.Slice(indexv, seqv.Len()).Interface(), nil return seqv.Slice(indexv, seqv.Len()).Interface(), nil
} }
// Random is exposed to templates, to iterate over N random items in a // Shuffle is exposed to templates, to iterate over items in rangeable list in
// rangeable list. // a randomised order.
func Random(count interface{}, seq interface{}) (interface{}, error) { func Shuffle(seq interface{}) (interface{}, error) {
if count == nil || seq == nil { if seq == nil {
return nil, errors.New("both count and seq must be provided") return nil, errors.New("both count and seq must be provided")
} }
countv, err := cast.ToIntE(count)
if err != nil {
return nil, err
}
if countv < 1 {
return nil, errors.New("can't return negative/empty count of items from sequence")
}
seqv := reflect.ValueOf(seq) seqv := reflect.ValueOf(seq)
seqv, isNil := indirect(seqv) seqv, isNil := indirect(seqv)
if isNil { if isNil {
@ -527,20 +517,16 @@ func Random(count interface{}, seq interface{}) (interface{}, error) {
return nil, errors.New("can't iterate over " + reflect.ValueOf(seq).Type().String()) return nil, errors.New("can't iterate over " + reflect.ValueOf(seq).Type().String())
} }
if countv >= seqv.Len() { shuffled := reflect.MakeSlice(reflect.TypeOf(seq), seqv.Len(), seqv.Len())
countv = seqv.Len()
}
suffled := reflect.MakeSlice(reflect.TypeOf(seq), seqv.Len(), seqv.Len())
rand.Seed(time.Now().UTC().UnixNano()) rand.Seed(time.Now().UTC().UnixNano())
randomIndices := rand.Perm(seqv.Len()) randomIndices := rand.Perm(seqv.Len())
for index, value := range randomIndices { for index, value := range randomIndices {
suffled.Index(value).Set(seqv.Index(index)) shuffled.Index(value).Set(seqv.Index(index))
} }
return suffled.Slice(0, countv).Interface(), nil return shuffled.Interface(), nil
} }
var ( var (
@ -1501,7 +1487,7 @@ func init() {
"first": First, "first": First,
"last": Last, "last": Last,
"after": After, "after": After,
"random": Random, "shuffle": Shuffle,
"where": Where, "where": Where,
"delimit": Delimit, "delimit": Delimit,
"sort": Sort, "sort": Sort,

View file

@ -25,6 +25,7 @@ import (
"runtime" "runtime"
"testing" "testing"
"time" "time"
"math/rand"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -341,43 +342,71 @@ func TestAfter(t *testing.T) {
} }
} }
func TestRandom(t *testing.T) { func TestShuffleInputAndOutputFormat(t *testing.T) {
for i, this := range []struct { for i, this := range []struct {
count interface{}
sequence interface{} sequence interface{}
expect interface{} success bool
}{ }{
{int(2), []string{"a", "b", "c", "d"}, 2}, {[]string{"a", "b", "c", "d"}, true},
{int64(2), []int{100, 200, 300}, 2}, {[]int{100, 200, 300}, true},
{"1", []int{100, 200, 300}, 1}, {[]int{100, 200, 300}, true},
{100, []int{100, 200}, 2}, {[]int{100, 200}, true},
{int32(3), []string{"a", "b"}, 2}, {[]string{"a", "b"}, true},
{int64(-1), []int{100, 200, 300}, false}, {[]int{100, 200, 300}, true},
{"noint", []int{100, 200, 300}, false}, {[]int{100, 200, 300}, true},
{1, nil, false}, {[]int{100}, true},
{nil, []int{100}, false}, {nil, false},
{1, t, false}, {t, false},
} { } {
results, err := Random(this.count, this.sequence) results, err := Shuffle(this.sequence)
if b, ok := this.expect.(bool); ok && !b { if !this.success {
if err == nil { if err == nil {
t.Errorf("[%d] First didn't return an expected error", i) t.Errorf("[%d] First didn't return an expected error", i)
} }
} else { } else {
resultsv := reflect.ValueOf(results) resultsv := reflect.ValueOf(results)
sequencev := reflect.ValueOf(this.sequence)
if err != nil { if err != nil {
t.Errorf("[%d] failed: %s", i, err) t.Errorf("[%d] failed: %s", i, err)
continue continue
} }
if resultsv.Len() != this.expect { if resultsv.Len() != sequencev.Len() {
t.Errorf("[%d] requested %d random items, got %v but expected %v", t.Errorf("Expected %d items, got %d items", sequencev.Len(), resultsv.Len())
i, this.count, resultsv.Len(), this.expect)
} }
} }
} }
} }
func TestShuffleRandomising(t *testing.T) {
// Note that this test can fail with false negative result if the shuffle
// of the sequence happens to be the same as the original sequence. However
// the propability of the event is 10^-158 which is negligible.
sequenceLength := 100
rand.Seed(time.Now().UTC().UnixNano())
for _, this := range []struct {
sequence []int
}{
{rand.Perm(sequenceLength)},
} {
results, _ := Shuffle(this.sequence)
resultsv := reflect.ValueOf(results)
allSame := true
for index, value := range this.sequence {
allSame = allSame && (resultsv.Index(index).Interface() == value)
}
if allSame {
t.Error("Expected sequence to be shuffled but was in the same order")
}
}
}
func TestDictionary(t *testing.T) { func TestDictionary(t *testing.T) {
for i, this := range []struct { for i, this := range []struct {
v1 []interface{} v1 []interface{}