From 9008ac0b55bc0f507e730e8b3d49fa8103e600e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antti=20J=C3=A4rvinen?= Date: Mon, 7 Dec 2015 07:27:37 +0200 Subject: [PATCH] Rename random to shuffle. Remove count parameteter to simplify its role. Add tests for randomising. --- tpl/template_funcs.go | 30 +++++------------- tpl/template_funcs_test.go | 65 +++++++++++++++++++++++++++----------- 2 files changed, 55 insertions(+), 40 deletions(-) diff --git a/tpl/template_funcs.go b/tpl/template_funcs.go index e933c0bb3..3ef36d694 100644 --- a/tpl/template_funcs.go +++ b/tpl/template_funcs.go @@ -496,24 +496,14 @@ func After(index interface{}, seq interface{}) (interface{}, error) { return seqv.Slice(indexv, seqv.Len()).Interface(), nil } -// Random is exposed to templates, to iterate over N random items in a -// rangeable list. -func Random(count interface{}, seq interface{}) (interface{}, error) { +// Shuffle is exposed to templates, to iterate over items in rangeable list in +// a randomised order. +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") } - 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, isNil := indirect(seqv) 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()) } - if countv >= seqv.Len() { - countv = seqv.Len() - } - - suffled := reflect.MakeSlice(reflect.TypeOf(seq), seqv.Len(), seqv.Len()) + shuffled := reflect.MakeSlice(reflect.TypeOf(seq), seqv.Len(), seqv.Len()) rand.Seed(time.Now().UTC().UnixNano()) randomIndices := rand.Perm(seqv.Len()) 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 ( @@ -1501,7 +1487,7 @@ func init() { "first": First, "last": Last, "after": After, - "random": Random, + "shuffle": Shuffle, "where": Where, "delimit": Delimit, "sort": Sort, diff --git a/tpl/template_funcs_test.go b/tpl/template_funcs_test.go index 91d3cbd1c..95e84bf36 100644 --- a/tpl/template_funcs_test.go +++ b/tpl/template_funcs_test.go @@ -25,6 +25,7 @@ import ( "runtime" "testing" "time" + "math/rand" "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 { - count interface{} sequence interface{} - expect interface{} + success bool }{ - {int(2), []string{"a", "b", "c", "d"}, 2}, - {int64(2), []int{100, 200, 300}, 2}, - {"1", []int{100, 200, 300}, 1}, - {100, []int{100, 200}, 2}, - {int32(3), []string{"a", "b"}, 2}, - {int64(-1), []int{100, 200, 300}, false}, - {"noint", []int{100, 200, 300}, false}, - {1, nil, false}, - {nil, []int{100}, false}, - {1, t, false}, + {[]string{"a", "b", "c", "d"}, true}, + {[]int{100, 200, 300}, true}, + {[]int{100, 200, 300}, true}, + {[]int{100, 200}, true}, + {[]string{"a", "b"}, true}, + {[]int{100, 200, 300}, true}, + {[]int{100, 200, 300}, true}, + {[]int{100}, true}, + {nil, false}, + {t, false}, } { - results, err := Random(this.count, this.sequence) - if b, ok := this.expect.(bool); ok && !b { + results, err := Shuffle(this.sequence) + if !this.success { if err == nil { t.Errorf("[%d] First didn't return an expected error", i) } } else { resultsv := reflect.ValueOf(results) + sequencev := reflect.ValueOf(this.sequence) + if err != nil { t.Errorf("[%d] failed: %s", i, err) continue } - if resultsv.Len() != this.expect { - t.Errorf("[%d] requested %d random items, got %v but expected %v", - i, this.count, resultsv.Len(), this.expect) + if resultsv.Len() != sequencev.Len() { + t.Errorf("Expected %d items, got %d items", sequencev.Len(), resultsv.Len()) } } } } +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) { for i, this := range []struct { v1 []interface{}