Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[chore] add some more slice related utility functions + remove duplicates #3149

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion internal/federation/federatingprotocol.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,9 @@ func (f *Federator) PostInboxRequestBodyHook(ctx context.Context, r *http.Reques

// OtherIRIs will likely contain some
// duplicate entries now, so remove them.
otherIRIs = util.UniqueURIs(otherIRIs)
otherIRIs = util.DeduplicateFunc(otherIRIs,
(*url.URL).String, // serialized URL is 'key()'
)

// Finished, set other IRIs on the context
// so they can be checked for blocks later.
Expand Down
2 changes: 1 addition & 1 deletion internal/gtsmodel/conversation.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ type Conversation struct {

// ConversationOtherAccountsKey creates an OtherAccountsKey from a list of OtherAccountIDs.
func ConversationOtherAccountsKey(otherAccountIDs []string) string {
otherAccountIDs = util.UniqueStrings(otherAccountIDs)
otherAccountIDs = util.Deduplicate(otherAccountIDs)
slices.Sort(otherAccountIDs)
return strings.Join(otherAccountIDs, ",")
}
Expand Down
9 changes: 7 additions & 2 deletions internal/media/processingmedia.go
Original file line number Diff line number Diff line change
Expand Up @@ -218,8 +218,13 @@ func (p *ProcessingMedia) store(ctx context.Context) error {
}

if width > 0 && height > 0 {
// Determine thumbnail dimensions to use.
thumbWidth, thumbHeight := thumbSize(width, height, aspect, result.rotation)
// Determine thumbnail dimens to use.
thumbWidth, thumbHeight := thumbSize(
width,
height,
aspect,
result.rotation,
)
p.media.FileMeta.Small.Width = thumbWidth
p.media.FileMeta.Small.Height = thumbHeight
p.media.FileMeta.Small.Size = (thumbWidth * thumbHeight)
Expand Down
1 change: 1 addition & 0 deletions internal/typeutils/internaltofrontend.go
Original file line number Diff line number Diff line change
Expand Up @@ -854,6 +854,7 @@ func (c *Converter) statusToAPIFilterResults(
for _, mention := range s.Mentions {
otherAccounts = append(otherAccounts, mention.TargetAccount)
}

// If there are no other accounts, skip this check.
if len(otherAccounts) > 0 {
// Start by assuming that they're all invisible or muted.
Expand Down
66 changes: 64 additions & 2 deletions internal/util/slices.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@

package util

import "slices"
import (
"slices"
)

// Deduplicate deduplicates entries in the given slice.
func Deduplicate[T comparable](in []T) []T {
Expand Down Expand Up @@ -68,9 +70,69 @@ func DeduplicateFunc[T any, C comparable](in []T, key func(v T) C) []T {
return deduped
}

// Gather will collect the values of type V from input type []T,
// passing each item to 'get' and appending V to the return slice.
func Gather[T, V any](out []V, in []T, get func(T) V) []V {
if get == nil {
panic("nil func")
}

// Starting write index
// in the resliced / re
// alloc'd output slice.
start := len(out)

// Total required slice len.
total := start + len(in)

if total > cap(out) {
// Reallocate output with
// capacity for total len.
out2 := make([]V, len(out), total)
copy(out2, out)
out = out2
}

// Reslice with capacity
// up to total required.
out = out[:total]

// Gather vs from 'in'.
for i, v := range in {
j := start + i
out[j] = get(v)
}

return out
}

// GatherIf is functionally similar to Gather(), but only when return bool is true.
// If you don't need to check the boolean, Gather() will be very slightly faster.
func GatherIf[T, V any](out []V, in []T, get func(T) (V, bool)) []V {
if get == nil {
panic("nil func")
}

if cap(out)-len(out) < len(in) {
// Reallocate output with capacity for 'in'.
out2 := make([]V, len(out), cap(out)+len(in))
copy(out2, out)
out = out2
}

// Gather vs from 'in'.
for _, v := range in {
if v, ok := get(v); ok {
out = append(out, v)
}
}

return out
}

// Collate will collect the values of type K from input type []T,
// passing each item to 'get' and deduplicating the end result.
// Compared to Deduplicate() this returns []K, NOT input type []T.
// This is equivalent to calling Gather() followed by Deduplicate().
func Collate[T any, K comparable](in []T, get func(T) K) []K {
if get == nil {
panic("nil func")
Expand Down
94 changes: 94 additions & 0 deletions internal/util/slices_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// GoToSocial
// Copyright (C) GoToSocial Authors [email protected]
// SPDX-License-Identifier: AGPL-3.0-or-later
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

package util_test

import (
"net/url"
"slices"
"testing"

"github.com/superseriousbusiness/gotosocial/internal/util"
)

var (
testURLSlice = []*url.URL{}
)

func TestGather(t *testing.T) {
out := util.Gather(nil, []*url.URL{
{Scheme: "https", Host: "google.com", Path: "/some-search"},
{Scheme: "http", Host: "example.com", Path: "/robots.txt"},
}, (*url.URL).String)
if !slices.Equal(out, []string{
"https://google.com/some-search",
"http://example.com/robots.txt",
}) {
t.Fatal("unexpected gather output")
}

out = util.Gather([]string{
"starting input string",
"another starting input",
}, []*url.URL{
{Scheme: "https", Host: "google.com", Path: "/some-search"},
{Scheme: "http", Host: "example.com", Path: "/robots.txt"},
}, (*url.URL).String)
if !slices.Equal(out, []string{
"starting input string",
"another starting input",
"https://google.com/some-search",
"http://example.com/robots.txt",
}) {
t.Fatal("unexpected gather output")
}
}

func TestGatherIf(t *testing.T) {
out := util.GatherIf(nil, []string{
"hello world",
"not hello world",
"hello world",
}, func(s string) (string, bool) {
return s, s == "hello world"
})
if !slices.Equal(out, []string{
"hello world",
"hello world",
}) {
t.Fatal("unexpected gatherif output")
}

out = util.GatherIf([]string{
"starting input string",
"another starting input",
}, []string{
"hello world",
"not hello world",
"hello world",
}, func(s string) (string, bool) {
return s, s == "hello world"
})
if !slices.Equal(out, []string{
"starting input string",
"another starting input",
"hello world",
"hello world",
}) {
t.Fatal("unexpected gatherif output")
}
}
79 changes: 44 additions & 35 deletions internal/util/unique.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,48 +17,57 @@

package util

import "net/url"
// Set represents a hashmap of only keys,
// useful for deduplication / key checking.
type Set[T comparable] map[T]struct{}

// UniqueStrings returns a deduplicated version of the given
// slice of strings, without changing the order of the entries.
func UniqueStrings(strings []string) []string {
var (
l = len(strings)
keys = make(map[string]any, l) // Use map to dedupe items.
unique = make([]string, 0, l) // Return slice.
)

for _, str := range strings {
// Check if already set as a key in the map;
// if not, add to return slice + mark key as set.
if _, set := keys[str]; !set {
keys[str] = nil // Value doesn't matter.
unique = append(unique, str)
}
// ToSet creates a Set[T] from given values,
// noting that this does not maintain any order.
func ToSet[T comparable](in []T) Set[T] {
set := make(Set[T], len(in))
for _, v := range in {
set[v] = struct{}{}
}

return unique
return set
}

// UniqueURIs returns a deduplicated version of the given
// slice of URIs, without changing the order of the entries.
func UniqueURIs(uris []*url.URL) []*url.URL {
var (
l = len(uris)
keys = make(map[string]any, l) // Use map to dedupe items.
unique = make([]*url.URL, 0, l) // Return slice.
)
// FromSet extracts the values from set to slice,
// noting that this does not maintain any order.
func FromSet[T comparable](in Set[T]) []T {
out := make([]T, len(in))
var i int
for v := range in {
out[i] = v
i++
}
return out
}

for _, uri := range uris {
uriStr := uri.String()
// In returns input slice filtered to
// only contain those in receiving set.
func (s Set[T]) In(vs []T) []T {
out := make([]T, 0, len(vs))
for _, v := range vs {
if _, ok := s[v]; ok {
out = append(out, v)
}
}
return out
}

// Check if already set as a key in the map;
// if not, add to return slice + mark key as set.
if _, set := keys[uriStr]; !set {
keys[uriStr] = nil // Value doesn't matter.
unique = append(unique, uri)
// NotIn is the functional inverse of In().
func (s Set[T]) NotIn(vs []T) []T {
out := make([]T, 0, len(vs))
for _, v := range vs {
if _, ok := s[v]; !ok {
out = append(out, v)
}
}
return out
}

return unique
// Has returns if value is in Set.
func (s Set[T]) Has(v T) bool {
_, ok := s[v]
return ok
}