diff --git a/go.sum b/go.sum index 356d0f5..61d5ef4 100644 --- a/go.sum +++ b/go.sum @@ -5,12 +5,18 @@ github.com/fxamacker/cbor/v2 v2.4.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrt github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.28.0 h1:MirSo27VyNi7RJYP3078AA1+Cyzd2GB66qy3aUHvsWY= +github.com/rs/zerolog v1.28.0/go.mod h1:NILgTygv/Uej1ra5XxGf82ZFSLk58MFGAUS2o6usyD0= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/weightedrandom/common.go b/weightedrandom/common.go new file mode 100644 index 0000000..628acaa --- /dev/null +++ b/weightedrandom/common.go @@ -0,0 +1,56 @@ +package weightedrandom + +import ( + "crypto/rand" + "math" + "math/big" +) + +type ordered interface { + int | int8 | int16 | int32 | int64 | + uint | uint8 | uint16 | uint32 | uint64 | + float32 | float64 | + string +} + +// RandomFunc is the Type for random values required for this +type RandomFunc func(int) int + +// AdjustFunc is the function that returns an adjusted value for the weighting +type AdjustFunc func(itemweight int, totalWeight int) int + +// AccessModifier is executed before an item is returned and returns the modified weight of an item. +type AccessModifier func(oldweight int) int + +// RandInt is the standard function for returning random numbers. It returns an random number between 0 and max +func RandInt(max int) int { + out, err := rand.Int(rand.Reader, big.NewInt(int64(max))) + if err != nil { + panic(err) + } + return int(out.Int64()) +} + +// StandardAdjustFunc returns the normal weight for the adjustment +func StandardAdjustFunc(standardweight, _ int) int { + return standardweight +} + +// ReverseWeightAdjustFunc returns the value relative to the total weight +func ReverseWeightAdjustFunc(weight, totalWeight int) int { + return totalWeight / weight +} + +// Addone adds one until the max value is reached, after that it returns only the max value +func Addone(oldweight int) int { + if oldweight != math.MaxInt { + return oldweight + 1 + } + return oldweight +} + +type weightitem[T any] struct { + weight int + adjusted int + value T +} diff --git a/weightedrandom/doc.go b/weightedrandom/doc.go new file mode 100644 index 0000000..2cf56fb --- /dev/null +++ b/weightedrandom/doc.go @@ -0,0 +1,2 @@ +// Package weightedrandom provides multiple weighted random lists +package weightedrandom diff --git a/weightedrandom/new.go b/weightedrandom/new.go new file mode 100644 index 0000000..b793a73 --- /dev/null +++ b/weightedrandom/new.go @@ -0,0 +1,11 @@ +package weightedrandom + +// New returns an new instance of WeightRandom +func New[T any](af AdjustFunc, rf RandomFunc, acf AccessModifier) *WeightRandom[T] { + return &WeightRandom[T]{ + data: []*weightitem[T]{}, + weightFunc: af, + randomFunc: rf, + accessfunc: acf, + } +} diff --git a/weightedrandom/weightedrandom.go b/weightedrandom/weightedrandom.go new file mode 100644 index 0000000..81aac63 --- /dev/null +++ b/weightedrandom/weightedrandom.go @@ -0,0 +1,100 @@ +package weightedrandom + +import "sort" + +var ( + _ sort.Interface = &wflist[int]{} +) + +type wflist[T any] []*weightitem[T] + +func (wfl wflist[T]) Less(i, j int) bool { + return wfl[i].adjusted < wfl[j].adjusted +} + +func (wfl wflist[T]) Len() int { + return len(wfl) +} + +func (wfl wflist[T]) Swap(i, j int) { + wfl[i], wfl[j] = wfl[j], wfl[i] +} + +// WeightRandom is the Type for an weighted random list +type WeightRandom[T any] struct { + data wflist[T] + weightFunc AdjustFunc + randomFunc RandomFunc + sorted bool + totalWeight int + adjustedWeight int + sorti sort.Interface + accessfunc AccessModifier +} + +func (wr *WeightRandom[T]) reweight() { + wr.updatetotal() + for _, item := range wr.data { + item.adjusted = wr.weightFunc(item.weight, wr.totalWeight) + } +} + +func (wr *WeightRandom[T]) sort() { + if wr.sorted { + return + } + if wr.sorti == nil { + wr.sorti = sort.Reverse(&wr.data) + } + wr.reweight() + sort.Stable(wr.sorti) + wr.sorted = true +} + +func (wr *WeightRandom[T]) updatetotal() { + newtotal := 0 + newadjusted := 0 + for _, item := range wr.data { + newtotal += item.weight + + newadjusted += item.adjusted + } + wr.adjustedWeight = newadjusted + wr.totalWeight = newtotal +} + +// AddElement adds an single Element to the list +func (wr *WeightRandom[T]) AddElement(item T, weight int) { + wr.sorted = false + wr.totalWeight += weight + wr.data = append(wr.data, &weightitem[T]{weight: weight, value: item}) +} + +// AddElements adds an list of elements to the list +func (wr *WeightRandom[T]) AddElements(weight int, items ...T) { + for _, item := range items { + wr.AddElement(item, weight) + } + wr.reweight() + wr.sort() +} + +// Get returns an random value of the list of items or the null value of the type in case no element was added +func (wr *WeightRandom[T]) Get() (t T) { + if !wr.sorted { + wr.reweight() + wr.sort() + } + pos := wr.randomFunc(wr.data.Len()) + for _, item := range wr.data { + pos -= item.adjusted + if pos <= 0 { + if wr.accessfunc != nil { + wr.sorted = false + item.weight = wr.accessfunc(item.weight) + } + return item.value + } + } + return +}