Use slices package from the stdlib when possible (#5360)

* Use slices from the stdlib when possible

* Add some unit tests

* More small tweaks + add benchmark func
This commit is contained in:
its-josh4
2024-10-28 17:26:23 -07:00
committed by GitHub
parent 093de3bce2
commit c6bcdd89be
38 changed files with 200 additions and 110 deletions

View File

@@ -3,6 +3,7 @@ package gallery
import (
"context"
"fmt"
"slices"
"strings"
"github.com/stashapp/stash/pkg/models"
@@ -153,7 +154,7 @@ func (i *Importer) populatePerformers(ctx context.Context) error {
}
missingPerformers := sliceutil.Filter(names, func(name string) bool {
return !sliceutil.Contains(pluckedNames, name)
return !slices.Contains(pluckedNames, name)
})
if len(missingPerformers) > 0 {
@@ -212,7 +213,7 @@ func (i *Importer) populateTags(ctx context.Context) error {
}
missingTags := sliceutil.Filter(names, func(name string) bool {
return !sliceutil.Contains(pluckedNames, name)
return !slices.Contains(pluckedNames, name)
})
if len(missingTags) > 0 {

View File

@@ -3,6 +3,7 @@ package group
import (
"context"
"fmt"
"slices"
"strings"
"github.com/stashapp/stash/pkg/models"
@@ -96,7 +97,7 @@ func importTags(ctx context.Context, tagWriter models.TagFinderCreator, names []
}
missingTags := sliceutil.Filter(names, func(name string) bool {
return !sliceutil.Contains(pluckedNames, name)
return !slices.Contains(pluckedNames, name)
})
if len(missingTags) > 0 {

View File

@@ -2,6 +2,7 @@ package group
import (
"context"
"slices"
"strings"
"github.com/stashapp/stash/pkg/models"
@@ -105,7 +106,7 @@ func (s *Service) validateUpdateGroupHierarchy(ctx context.Context, existing *mo
subIDs := idsFromGroupDescriptions(effectiveSubGroups)
// ensure we haven't set the group as a subgroup of itself
if sliceutil.Contains(containingIDs, existing.ID) || sliceutil.Contains(subIDs, existing.ID) {
if slices.Contains(containingIDs, existing.ID) || slices.Contains(subIDs, existing.ID) {
return ErrHierarchyLoop
}

View File

@@ -3,6 +3,7 @@ package image
import (
"context"
"fmt"
"slices"
"strings"
"github.com/stashapp/stash/pkg/models"
@@ -239,7 +240,7 @@ func (i *Importer) populatePerformers(ctx context.Context) error {
}
missingPerformers := sliceutil.Filter(names, func(name string) bool {
return !sliceutil.Contains(pluckedNames, name)
return !slices.Contains(pluckedNames, name)
})
if len(missingPerformers) > 0 {
@@ -375,7 +376,7 @@ func importTags(ctx context.Context, tagWriter models.TagFinderCreator, names []
}
missingTags := sliceutil.Filter(names, func(name string) bool {
return !sliceutil.Contains(pluckedNames, name)
return !slices.Contains(pluckedNames, name)
})
if len(missingTags) > 0 {

View File

@@ -6,13 +6,13 @@ import (
"fmt"
"os"
"path/filepath"
"slices"
"github.com/stashapp/stash/pkg/logger"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/models/paths"
"github.com/stashapp/stash/pkg/plugin"
"github.com/stashapp/stash/pkg/plugin/hook"
"github.com/stashapp/stash/pkg/sliceutil"
"github.com/stashapp/stash/pkg/txn"
)
@@ -356,7 +356,7 @@ func (h *ScanHandler) getGalleryToAssociate(ctx context.Context, newImage *model
return nil, err
}
if g != nil && !sliceutil.Contains(newImage.GalleryIDs.List(), g.ID) {
if g != nil && !slices.Contains(newImage.GalleryIDs.List(), g.ID) {
return g, nil
}

View File

@@ -3,6 +3,7 @@ package performer
import (
"context"
"fmt"
"slices"
"strconv"
"strings"
@@ -75,7 +76,7 @@ func importTags(ctx context.Context, tagWriter models.TagFinderCreator, names []
}
missingTags := sliceutil.Filter(names, func(name string) bool {
return !sliceutil.Contains(pluckedNames, name)
return !slices.Contains(pluckedNames, name)
})
if len(missingTags) > 0 {

View File

@@ -14,6 +14,7 @@ import (
"net/http"
"os"
"path/filepath"
"slices"
"strconv"
"strings"
@@ -23,7 +24,6 @@ import (
"github.com/stashapp/stash/pkg/plugin/common"
"github.com/stashapp/stash/pkg/plugin/hook"
"github.com/stashapp/stash/pkg/session"
"github.com/stashapp/stash/pkg/sliceutil"
"github.com/stashapp/stash/pkg/txn"
"github.com/stashapp/stash/pkg/utils"
)
@@ -168,7 +168,7 @@ func (c Cache) enabledPlugins() []Config {
var ret []Config
for _, p := range c.plugins {
disabled := sliceutil.Contains(disabledPlugins, p.id)
disabled := slices.Contains(disabledPlugins, p.id)
if !disabled {
ret = append(ret, p)
@@ -181,7 +181,7 @@ func (c Cache) enabledPlugins() []Config {
func (c Cache) pluginDisabled(id string) bool {
disabledPlugins := c.config.GetDisabledPlugins()
return sliceutil.Contains(disabledPlugins, id)
return slices.Contains(disabledPlugins, id)
}
// ListPlugins returns plugin details for all of the loaded plugins.
@@ -192,7 +192,7 @@ func (c Cache) ListPlugins() []*Plugin {
for _, s := range c.plugins {
p := s.toPlugin()
disabled := sliceutil.Contains(disabledPlugins, p.ID)
disabled := slices.Contains(disabledPlugins, p.ID)
p.Enabled = !disabled
ret = append(ret, p)
@@ -209,7 +209,7 @@ func (c Cache) GetPlugin(id string) *Plugin {
if plugin != nil {
p := plugin.toPlugin()
disabled := sliceutil.Contains(disabledPlugins, p.ID)
disabled := slices.Contains(disabledPlugins, p.ID)
p.Enabled = !disabled
return p
}

View File

@@ -3,6 +3,7 @@ package scene
import (
"context"
"fmt"
"slices"
"strings"
"time"
@@ -290,7 +291,7 @@ func (i *Importer) populatePerformers(ctx context.Context) error {
}
missingPerformers := sliceutil.Filter(names, func(name string) bool {
return !sliceutil.Contains(pluckedNames, name)
return !slices.Contains(pluckedNames, name)
})
if len(missingPerformers) > 0 {
@@ -517,7 +518,7 @@ func importTags(ctx context.Context, tagWriter models.TagFinderCreator, names []
}
missingTags := sliceutil.Filter(names, func(name string) bool {
return !sliceutil.Contains(pluckedNames, name)
return !slices.Contains(pluckedNames, name)
})
if len(missingTags) > 0 {

View File

@@ -6,6 +6,7 @@ import (
"fmt"
"os"
"path/filepath"
"slices"
"time"
"github.com/stashapp/stash/pkg/fsutil"
@@ -28,7 +29,7 @@ func (s *Service) Merge(ctx context.Context, sourceIDs []int, destinationID int,
sourceIDs = sliceutil.AppendUniques(nil, sourceIDs)
// ensure destination is not in source list
if sliceutil.Contains(sourceIDs, destinationID) {
if slices.Contains(sourceIDs, destinationID) {
return errors.New("destination scene cannot be in source list")
}

View File

@@ -1,26 +1,14 @@
// Package sliceutil provides utilities for working with slices.
package sliceutil
// Index returns the first index of the provided value in the provided
// slice. It returns -1 if it is not found.
func Index[T comparable](vs []T, t T) int {
for i, v := range vs {
if v == t {
return i
}
}
return -1
}
// Contains returns whether the vs slice contains t.
func Contains[T comparable](vs []T, t T) bool {
return Index(vs, t) >= 0
}
import (
"slices"
)
// AppendUnique appends toAdd to the vs slice if toAdd does not already
// exist in the slice. It returns the new or unchanged slice.
func AppendUnique[T comparable](vs []T, toAdd T) []T {
if Contains(vs, toAdd) {
if slices.Contains(vs, toAdd) {
return vs
}
@@ -31,6 +19,13 @@ func AppendUnique[T comparable](vs []T, toAdd T) []T {
// appends values that do not already exist in the slice.
// It returns the new or unchanged slice.
func AppendUniques[T comparable](vs []T, toAdd []T) []T {
if len(toAdd) == 0 {
return vs
}
// Extend the slice's capacity to avoid multiple re-allocations even in the worst case
vs = slices.Grow(vs, len(toAdd))
for _, v := range toAdd {
vs = AppendUnique(vs, v)
}
@@ -41,9 +36,9 @@ func AppendUniques[T comparable](vs []T, toAdd []T) []T {
// Exclude returns a copy of the vs slice, excluding all values
// that are also present in the toExclude slice.
func Exclude[T comparable](vs []T, toExclude []T) []T {
var ret []T
ret := make([]T, 0, len(vs))
for _, v := range vs {
if !Contains(toExclude, v) {
if !slices.Contains(toExclude, v) {
ret = append(ret, v)
}
}
@@ -53,8 +48,8 @@ func Exclude[T comparable](vs []T, toExclude []T) []T {
// Unique returns a copy of the vs slice, with non-unique values removed.
func Unique[T comparable](vs []T) []T {
distinctValues := make(map[T]struct{})
var ret []T
distinctValues := make(map[T]struct{}, len(vs))
ret := make([]T, 0, len(vs))
for _, v := range vs {
if _, exists := distinctValues[v]; !exists {
distinctValues[v] = struct{}{}
@@ -66,7 +61,7 @@ func Unique[T comparable](vs []T) []T {
// Delete returns a copy of the vs slice with toDel values removed.
func Delete[T comparable](vs []T, toDel T) []T {
var ret []T
ret := make([]T, 0, len(vs))
for _, v := range vs {
if v != toDel {
ret = append(ret, v)
@@ -79,7 +74,7 @@ func Delete[T comparable](vs []T, toDel T) []T {
func Intersect[T comparable](a []T, b []T) []T {
var ret []T
for _, v := range a {
if Contains(b, v) {
if slices.Contains(b, v) {
ret = append(ret, v)
}
}
@@ -91,13 +86,13 @@ func Intersect[T comparable](a []T, b []T) []T {
func NotIntersect[T comparable](a []T, b []T) []T {
var ret []T
for _, v := range a {
if !Contains(b, v) {
if !slices.Contains(b, v) {
ret = append(ret, v)
}
}
for _, v := range b {
if !Contains(a, v) {
if !slices.Contains(a, v) {
ret = append(ret, v)
}
}
@@ -166,8 +161,9 @@ func PtrsToValues[T any](vs []*T) []T {
func ValuesToPtrs[T any](vs []T) []*T {
ret := make([]*T, len(vs))
for i, v := range vs {
vv := v
ret[i] = &vv
// We can do this safely because go.mod indicates Go 1.22
// See: https://go.dev/blog/loopvar-preview
ret[i] = &v
}
return ret
}

View File

@@ -1,6 +1,7 @@
package sliceutil
import (
"reflect"
"testing"
"github.com/stretchr/testify/assert"
@@ -66,3 +67,85 @@ func TestSliceSame(t *testing.T) {
})
}
}
func TestAppendUniques(t *testing.T) {
type args struct {
vs []int
toAdd []int
}
tests := []struct {
name string
args args
want []int
}{
{
name: "append to empty slice",
args: args{
vs: []int{},
toAdd: []int{1, 2, 3},
},
want: []int{1, 2, 3},
},
{
name: "append all unique values",
args: args{
vs: []int{1, 2, 3},
toAdd: []int{4, 5, 6},
},
want: []int{1, 2, 3, 4, 5, 6},
},
{
name: "append with some duplicates",
args: args{
vs: []int{1, 2, 3},
toAdd: []int{3, 4, 5},
},
want: []int{1, 2, 3, 4, 5},
},
{
name: "append all duplicates",
args: args{
vs: []int{1, 2, 3},
toAdd: []int{1, 2, 3},
},
want: []int{1, 2, 3},
},
{
name: "append to nil slice",
args: args{
vs: nil,
toAdd: []int{1, 2, 3},
},
want: []int{1, 2, 3},
},
{
name: "append empty slice",
args: args{
vs: []int{1, 2, 3},
toAdd: []int{},
},
want: []int{1, 2, 3},
},
{
name: "append nil to slice",
args: args{
vs: []int{1, 2, 3},
toAdd: nil,
},
want: []int{1, 2, 3},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := AppendUniques(tt.args.vs, tt.args.toAdd); !reflect.DeepEqual(got, tt.want) {
t.Errorf("AppendUniques() = %v, want %v", got, tt.want)
}
})
}
}
func BenchmarkAppendUniques(b *testing.B) {
for i := 0; i < b.N; i++ {
AppendUniques([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, []int{3, 4, 4, 11, 12, 13, 14, 15, 16, 17, 18})
}
}

View File

@@ -33,8 +33,8 @@ func FromString(s string, sep string) []string {
// Unique returns a slice containing only unique values from the provided slice.
// The comparison is case-insensitive.
func UniqueFold(s []string) []string {
seen := make(map[string]struct{})
var ret []string
seen := make(map[string]struct{}, len(s))
ret := make([]string, 0, len(s))
for _, v := range s {
if _, exists := seen[strings.ToLower(v)]; exists {
continue

View File

@@ -6,12 +6,12 @@ import (
"errors"
"fmt"
"path/filepath"
"slices"
"github.com/doug-martin/goqu/v9"
"github.com/doug-martin/goqu/v9/exp"
"github.com/jmoiron/sqlx"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/sliceutil"
"gopkg.in/guregu/null.v4"
"gopkg.in/guregu/null.v4/zero"
)
@@ -412,7 +412,7 @@ func (qb *GalleryStore) FindMany(ctx context.Context, ids []int) ([]*models.Gall
}
for _, s := range unsorted {
i := sliceutil.Index(ids, s.ID)
i := slices.Index(ids, s.ID)
galleries[i] = s
}

View File

@@ -5,13 +5,13 @@ import (
"database/sql"
"errors"
"fmt"
"slices"
"github.com/doug-martin/goqu/v9"
"github.com/doug-martin/goqu/v9/exp"
"github.com/jmoiron/sqlx"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/sliceutil"
)
const (
@@ -162,7 +162,7 @@ func (qb *GalleryChapterStore) FindMany(ctx context.Context, ids []int) ([]*mode
}
for _, s := range unsorted {
i := sliceutil.Index(ids, s.ID)
i := slices.Index(ids, s.ID)
ret[i] = s
}

View File

@@ -5,6 +5,7 @@ import (
"database/sql"
"errors"
"fmt"
"slices"
"github.com/doug-martin/goqu/v9"
"github.com/doug-martin/goqu/v9/exp"
@@ -13,7 +14,6 @@ import (
"gopkg.in/guregu/null.v4/zero"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/sliceutil"
)
const (
@@ -295,7 +295,7 @@ func (qb *GroupStore) FindMany(ctx context.Context, ids []int) ([]*models.Group,
}
for _, s := range unsorted {
i := sliceutil.Index(ids, s.ID)
i := slices.Index(ids, s.ID)
ret[i] = s
}

View File

@@ -6,6 +6,7 @@ package sqlite_test
import (
"context"
"fmt"
"slices"
"strconv"
"strings"
"testing"
@@ -1605,7 +1606,7 @@ func TestGroupReorderSubGroups(t *testing.T) {
// get ids of groups
newIDs := sliceutil.Map(gd, func(gd models.GroupIDDescription) int { return gd.GroupID })
newIdxs := sliceutil.Map(newIDs, func(id int) int { return sliceutil.Index(idxToId, id) })
newIdxs := sliceutil.Map(newIDs, func(id int) int { return slices.Index(idxToId, id) })
assert.ElementsMatch(t, tt.expectedIdxs, newIdxs)
})
@@ -1733,7 +1734,7 @@ func TestGroupAddSubGroups(t *testing.T) {
// get ids of groups
newIDs := sliceutil.Map(gd, func(gd models.GroupIDDescription) int { return gd.GroupID })
newIdxs := sliceutil.Map(newIDs, func(id int) int { return sliceutil.Index(idxToId, id) })
newIdxs := sliceutil.Map(newIDs, func(id int) int { return slices.Index(idxToId, id) })
assert.ElementsMatch(t, tt.expectedIdxs, newIdxs)
})
@@ -1828,7 +1829,7 @@ func TestGroupRemoveSubGroups(t *testing.T) {
// get ids of groups
newIDs := sliceutil.Map(gd, func(gd models.GroupIDDescription) int { return gd.GroupID })
newIdxs := sliceutil.Map(newIDs, func(id int) int { return sliceutil.Index(idxToId, id) })
newIdxs := sliceutil.Map(newIDs, func(id int) int { return slices.Index(idxToId, id) })
assert.ElementsMatch(t, tt.expectedIdxs, newIdxs)
})
@@ -1883,7 +1884,7 @@ func TestGroupFindSubGroupIDs(t *testing.T) {
}
// get ids of groups
foundIdxs := sliceutil.Map(found, func(id int) int { return sliceutil.Index(groupIDs, id) })
foundIdxs := sliceutil.Map(found, func(id int) int { return slices.Index(groupIDs, id) })
assert.ElementsMatch(t, tt.expectedIdxs, foundIdxs)
})

View File

@@ -6,6 +6,7 @@ import (
"errors"
"fmt"
"path/filepath"
"slices"
"github.com/jmoiron/sqlx"
"github.com/stashapp/stash/pkg/models"
@@ -398,7 +399,7 @@ func (qb *ImageStore) FindMany(ctx context.Context, ids []int) ([]*models.Image,
}
for _, s := range unsorted {
i := sliceutil.Index(ids, s.ID)
i := slices.Index(ids, s.ID)
images[i] = s
}

View File

@@ -5,12 +5,12 @@ import (
"database/sql"
"errors"
"fmt"
"slices"
"github.com/doug-martin/goqu/v9"
"github.com/doug-martin/goqu/v9/exp"
"github.com/jmoiron/sqlx"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/sliceutil"
"github.com/stashapp/stash/pkg/utils"
"gopkg.in/guregu/null.v4"
"gopkg.in/guregu/null.v4/zero"
@@ -398,7 +398,7 @@ func (qb *PerformerStore) FindMany(ctx context.Context, ids []int) ([]*models.Pe
}
for _, s := range unsorted {
i := sliceutil.Index(ids, s.ID)
i := slices.Index(ids, s.ID)
ret[i] = s
}

View File

@@ -6,6 +6,7 @@ import (
"encoding/json"
"errors"
"fmt"
"slices"
"github.com/doug-martin/goqu/v9"
"github.com/doug-martin/goqu/v9/exp"
@@ -13,7 +14,6 @@ import (
"github.com/stashapp/stash/pkg/logger"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/sliceutil"
)
const (
@@ -165,7 +165,7 @@ func (qb *SavedFilterStore) FindMany(ctx context.Context, ids []int, ignoreNotFo
}
for _, s := range unsorted {
i := sliceutil.Index(ids, s.ID)
i := slices.Index(ids, s.ID)
ret[i] = s
}

View File

@@ -6,6 +6,7 @@ import (
"errors"
"fmt"
"path/filepath"
"slices"
"sort"
"strconv"
"strings"
@@ -504,7 +505,7 @@ func (qb *SceneStore) FindMany(ctx context.Context, ids []int) ([]*models.Scene,
}
for _, s := range unsorted {
i := sliceutil.Index(ids, s.ID)
i := slices.Index(ids, s.ID)
scenes[i] = s
}

View File

@@ -5,13 +5,13 @@ import (
"database/sql"
"errors"
"fmt"
"slices"
"github.com/doug-martin/goqu/v9"
"github.com/doug-martin/goqu/v9/exp"
"github.com/jmoiron/sqlx"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/sliceutil"
)
const sceneMarkerTable = "scene_markers"
@@ -188,7 +188,7 @@ func (qb *SceneMarkerStore) FindMany(ctx context.Context, ids []int) ([]*models.
}
for _, s := range unsorted {
i := sliceutil.Index(ids, s.ID)
i := slices.Index(ids, s.ID)
ret[i] = s
}

View File

@@ -5,11 +5,11 @@ package sqlite_test
import (
"context"
"slices"
"strconv"
"testing"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/sliceutil"
"github.com/stashapp/stash/pkg/sliceutil/stringslice"
"github.com/stretchr/testify/assert"
)
@@ -133,7 +133,7 @@ func verifyIDs(t *testing.T, modifier models.CriterionModifier, values []int, re
case models.CriterionModifierNotEquals:
foundAll := true
for _, v := range values {
if !sliceutil.Contains(results, v) {
if !slices.Contains(results, v) {
foundAll = false
break
}

View File

@@ -10,13 +10,13 @@ import (
"fmt"
"os"
"path/filepath"
"slices"
"strconv"
"testing"
"time"
"github.com/stashapp/stash/internal/manager/config"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/sliceutil"
"github.com/stashapp/stash/pkg/sqlite"
"github.com/stashapp/stash/pkg/txn"
@@ -1585,7 +1585,7 @@ func getTagMarkerCount(id int) int {
count := 0
idx := indexFromID(tagIDs, id)
for _, s := range markerSpecs {
if s.primaryTagIdx == idx || sliceutil.Contains(s.tagIdxs, idx) {
if s.primaryTagIdx == idx || slices.Contains(s.tagIdxs, idx) {
count++
}
}

View File

@@ -5,6 +5,7 @@ import (
"database/sql"
"errors"
"fmt"
"slices"
"github.com/doug-martin/goqu/v9"
"github.com/doug-martin/goqu/v9/exp"
@@ -13,7 +14,6 @@ import (
"gopkg.in/guregu/null.v4/zero"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/sliceutil"
"github.com/stashapp/stash/pkg/studio"
)
@@ -305,7 +305,7 @@ func (qb *StudioStore) FindMany(ctx context.Context, ids []int) ([]*models.Studi
}
for _, s := range unsorted {
i := sliceutil.Index(ids, s.ID)
i := slices.Index(ids, s.ID)
ret[i] = s
}

View File

@@ -5,6 +5,7 @@ import (
"database/sql"
"errors"
"fmt"
"slices"
"strings"
"github.com/doug-martin/goqu/v9"
@@ -14,7 +15,6 @@ import (
"gopkg.in/guregu/null.v4/zero"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/sliceutil"
)
const (
@@ -312,7 +312,7 @@ func (qb *TagStore) FindMany(ctx context.Context, ids []int) ([]*models.Tag, err
}
for _, s := range unsorted {
i := sliceutil.Index(ids, s.ID)
i := slices.Index(ids, s.ID)
ret[i] = s
}

View File

@@ -4,6 +4,7 @@ import (
"context"
"errors"
"fmt"
"slices"
"strings"
"github.com/stashapp/stash/pkg/models"
@@ -80,7 +81,7 @@ func importTags(ctx context.Context, tagWriter models.TagFinderCreator, names []
}
missingTags := sliceutil.Filter(names, func(name string) bool {
return !sliceutil.Contains(pluckedNames, name)
return !slices.Contains(pluckedNames, name)
})
if len(missingTags) > 0 {