ref: 302a6ac701bc2599d4ca98c7d263d364505a3980
parent: b1f2b433bc4ec520df81c04493e8e1634293c8e7
author: Antti Järvinen <[email protected]>
date: Sun Dec 6 07:28:03 EST 2015
Add Random function to template functions Adds Random function to pick N random items from sequence.
--- a/tpl/template_funcs.go
+++ b/tpl/template_funcs.go
@@ -20,6 +20,7 @@
"fmt"
"html"
"html/template"
+ "math/rand"
"os"
"reflect"
"sort"
@@ -495,6 +496,53 @@
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) {
+
+ if count == nil || 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 {
+ return nil, errors.New("can't iterate over a nil value")
+ }
+
+ switch seqv.Kind() {
+ case reflect.Array, reflect.Slice, reflect.String:
+ // okay
+ default:
+ 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())
+
+ rand.Seed(time.Now().UTC().UnixNano())
+ randomIndices := rand.Perm(seqv.Len())
+
+ for index, value := range randomIndices {
+ suffled.Index(value).Set(seqv.Index(index))
+ }
+
+ return suffled.Slice(0, countv).Interface(), nil
+}
+
var (
zero reflect.Value
errorType = reflect.TypeOf((*error)(nil)).Elem()
@@ -1453,6 +1501,7 @@
"first": First,
"last": Last,
"after": After,
+ "random": Random,
"where": Where,
"delimit": Delimit,
"sort": Sort,
--- a/tpl/template_funcs_test.go
+++ b/tpl/template_funcs_test.go
@@ -341,6 +341,43 @@
}
}
+func TestRandom(t *testing.T) {
+ for i, this := range []struct {
+ count interface{}
+ sequence interface{}
+ expect interface{}
+ }{
+ {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},
+ } {
+ results, err := Random(this.count, this.sequence)
+ if b, ok := this.expect.(bool); ok && !b {
+ if err == nil {
+ t.Errorf("[%d] First didn't return an expected error", i)
+ }
+ } else {
+ resultsv := reflect.ValueOf(results)
+ 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)
+ }
+ }
+ }
+}
+
func TestDictionary(t *testing.T) {
for i, this := range []struct {
v1 []interface{}