diff options
Diffstat (limited to 'internal/util')
| -rw-r--r-- | internal/util/slices.go | 66 | ||||
| -rw-r--r-- | internal/util/slices_test.go | 94 | ||||
| -rw-r--r-- | internal/util/unique.go | 79 | 
3 files changed, 202 insertions, 37 deletions
| diff --git a/internal/util/slices.go b/internal/util/slices.go index 0505229e5..955fe8830 100644 --- a/internal/util/slices.go +++ b/internal/util/slices.go @@ -17,7 +17,9 @@  package util -import "slices" +import ( +	"slices" +)  // Deduplicate deduplicates entries in the given slice.  func Deduplicate[T comparable](in []T) []T { @@ -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") diff --git a/internal/util/slices_test.go b/internal/util/slices_test.go new file mode 100644 index 000000000..c93e489f5 --- /dev/null +++ b/internal/util/slices_test.go @@ -0,0 +1,94 @@ +// GoToSocial +// Copyright (C) GoToSocial Authors admin@gotosocial.org +// 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") +	} +} diff --git a/internal/util/unique.go b/internal/util/unique.go index f0ded1446..bad553d3f 100644 --- a/internal/util/unique.go +++ b/internal/util/unique.go @@ -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  } | 
