libn/i/setmatrix: make generic and constructorless
Allow SetMatrix to be used as a value type with a ready-to-use zero value. SetMatrix values are already non-copyable by virtue of having a mutex field so there is no harm in allowing non-pointer values to be used as local variables or struct fields. Any attempts to pass around by-value copies, e.g. as function arguments, will be flagged by go vet. Signed-off-by: Cory Snider <csnider@mirantis.com>
This commit is contained in:
parent
0d761d19b9
commit
9e3a6ccf69
24 changed files with 1137 additions and 807 deletions
|
@ -94,7 +94,7 @@ type Controller struct {
|
|||
extKeyListener net.Listener
|
||||
watchCh chan *Endpoint
|
||||
unWatchCh chan *Endpoint
|
||||
svcRecords map[string]svcInfo
|
||||
svcRecords map[string]*svcInfo
|
||||
nmap map[string]*netWatch
|
||||
serviceBindings map[serviceKey]*service
|
||||
defOsSbox osl.Sandbox
|
||||
|
@ -120,7 +120,7 @@ func New(cfgOptions ...config.Option) (*Controller, error) {
|
|||
id: stringid.GenerateRandomID(),
|
||||
cfg: config.New(cfgOptions...),
|
||||
sandboxes: sandboxTable{},
|
||||
svcRecords: make(map[string]svcInfo),
|
||||
svcRecords: make(map[string]*svcInfo),
|
||||
serviceBindings: make(map[serviceKey]*service),
|
||||
agentInitDone: make(chan struct{}),
|
||||
networkLocker: locker.New(),
|
||||
|
|
|
@ -60,8 +60,8 @@ func (p *peerEntryDB) UnMarshalDB() peerEntry {
|
|||
}
|
||||
|
||||
type peerMap struct {
|
||||
// set of peerEntry, note they have to be objects and not pointers to maintain the proper equality checks
|
||||
mp *setmatrix.SetMatrix
|
||||
// set of peerEntry, note the values have to be objects and not pointers to maintain the proper equality checks
|
||||
mp setmatrix.SetMatrix[peerEntryDB]
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
|
@ -122,7 +122,7 @@ func (d *driver) peerDbNetworkWalk(nid string, f func(*peerKey, *peerEntry) bool
|
|||
for _, pKeyStr := range pMap.mp.Keys() {
|
||||
entryDBList, ok := pMap.mp.Get(pKeyStr)
|
||||
if ok {
|
||||
peerEntryDB := entryDBList[0].(peerEntryDB)
|
||||
peerEntryDB := entryDBList[0]
|
||||
mp[pKeyStr] = peerEntryDB.UnMarshalDB()
|
||||
}
|
||||
}
|
||||
|
@ -170,11 +170,8 @@ func (d *driver) peerDbAdd(nid, eid string, peerIP net.IP, peerIPMask net.IPMask
|
|||
d.peerDb.Lock()
|
||||
pMap, ok := d.peerDb.mp[nid]
|
||||
if !ok {
|
||||
d.peerDb.mp[nid] = &peerMap{
|
||||
mp: setmatrix.NewSetMatrix(),
|
||||
}
|
||||
|
||||
pMap = d.peerDb.mp[nid]
|
||||
pMap = &peerMap{}
|
||||
d.peerDb.mp[nid] = pMap
|
||||
}
|
||||
d.peerDb.Unlock()
|
||||
|
||||
|
|
|
@ -3,29 +3,21 @@ package setmatrix
|
|||
import (
|
||||
"sync"
|
||||
|
||||
mapset "github.com/deckarep/golang-set"
|
||||
mapset "github.com/deckarep/golang-set/v2"
|
||||
)
|
||||
|
||||
// SetMatrix is a map of Sets.
|
||||
type SetMatrix struct {
|
||||
matrix map[string]mapset.Set
|
||||
// The zero value is an empty set matrix ready to use.
|
||||
//
|
||||
// SetMatrix values are safe for concurrent use.
|
||||
type SetMatrix[T comparable] struct {
|
||||
matrix map[string]mapset.Set[T]
|
||||
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
// NewSetMatrix creates a new set matrix object.
|
||||
func NewSetMatrix() *SetMatrix {
|
||||
s := &SetMatrix{}
|
||||
s.init()
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *SetMatrix) init() {
|
||||
s.matrix = make(map[string]mapset.Set)
|
||||
}
|
||||
|
||||
// Get returns the members of the set for a specific key as a slice.
|
||||
func (s *SetMatrix) Get(key string) ([]interface{}, bool) {
|
||||
func (s *SetMatrix[T]) Get(key string) ([]T, bool) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
set, ok := s.matrix[key]
|
||||
|
@ -36,7 +28,7 @@ func (s *SetMatrix) Get(key string) ([]interface{}, bool) {
|
|||
}
|
||||
|
||||
// Contains is used to verify if an element is in a set for a specific key.
|
||||
func (s *SetMatrix) Contains(key string, value interface{}) (containsElement, setExists bool) {
|
||||
func (s *SetMatrix[T]) Contains(key string, value T) (containsElement, setExists bool) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
set, ok := s.matrix[key]
|
||||
|
@ -48,13 +40,15 @@ func (s *SetMatrix) Contains(key string, value interface{}) (containsElement, se
|
|||
|
||||
// Insert inserts the value in the set of a key and returns whether the value is
|
||||
// inserted (was not already in the set) and the number of elements in the set.
|
||||
func (s *SetMatrix) Insert(key string, value interface{}) (insetrted bool, cardinality int) {
|
||||
func (s *SetMatrix[T]) Insert(key string, value T) (inserted bool, cardinality int) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
set, ok := s.matrix[key]
|
||||
if !ok {
|
||||
s.matrix[key] = mapset.NewSet()
|
||||
s.matrix[key].Add(value)
|
||||
if s.matrix == nil {
|
||||
s.matrix = make(map[string]mapset.Set[T])
|
||||
}
|
||||
s.matrix[key] = mapset.NewThreadUnsafeSet(value)
|
||||
return true, 1
|
||||
}
|
||||
|
||||
|
@ -62,7 +56,7 @@ func (s *SetMatrix) Insert(key string, value interface{}) (insetrted bool, cardi
|
|||
}
|
||||
|
||||
// Remove removes the value in the set for a specific key.
|
||||
func (s *SetMatrix) Remove(key string, value interface{}) (removed bool, cardinality int) {
|
||||
func (s *SetMatrix[T]) Remove(key string, value T) (removed bool, cardinality int) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
set, ok := s.matrix[key]
|
||||
|
@ -83,7 +77,7 @@ func (s *SetMatrix) Remove(key string, value interface{}) (removed bool, cardina
|
|||
}
|
||||
|
||||
// Cardinality returns the number of elements in the set for a key.
|
||||
func (s *SetMatrix) Cardinality(key string) (cardinality int, ok bool) {
|
||||
func (s *SetMatrix[T]) Cardinality(key string) (cardinality int, ok bool) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
set, ok := s.matrix[key]
|
||||
|
@ -96,7 +90,7 @@ func (s *SetMatrix) Cardinality(key string) (cardinality int, ok bool) {
|
|||
|
||||
// String returns the string version of the set.
|
||||
// The empty string is returned if there is no set for key.
|
||||
func (s *SetMatrix) String(key string) (v string, ok bool) {
|
||||
func (s *SetMatrix[T]) String(key string) (v string, ok bool) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
set, ok := s.matrix[key]
|
||||
|
@ -107,7 +101,7 @@ func (s *SetMatrix) String(key string) (v string, ok bool) {
|
|||
}
|
||||
|
||||
// Keys returns all the keys in the map.
|
||||
func (s *SetMatrix) Keys() []string {
|
||||
func (s *SetMatrix[T]) Keys() []string {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
keys := make([]string, 0, len(s.matrix))
|
||||
|
|
|
@ -9,7 +9,7 @@ import (
|
|||
)
|
||||
|
||||
func TestSetSerialInsertDelete(t *testing.T) {
|
||||
s := NewSetMatrix()
|
||||
var s SetMatrix[string]
|
||||
|
||||
b, i := s.Insert("a", "1")
|
||||
if !b || i != 1 {
|
||||
|
@ -135,7 +135,7 @@ func TestSetSerialInsertDelete(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func insertDeleteRotuine(ctx context.Context, endCh chan int, s *SetMatrix, key, value string) {
|
||||
func insertDeleteRotuine(ctx context.Context, endCh chan int, s *SetMatrix[string], key, value string) {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
|
@ -158,14 +158,14 @@ func insertDeleteRotuine(ctx context.Context, endCh chan int, s *SetMatrix, key,
|
|||
}
|
||||
|
||||
func TestSetParallelInsertDelete(t *testing.T) {
|
||||
s := NewSetMatrix()
|
||||
var s SetMatrix[string]
|
||||
parallelRoutines := 6
|
||||
endCh := make(chan int)
|
||||
// Let the routines running and competing for 10s
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
for i := 0; i < parallelRoutines; i++ {
|
||||
go insertDeleteRotuine(ctx, endCh, s, "key-"+strconv.Itoa(i%3), strconv.Itoa(i))
|
||||
go insertDeleteRotuine(ctx, endCh, &s, "key-"+strconv.Itoa(i%3), strconv.Itoa(i))
|
||||
}
|
||||
for parallelRoutines > 0 {
|
||||
v := <-endCh
|
||||
|
|
|
@ -11,7 +11,6 @@ import (
|
|||
"github.com/docker/docker/libnetwork/datastore"
|
||||
"github.com/docker/docker/libnetwork/discoverapi"
|
||||
"github.com/docker/docker/libnetwork/driverapi"
|
||||
"github.com/docker/docker/libnetwork/internal/setmatrix"
|
||||
"github.com/docker/docker/libnetwork/ipamapi"
|
||||
"github.com/docker/docker/libnetwork/netlabel"
|
||||
"github.com/docker/docker/libnetwork/netutils"
|
||||
|
@ -389,11 +388,8 @@ func TestSRVServiceQuery(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
sr := svcInfo{
|
||||
svcMap: setmatrix.NewSetMatrix(),
|
||||
svcIPv6Map: setmatrix.NewSetMatrix(),
|
||||
ipMap: setmatrix.NewSetMatrix(),
|
||||
service: make(map[string][]servicePorts),
|
||||
sr := &svcInfo{
|
||||
service: make(map[string][]servicePorts),
|
||||
}
|
||||
// backing container for the service
|
||||
cTarget := serviceTarget{
|
||||
|
|
|
@ -105,9 +105,9 @@ type svcMapEntry struct {
|
|||
}
|
||||
|
||||
type svcInfo struct {
|
||||
svcMap *setmatrix.SetMatrix
|
||||
svcIPv6Map *setmatrix.SetMatrix
|
||||
ipMap *setmatrix.SetMatrix
|
||||
svcMap setmatrix.SetMatrix[svcMapEntry]
|
||||
svcIPv6Map setmatrix.SetMatrix[svcMapEntry]
|
||||
ipMap setmatrix.SetMatrix[ipInfo]
|
||||
service map[string][]servicePorts
|
||||
}
|
||||
|
||||
|
@ -1362,7 +1362,7 @@ func (n *network) updateSvcRecord(ep *Endpoint, localEps []*Endpoint, isAdd bool
|
|||
}
|
||||
}
|
||||
|
||||
func addIPToName(ipMap *setmatrix.SetMatrix, name, serviceID string, ip net.IP) {
|
||||
func addIPToName(ipMap *setmatrix.SetMatrix[ipInfo], name, serviceID string, ip net.IP) {
|
||||
reverseIP := netutils.ReverseIP(ip.String())
|
||||
ipMap.Insert(reverseIP, ipInfo{
|
||||
name: name,
|
||||
|
@ -1370,7 +1370,7 @@ func addIPToName(ipMap *setmatrix.SetMatrix, name, serviceID string, ip net.IP)
|
|||
})
|
||||
}
|
||||
|
||||
func delIPToName(ipMap *setmatrix.SetMatrix, name, serviceID string, ip net.IP) {
|
||||
func delIPToName(ipMap *setmatrix.SetMatrix[ipInfo], name, serviceID string, ip net.IP) {
|
||||
reverseIP := netutils.ReverseIP(ip.String())
|
||||
ipMap.Remove(reverseIP, ipInfo{
|
||||
name: name,
|
||||
|
@ -1378,7 +1378,7 @@ func delIPToName(ipMap *setmatrix.SetMatrix, name, serviceID string, ip net.IP)
|
|||
})
|
||||
}
|
||||
|
||||
func addNameToIP(svcMap *setmatrix.SetMatrix, name, serviceID string, epIP net.IP) {
|
||||
func addNameToIP(svcMap *setmatrix.SetMatrix[svcMapEntry], name, serviceID string, epIP net.IP) {
|
||||
// Since DNS name resolution is case-insensitive, Use the lower-case form
|
||||
// of the name as the key into svcMap
|
||||
lowerCaseName := strings.ToLower(name)
|
||||
|
@ -1388,7 +1388,7 @@ func addNameToIP(svcMap *setmatrix.SetMatrix, name, serviceID string, epIP net.I
|
|||
})
|
||||
}
|
||||
|
||||
func delNameToIP(svcMap *setmatrix.SetMatrix, name, serviceID string, epIP net.IP) {
|
||||
func delNameToIP(svcMap *setmatrix.SetMatrix[svcMapEntry], name, serviceID string, epIP net.IP) {
|
||||
lowerCaseName := strings.ToLower(name)
|
||||
svcMap.Remove(lowerCaseName, svcMapEntry{
|
||||
ip: epIP.String(),
|
||||
|
@ -1411,24 +1411,20 @@ func (n *network) addSvcRecords(eID, name, serviceID string, epIP, epIPv6 net.IP
|
|||
|
||||
sr, ok := c.svcRecords[networkID]
|
||||
if !ok {
|
||||
sr = svcInfo{
|
||||
svcMap: setmatrix.NewSetMatrix(),
|
||||
svcIPv6Map: setmatrix.NewSetMatrix(),
|
||||
ipMap: setmatrix.NewSetMatrix(),
|
||||
}
|
||||
sr = &svcInfo{}
|
||||
c.svcRecords[networkID] = sr
|
||||
}
|
||||
|
||||
if ipMapUpdate {
|
||||
addIPToName(sr.ipMap, name, serviceID, epIP)
|
||||
addIPToName(&sr.ipMap, name, serviceID, epIP)
|
||||
if epIPv6 != nil {
|
||||
addIPToName(sr.ipMap, name, serviceID, epIPv6)
|
||||
addIPToName(&sr.ipMap, name, serviceID, epIPv6)
|
||||
}
|
||||
}
|
||||
|
||||
addNameToIP(sr.svcMap, name, serviceID, epIP)
|
||||
addNameToIP(&sr.svcMap, name, serviceID, epIP)
|
||||
if epIPv6 != nil {
|
||||
addNameToIP(sr.svcIPv6Map, name, serviceID, epIPv6)
|
||||
addNameToIP(&sr.svcIPv6Map, name, serviceID, epIPv6)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1451,17 +1447,17 @@ func (n *network) deleteSvcRecords(eID, name, serviceID string, epIP net.IP, epI
|
|||
}
|
||||
|
||||
if ipMapUpdate {
|
||||
delIPToName(sr.ipMap, name, serviceID, epIP)
|
||||
delIPToName(&sr.ipMap, name, serviceID, epIP)
|
||||
|
||||
if epIPv6 != nil {
|
||||
delIPToName(sr.ipMap, name, serviceID, epIPv6)
|
||||
delIPToName(&sr.ipMap, name, serviceID, epIPv6)
|
||||
}
|
||||
}
|
||||
|
||||
delNameToIP(sr.svcMap, name, serviceID, epIP)
|
||||
delNameToIP(&sr.svcMap, name, serviceID, epIP)
|
||||
|
||||
if epIPv6 != nil {
|
||||
delNameToIP(sr.svcIPv6Map, name, serviceID, epIPv6)
|
||||
delNameToIP(&sr.svcIPv6Map, name, serviceID, epIPv6)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1480,7 +1476,7 @@ func (n *network) getSvcRecords(ep *Endpoint) []etchosts.Record {
|
|||
n.ctrlr.mu.Lock()
|
||||
defer n.ctrlr.mu.Unlock()
|
||||
sr, ok := n.ctrlr.svcRecords[n.id]
|
||||
if !ok || sr.svcMap == nil {
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -1503,7 +1499,7 @@ func (n *network) getSvcRecords(ep *Endpoint) []etchosts.Record {
|
|||
|
||||
recs = append(recs, etchosts.Record{
|
||||
Hosts: k,
|
||||
IP: mapEntryList[0].(svcMapEntry).ip,
|
||||
IP: mapEntryList[0].ip,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -2005,9 +2001,9 @@ func (n *network) ResolveName(req string, ipType int) ([]net.IP, bool) {
|
|||
noDup := make(map[string]bool)
|
||||
var ipLocal []net.IP
|
||||
for _, ip := range ipSet {
|
||||
if _, dup := noDup[ip.(svcMapEntry).ip]; !dup {
|
||||
noDup[ip.(svcMapEntry).ip] = true
|
||||
ipLocal = append(ipLocal, net.ParseIP(ip.(svcMapEntry).ip))
|
||||
if _, dup := noDup[ip.ip]; !dup {
|
||||
noDup[ip.ip] = true
|
||||
ipLocal = append(ipLocal, net.ParseIP(ip.ip))
|
||||
}
|
||||
}
|
||||
return ipLocal, ok
|
||||
|
@ -2058,13 +2054,7 @@ func (n *network) ResolveIP(ip string) string {
|
|||
// network db notifications)
|
||||
// In such cases the resolution will be based on the first element of the set, and can vary
|
||||
// during the system stabilitation
|
||||
elem, ok := elemSet[0].(ipInfo)
|
||||
if !ok {
|
||||
setStr, b := sr.ipMap.String(ip)
|
||||
logrus.Errorf("expected set of ipInfo type for key %s set:%t %s", ip, b, setStr)
|
||||
return ""
|
||||
}
|
||||
|
||||
elem := elemSet[0]
|
||||
if elem.extResolver {
|
||||
return ""
|
||||
}
|
||||
|
|
|
@ -54,7 +54,7 @@ type service struct {
|
|||
// associated with it. At stable state the endpoint ID expected is 1
|
||||
// but during transition and service change it is possible to have
|
||||
// temporary more than 1
|
||||
ipToEndpoint *setmatrix.SetMatrix
|
||||
ipToEndpoint setmatrix.SetMatrix[string]
|
||||
|
||||
deleted bool
|
||||
|
||||
|
|
|
@ -6,7 +6,6 @@ package libnetwork
|
|||
import (
|
||||
"net"
|
||||
|
||||
"github.com/docker/docker/libnetwork/internal/setmatrix"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
|
@ -144,7 +143,6 @@ func newService(name string, id string, ingressPorts []*PortConfig, serviceAlias
|
|||
ingressPorts: ingressPorts,
|
||||
loadBalancers: make(map[string]*loadBalancer),
|
||||
aliases: serviceAliases,
|
||||
ipToEndpoint: setmatrix.NewSetMatrix(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -174,7 +172,7 @@ func (c *Controller) cleanupServiceDiscovery(cleanupNID string) {
|
|||
defer c.mu.Unlock()
|
||||
if cleanupNID == "" {
|
||||
logrus.Debugf("cleanupServiceDiscovery for all networks")
|
||||
c.svcRecords = make(map[string]svcInfo)
|
||||
c.svcRecords = make(map[string]*svcInfo)
|
||||
return
|
||||
}
|
||||
logrus.Debugf("cleanupServiceDiscovery for network:%s", cleanupNID)
|
||||
|
|
|
@ -31,7 +31,7 @@ require (
|
|||
github.com/containerd/typeurl/v2 v2.1.0
|
||||
github.com/coreos/go-systemd/v22 v22.5.0
|
||||
github.com/creack/pty v1.1.11
|
||||
github.com/deckarep/golang-set v0.0.0-20141123011944-ef32fa3046d9
|
||||
github.com/deckarep/golang-set/v2 v2.3.0
|
||||
github.com/docker/distribution v2.8.1+incompatible
|
||||
github.com/docker/go-connections v0.4.0
|
||||
github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c
|
||||
|
|
|
@ -484,8 +484,8 @@ github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2
|
|||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/deckarep/golang-set v0.0.0-20141123011944-ef32fa3046d9 h1:YpTz1+8tEHbybtxtMJNkV3U3GBAA05EakMRTR3dXkis=
|
||||
github.com/deckarep/golang-set v0.0.0-20141123011944-ef32fa3046d9/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ=
|
||||
github.com/deckarep/golang-set/v2 v2.3.0 h1:qs18EKUfHm2X9fA50Mr/M5hccg2tNnVqsiBImnyDs0g=
|
||||
github.com/deckarep/golang-set/v2 v2.3.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4=
|
||||
github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0=
|
||||
github.com/devigned/tab v0.1.1/go.mod h1:XG9mPq0dFghrYvoBF3xdRrJzSTX1b7IQrvaL9mzjeJY=
|
||||
github.com/dgrijalva/jwt-go v0.0.0-20170104182250-a601269ab70c/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
|
|
9
vendor/github.com/deckarep/golang-set/.travis.yml
generated
vendored
9
vendor/github.com/deckarep/golang-set/.travis.yml
generated
vendored
|
@ -1,9 +0,0 @@
|
|||
language: go
|
||||
|
||||
go:
|
||||
- 1.2
|
||||
|
||||
script:
|
||||
- go test ./...
|
||||
#- go test -race ./...
|
||||
|
94
vendor/github.com/deckarep/golang-set/README.md
generated
vendored
94
vendor/github.com/deckarep/golang-set/README.md
generated
vendored
|
@ -1,94 +0,0 @@
|
|||
[![Build Status](https://travis-ci.org/deckarep/golang-set.png?branch=master)](https://travis-ci.org/deckarep/golang-set)
|
||||
[![GoDoc](https://godoc.org/github.com/deckarep/golang-set?status.png)](http://godoc.org/github.com/deckarep/golang-set)
|
||||
|
||||
## golang-set
|
||||
|
||||
|
||||
The missing set collection for the Go language. Until Go has sets built-in...use this.
|
||||
|
||||
Coming from Python one of the things I miss is the superbly wonderful set collection. This is my attempt to mimic the primary features of the set from Python.
|
||||
You can of course argue that there is no need for a set in Go, otherwise the creators would have added one to the standard library. To those I say simply ignore this repository
|
||||
and carry-on and to the rest that find this useful please contribute in helping me make it better by:
|
||||
|
||||
* Helping to make more idiomatic improvements to the code.
|
||||
* Helping to increase the performance of it. ~~(So far, no attempt has been made, but since it uses a map internally, I expect it to be mostly performant.)~~
|
||||
* Helping to make the unit-tests more robust and kick-ass.
|
||||
* Helping to fill in the [documentation.](http://godoc.org/github.com/deckarep/golang-set)
|
||||
* Simply offering feedback and suggestions. (Positive, constructive feedback is appreciated.)
|
||||
|
||||
I have to give some credit for helping seed the idea with this post on [stackoverflow.](http://programmers.stackexchange.com/questions/177428/sets-data-structure-in-golang)
|
||||
|
||||
*Update* - as of 3/9/2014, you can use a compile-time generic version of this package in the [gen](http://clipperhouse.github.io/gen/) framework. This framework allows you to use the golang-set in a completely generic and type-safe way by allowing you to generate a supporting .go file based on your custom types.
|
||||
|
||||
## Features (as of 9/22/2014)
|
||||
|
||||
* a CartesionProduct() method has been added with unit-tests: [Read more about the cartesion product](http://en.wikipedia.org/wiki/Cartesian_product)
|
||||
|
||||
## Features (as of 9/15/2014)
|
||||
|
||||
* a PowerSet() method has been added with unit-tests: [Read more about the Power set](http://en.wikipedia.org/wiki/Power_set)
|
||||
|
||||
## Features (as of 4/22/2014)
|
||||
|
||||
* One common interface to both implementations
|
||||
* Two set implementations to choose from
|
||||
* a thread-safe implementation designed for concurrent use
|
||||
* a non-thread-safe implementation designed for performance
|
||||
* 75 benchmarks for both implementations
|
||||
* 35 unit tests for both implementations
|
||||
* 14 concurrent tests for the thread-safe implementation
|
||||
|
||||
|
||||
|
||||
Please see the unit test file for additional usage examples. The Python set documentation will also do a better job than I can of explaining how a set typically [works.](http://docs.python.org/2/library/sets.html) Please keep in mind
|
||||
however that the Python set is a built-in type and supports additional features and syntax that make it awesome.
|
||||
|
||||
## Examples but not exhaustive:
|
||||
|
||||
```go
|
||||
requiredClasses := mapset.NewSet()
|
||||
requiredClasses.Add("Cooking")
|
||||
requiredClasses.Add("English")
|
||||
requiredClasses.Add("Math")
|
||||
requiredClasses.Add("Biology")
|
||||
|
||||
scienceSlice := []interface{}{"Biology", "Chemistry"}
|
||||
scienceClasses := mapset.NewSetFromSlice(scienceSlice)
|
||||
|
||||
electiveClasses := mapset.NewSet()
|
||||
electiveClasses.Add("Welding")
|
||||
electiveClasses.Add("Music")
|
||||
electiveClasses.Add("Automotive")
|
||||
|
||||
bonusClasses := mapset.NewSet()
|
||||
bonusClasses.Add("Go Programming")
|
||||
bonusClasses.Add("Python Programming")
|
||||
|
||||
//Show me all the available classes I can take
|
||||
allClasses := requiredClasses.Union(scienceClasses).Union(electiveClasses).Union(bonusClasses)
|
||||
fmt.Println(allClasses) //Set{Cooking, English, Math, Chemistry, Welding, Biology, Music, Automotive, Go Programming, Python Programming}
|
||||
|
||||
|
||||
//Is cooking considered a science class?
|
||||
fmt.Println(scienceClasses.Contains("Cooking")) //false
|
||||
|
||||
//Show me all classes that are not science classes, since I hate science.
|
||||
fmt.Println(allClasses.Difference(scienceClasses)) //Set{Music, Automotive, Go Programming, Python Programming, Cooking, English, Math, Welding}
|
||||
|
||||
//Which science classes are also required classes?
|
||||
fmt.Println(scienceClasses.Intersect(requiredClasses)) //Set{Biology}
|
||||
|
||||
//How many bonus classes do you offer?
|
||||
fmt.Println(bonusClasses.Cardinality()) //2
|
||||
|
||||
//Do you have the following classes? Welding, Automotive and English?
|
||||
fmt.Println(allClasses.IsSuperset(mapset.NewSetFromSlice([]interface{}{"Welding", "Automotive", "English"}))) //true
|
||||
```
|
||||
|
||||
Thanks!
|
||||
|
||||
-Ralph
|
||||
|
||||
[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/deckarep/golang-set/trend.png)](https://bitdeli.com/free "Bitdeli Badge")
|
||||
|
||||
[![Analytics](https://ga-beacon.appspot.com/UA-42584447-2/deckarep/golang-set)](https://github.com/igrigorik/ga-beacon)
|
168
vendor/github.com/deckarep/golang-set/set.go
generated
vendored
168
vendor/github.com/deckarep/golang-set/set.go
generated
vendored
|
@ -1,168 +0,0 @@
|
|||
/*
|
||||
Open Source Initiative OSI - The MIT License (MIT):Licensing
|
||||
|
||||
The MIT License (MIT)
|
||||
Copyright (c) 2013 Ralph Caraveo (deckarep@gmail.com)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
// Package mapset implements a simple and generic set collection.
|
||||
// Items stored within it are unordered and unique. It supports
|
||||
// typical set operations: membership testing, intersection, union,
|
||||
// difference, symmetric difference and cloning.
|
||||
//
|
||||
// Package mapset provides two implementations. The default
|
||||
// implementation is safe for concurrent access. There is a non-threadsafe
|
||||
// implementation which is slightly more performant.
|
||||
package mapset
|
||||
|
||||
type Set interface {
|
||||
// Adds an element to the set. Returns whether
|
||||
// the item was added.
|
||||
Add(i interface{}) bool
|
||||
|
||||
// Returns the number of elements in the set.
|
||||
Cardinality() int
|
||||
|
||||
// Removes all elements from the set, leaving
|
||||
// the emtpy set.
|
||||
Clear()
|
||||
|
||||
// Returns a clone of the set using the same
|
||||
// implementation, duplicating all keys.
|
||||
Clone() Set
|
||||
|
||||
// Returns whether the given items
|
||||
// are all in the set.
|
||||
Contains(i ...interface{}) bool
|
||||
|
||||
// Returns the difference between this set
|
||||
// and other. The returned set will contain
|
||||
// all elements of this set that are not also
|
||||
// elements of other.
|
||||
//
|
||||
// Note that the argument to Difference
|
||||
// must be of the same type as the receiver
|
||||
// of the method. Otherwise, Difference will
|
||||
// panic.
|
||||
Difference(other Set) Set
|
||||
|
||||
// Determines if two sets are equal to each
|
||||
// other. If they have the same cardinality
|
||||
// and contain the same elements, they are
|
||||
// considered equal. The order in which
|
||||
// the elements were added is irrelevant.
|
||||
//
|
||||
// Note that the argument to Equal must be
|
||||
// of the same type as the receiver of the
|
||||
// method. Otherwise, Equal will panic.
|
||||
Equal(other Set) bool
|
||||
|
||||
// Returns a new set containing only the elements
|
||||
// that exist only in both sets.
|
||||
//
|
||||
// Note that the argument to Intersect
|
||||
// must be of the same type as the receiver
|
||||
// of the method. Otherwise, Intersect will
|
||||
// panic.
|
||||
Intersect(other Set) Set
|
||||
|
||||
// Determines if every element in the other set
|
||||
// is in this set.
|
||||
//
|
||||
// Note that the argument to IsSubset
|
||||
// must be of the same type as the receiver
|
||||
// of the method. Otherwise, IsSubset will
|
||||
// panic.
|
||||
IsSubset(other Set) bool
|
||||
|
||||
// Determines if every element in this set is in
|
||||
// the other set.
|
||||
//
|
||||
// Note that the argument to IsSuperset
|
||||
// must be of the same type as the receiver
|
||||
// of the method. Otherwise, IsSuperset will
|
||||
// panic.
|
||||
IsSuperset(other Set) bool
|
||||
|
||||
// Returns a channel of elements that you can
|
||||
// range over.
|
||||
Iter() <-chan interface{}
|
||||
|
||||
// Remove a single element from the set.
|
||||
Remove(i interface{})
|
||||
|
||||
// Provides a convenient string representation
|
||||
// of the current state of the set.
|
||||
String() string
|
||||
|
||||
// Returns a new set with all elements which are
|
||||
// in either this set or the other set but not in both.
|
||||
//
|
||||
// Note that the argument to SymmetricDifference
|
||||
// must be of the same type as the receiver
|
||||
// of the method. Otherwise, SymmetricDifference
|
||||
// will panic.
|
||||
SymmetricDifference(other Set) Set
|
||||
|
||||
// Returns a new set with all elements in both sets.
|
||||
//
|
||||
// Note that the argument to Union must be of the
|
||||
// same type as the receiver of the method.
|
||||
// Otherwise, IsSuperset will panic.
|
||||
Union(other Set) Set
|
||||
|
||||
// Returns all subsets of a given set (Power Set).
|
||||
PowerSet() Set
|
||||
|
||||
// Returns the Cartesian Product of two sets.
|
||||
CartesianProduct(other Set) Set
|
||||
|
||||
// Returns the members of the set as a slice.
|
||||
ToSlice() []interface{}
|
||||
}
|
||||
|
||||
// Creates and returns a reference to an empty set.
|
||||
func NewSet() Set {
|
||||
set := newThreadSafeSet()
|
||||
return &set
|
||||
}
|
||||
|
||||
// Creates and returns a reference to a set from an existing slice
|
||||
func NewSetFromSlice(s []interface{}) Set {
|
||||
a := NewSet()
|
||||
for _, item := range s {
|
||||
a.Add(item)
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
func NewThreadUnsafeSet() Set {
|
||||
set := newThreadUnsafeSet()
|
||||
return &set
|
||||
}
|
||||
|
||||
func NewThreadUnsafeSetFromSlice(s []interface{}) Set {
|
||||
a := NewThreadUnsafeSet()
|
||||
for _, item := range s {
|
||||
a.Add(item)
|
||||
}
|
||||
return a
|
||||
}
|
204
vendor/github.com/deckarep/golang-set/threadsafe.go
generated
vendored
204
vendor/github.com/deckarep/golang-set/threadsafe.go
generated
vendored
|
@ -1,204 +0,0 @@
|
|||
/*
|
||||
Open Source Initiative OSI - The MIT License (MIT):Licensing
|
||||
|
||||
The MIT License (MIT)
|
||||
Copyright (c) 2013 Ralph Caraveo (deckarep@gmail.com)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
package mapset
|
||||
|
||||
import "sync"
|
||||
|
||||
type threadSafeSet struct {
|
||||
s threadUnsafeSet
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
func newThreadSafeSet() threadSafeSet {
|
||||
return threadSafeSet{s: newThreadUnsafeSet()}
|
||||
}
|
||||
|
||||
func (set *threadSafeSet) Add(i interface{}) bool {
|
||||
set.Lock()
|
||||
ret := set.s.Add(i)
|
||||
set.Unlock()
|
||||
return ret
|
||||
}
|
||||
|
||||
func (set *threadSafeSet) Contains(i ...interface{}) bool {
|
||||
set.RLock()
|
||||
ret := set.s.Contains(i...)
|
||||
set.RUnlock()
|
||||
return ret
|
||||
}
|
||||
|
||||
func (set *threadSafeSet) IsSubset(other Set) bool {
|
||||
o := other.(*threadSafeSet)
|
||||
|
||||
set.RLock()
|
||||
o.RLock()
|
||||
|
||||
ret := set.s.IsSubset(&o.s)
|
||||
set.RUnlock()
|
||||
o.RUnlock()
|
||||
return ret
|
||||
}
|
||||
|
||||
func (set *threadSafeSet) IsSuperset(other Set) bool {
|
||||
return other.IsSubset(set)
|
||||
}
|
||||
|
||||
func (set *threadSafeSet) Union(other Set) Set {
|
||||
o := other.(*threadSafeSet)
|
||||
|
||||
set.RLock()
|
||||
o.RLock()
|
||||
|
||||
unsafeUnion := set.s.Union(&o.s).(*threadUnsafeSet)
|
||||
ret := &threadSafeSet{s: *unsafeUnion}
|
||||
set.RUnlock()
|
||||
o.RUnlock()
|
||||
return ret
|
||||
}
|
||||
|
||||
func (set *threadSafeSet) Intersect(other Set) Set {
|
||||
o := other.(*threadSafeSet)
|
||||
|
||||
set.RLock()
|
||||
o.RLock()
|
||||
|
||||
unsafeIntersection := set.s.Intersect(&o.s).(*threadUnsafeSet)
|
||||
ret := &threadSafeSet{s: *unsafeIntersection}
|
||||
set.RUnlock()
|
||||
o.RUnlock()
|
||||
return ret
|
||||
}
|
||||
|
||||
func (set *threadSafeSet) Difference(other Set) Set {
|
||||
o := other.(*threadSafeSet)
|
||||
|
||||
set.RLock()
|
||||
o.RLock()
|
||||
|
||||
unsafeDifference := set.s.Difference(&o.s).(*threadUnsafeSet)
|
||||
ret := &threadSafeSet{s: *unsafeDifference}
|
||||
set.RUnlock()
|
||||
o.RUnlock()
|
||||
return ret
|
||||
}
|
||||
|
||||
func (set *threadSafeSet) SymmetricDifference(other Set) Set {
|
||||
o := other.(*threadSafeSet)
|
||||
|
||||
unsafeDifference := set.s.SymmetricDifference(&o.s).(*threadUnsafeSet)
|
||||
return &threadSafeSet{s: *unsafeDifference}
|
||||
}
|
||||
|
||||
func (set *threadSafeSet) Clear() {
|
||||
set.Lock()
|
||||
set.s = newThreadUnsafeSet()
|
||||
set.Unlock()
|
||||
}
|
||||
|
||||
func (set *threadSafeSet) Remove(i interface{}) {
|
||||
set.Lock()
|
||||
delete(set.s, i)
|
||||
set.Unlock()
|
||||
}
|
||||
|
||||
func (set *threadSafeSet) Cardinality() int {
|
||||
set.RLock()
|
||||
defer set.RUnlock()
|
||||
return len(set.s)
|
||||
}
|
||||
|
||||
func (set *threadSafeSet) Iter() <-chan interface{} {
|
||||
ch := make(chan interface{})
|
||||
go func() {
|
||||
set.RLock()
|
||||
|
||||
for elem := range set.s {
|
||||
ch <- elem
|
||||
}
|
||||
close(ch)
|
||||
set.RUnlock()
|
||||
}()
|
||||
|
||||
return ch
|
||||
}
|
||||
|
||||
func (set *threadSafeSet) Equal(other Set) bool {
|
||||
o := other.(*threadSafeSet)
|
||||
|
||||
set.RLock()
|
||||
o.RLock()
|
||||
|
||||
ret := set.s.Equal(&o.s)
|
||||
set.RUnlock()
|
||||
o.RUnlock()
|
||||
return ret
|
||||
}
|
||||
|
||||
func (set *threadSafeSet) Clone() Set {
|
||||
set.RLock()
|
||||
|
||||
unsafeClone := set.s.Clone().(*threadUnsafeSet)
|
||||
ret := &threadSafeSet{s: *unsafeClone}
|
||||
set.RUnlock()
|
||||
return ret
|
||||
}
|
||||
|
||||
func (set *threadSafeSet) String() string {
|
||||
set.RLock()
|
||||
ret := set.s.String()
|
||||
set.RUnlock()
|
||||
return ret
|
||||
}
|
||||
|
||||
func (set *threadSafeSet) PowerSet() Set {
|
||||
set.RLock()
|
||||
ret := set.s.PowerSet()
|
||||
set.RUnlock()
|
||||
return ret
|
||||
}
|
||||
|
||||
func (set *threadSafeSet) CartesianProduct(other Set) Set {
|
||||
o := other.(*threadSafeSet)
|
||||
|
||||
set.RLock()
|
||||
o.RLock()
|
||||
|
||||
unsafeCartProduct := set.s.CartesianProduct(&o.s).(*threadUnsafeSet)
|
||||
ret := &threadSafeSet{s: *unsafeCartProduct}
|
||||
set.RUnlock()
|
||||
o.RUnlock()
|
||||
return ret
|
||||
}
|
||||
|
||||
func (set *threadSafeSet) ToSlice() []interface{} {
|
||||
set.RLock()
|
||||
keys := make([]interface{}, 0, set.Cardinality())
|
||||
for elem := range set.s {
|
||||
keys = append(keys, elem)
|
||||
}
|
||||
set.RUnlock()
|
||||
return keys
|
||||
}
|
246
vendor/github.com/deckarep/golang-set/threadunsafe.go
generated
vendored
246
vendor/github.com/deckarep/golang-set/threadunsafe.go
generated
vendored
|
@ -1,246 +0,0 @@
|
|||
/*
|
||||
Open Source Initiative OSI - The MIT License (MIT):Licensing
|
||||
|
||||
The MIT License (MIT)
|
||||
Copyright (c) 2013 Ralph Caraveo (deckarep@gmail.com)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
package mapset
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type threadUnsafeSet map[interface{}]struct{}
|
||||
|
||||
type orderedPair struct {
|
||||
first interface{}
|
||||
second interface{}
|
||||
}
|
||||
|
||||
func newThreadUnsafeSet() threadUnsafeSet {
|
||||
return make(threadUnsafeSet)
|
||||
}
|
||||
|
||||
func (pair *orderedPair) Equal(other orderedPair) bool {
|
||||
if pair.first == other.first &&
|
||||
pair.second == other.second {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (set *threadUnsafeSet) Add(i interface{}) bool {
|
||||
_, found := (*set)[i]
|
||||
(*set)[i] = struct{}{}
|
||||
return !found //False if it existed already
|
||||
}
|
||||
|
||||
func (set *threadUnsafeSet) Contains(i ...interface{}) bool {
|
||||
for _, val := range i {
|
||||
if _, ok := (*set)[val]; !ok {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (set *threadUnsafeSet) IsSubset(other Set) bool {
|
||||
_ = other.(*threadUnsafeSet)
|
||||
for elem := range *set {
|
||||
if !other.Contains(elem) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (set *threadUnsafeSet) IsSuperset(other Set) bool {
|
||||
return other.IsSubset(set)
|
||||
}
|
||||
|
||||
func (set *threadUnsafeSet) Union(other Set) Set {
|
||||
o := other.(*threadUnsafeSet)
|
||||
|
||||
unionedSet := newThreadUnsafeSet()
|
||||
|
||||
for elem := range *set {
|
||||
unionedSet.Add(elem)
|
||||
}
|
||||
for elem := range *o {
|
||||
unionedSet.Add(elem)
|
||||
}
|
||||
return &unionedSet
|
||||
}
|
||||
|
||||
func (set *threadUnsafeSet) Intersect(other Set) Set {
|
||||
o := other.(*threadUnsafeSet)
|
||||
|
||||
intersection := newThreadUnsafeSet()
|
||||
// loop over smaller set
|
||||
if set.Cardinality() < other.Cardinality() {
|
||||
for elem := range *set {
|
||||
if other.Contains(elem) {
|
||||
intersection.Add(elem)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for elem := range *o {
|
||||
if set.Contains(elem) {
|
||||
intersection.Add(elem)
|
||||
}
|
||||
}
|
||||
}
|
||||
return &intersection
|
||||
}
|
||||
|
||||
func (set *threadUnsafeSet) Difference(other Set) Set {
|
||||
_ = other.(*threadUnsafeSet)
|
||||
|
||||
difference := newThreadUnsafeSet()
|
||||
for elem := range *set {
|
||||
if !other.Contains(elem) {
|
||||
difference.Add(elem)
|
||||
}
|
||||
}
|
||||
return &difference
|
||||
}
|
||||
|
||||
func (set *threadUnsafeSet) SymmetricDifference(other Set) Set {
|
||||
_ = other.(*threadUnsafeSet)
|
||||
|
||||
aDiff := set.Difference(other)
|
||||
bDiff := other.Difference(set)
|
||||
return aDiff.Union(bDiff)
|
||||
}
|
||||
|
||||
func (set *threadUnsafeSet) Clear() {
|
||||
*set = newThreadUnsafeSet()
|
||||
}
|
||||
|
||||
func (set *threadUnsafeSet) Remove(i interface{}) {
|
||||
delete(*set, i)
|
||||
}
|
||||
|
||||
func (set *threadUnsafeSet) Cardinality() int {
|
||||
return len(*set)
|
||||
}
|
||||
|
||||
func (set *threadUnsafeSet) Iter() <-chan interface{} {
|
||||
ch := make(chan interface{})
|
||||
go func() {
|
||||
for elem := range *set {
|
||||
ch <- elem
|
||||
}
|
||||
close(ch)
|
||||
}()
|
||||
|
||||
return ch
|
||||
}
|
||||
|
||||
func (set *threadUnsafeSet) Equal(other Set) bool {
|
||||
_ = other.(*threadUnsafeSet)
|
||||
|
||||
if set.Cardinality() != other.Cardinality() {
|
||||
return false
|
||||
}
|
||||
for elem := range *set {
|
||||
if !other.Contains(elem) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (set *threadUnsafeSet) Clone() Set {
|
||||
clonedSet := newThreadUnsafeSet()
|
||||
for elem := range *set {
|
||||
clonedSet.Add(elem)
|
||||
}
|
||||
return &clonedSet
|
||||
}
|
||||
|
||||
func (set *threadUnsafeSet) String() string {
|
||||
items := make([]string, 0, len(*set))
|
||||
|
||||
for elem := range *set {
|
||||
items = append(items, fmt.Sprintf("%v", elem))
|
||||
}
|
||||
return fmt.Sprintf("Set{%s}", strings.Join(items, ", "))
|
||||
}
|
||||
|
||||
func (pair orderedPair) String() string {
|
||||
return fmt.Sprintf("(%v, %v)", pair.first, pair.second)
|
||||
}
|
||||
|
||||
func (set *threadUnsafeSet) PowerSet() Set {
|
||||
powSet := NewThreadUnsafeSet()
|
||||
nullset := newThreadUnsafeSet()
|
||||
powSet.Add(&nullset)
|
||||
|
||||
for es := range *set {
|
||||
u := newThreadUnsafeSet()
|
||||
j := powSet.Iter()
|
||||
for er := range j {
|
||||
p := newThreadUnsafeSet()
|
||||
if reflect.TypeOf(er).Name() == "" {
|
||||
k := er.(*threadUnsafeSet)
|
||||
for ek := range *(k) {
|
||||
p.Add(ek)
|
||||
}
|
||||
} else {
|
||||
p.Add(er)
|
||||
}
|
||||
p.Add(es)
|
||||
u.Add(&p)
|
||||
}
|
||||
|
||||
powSet = powSet.Union(&u)
|
||||
}
|
||||
|
||||
return powSet
|
||||
}
|
||||
|
||||
func (set *threadUnsafeSet) CartesianProduct(other Set) Set {
|
||||
o := other.(*threadUnsafeSet)
|
||||
cartProduct := NewThreadUnsafeSet()
|
||||
|
||||
for i := range *set {
|
||||
for j := range *o {
|
||||
elem := orderedPair{first: i, second: j}
|
||||
cartProduct.Add(elem)
|
||||
}
|
||||
}
|
||||
|
||||
return cartProduct
|
||||
}
|
||||
|
||||
func (set *threadUnsafeSet) ToSlice() []interface{} {
|
||||
keys := make([]interface{}, 0, set.Cardinality())
|
||||
for elem := range *set {
|
||||
keys = append(keys, elem)
|
||||
}
|
||||
|
||||
return keys
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
Open Source Initiative OSI - The MIT License (MIT):Licensing
|
||||
|
||||
The MIT License (MIT)
|
||||
Copyright (c) 2013 Ralph Caraveo (deckarep@gmail.com)
|
||||
Copyright (c) 2013 - 2022 Ralph Caraveo (deckarep@gmail.com)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
173
vendor/github.com/deckarep/golang-set/v2/README.md
generated
vendored
Normal file
173
vendor/github.com/deckarep/golang-set/v2/README.md
generated
vendored
Normal file
|
@ -0,0 +1,173 @@
|
|||
![example workflow](https://github.com/deckarep/golang-set/actions/workflows/ci.yml/badge.svg)
|
||||
[![Go Report Card](https://goreportcard.com/badge/github.com/deckarep/golang-set/v2)](https://goreportcard.com/report/github.com/deckarep/golang-set/v2)
|
||||
[![GoDoc](https://godoc.org/github.com/deckarep/golang-set/v2?status.svg)](http://godoc.org/github.com/deckarep/golang-set/v2)
|
||||
|
||||
# golang-set
|
||||
|
||||
The missing `generic` set collection for the Go language. Until Go has sets built-in...use this.
|
||||
|
||||
## Update 3/5/2023
|
||||
* Packaged version: `2.2.0` release includes a refactor to minimize pointer indirection, better method documentation standards and a few constructor convenience methods to increase ergonomics when appending items `Append` or creating a new set from an exist `Map`.
|
||||
* supports `new generic` syntax
|
||||
* Go `1.18.0` or higher
|
||||
* Workflow tested on Go `1.20`
|
||||
|
||||
![With Generics](new_improved.jpeg)
|
||||
|
||||
Coming from Python one of the things I miss is the superbly wonderful set collection. This is my attempt to mimic the primary features of the set collection from Python.
|
||||
You can of course argue that there is no need for a set in Go, otherwise the creators would have added one to the standard library. To those I say simply ignore this repository and carry-on and to the rest that find this useful please contribute in helping me make it better by contributing with suggestions or PRs.
|
||||
|
||||
## Features
|
||||
|
||||
* *NEW* [Generics](https://go.dev/doc/tutorial/generics) based implementation (requires [Go 1.18](https://go.dev/blog/go1.18beta1) or higher)
|
||||
* One common *interface* to both implementations
|
||||
* a **non threadsafe** implementation favoring *performance*
|
||||
* a **threadsafe** implementation favoring *concurrent* use
|
||||
* Feature complete set implementation modeled after [Python's set implementation](https://docs.python.org/3/library/stdtypes.html#set).
|
||||
* Exhaustive unit-test and benchmark suite
|
||||
|
||||
## Trusted by
|
||||
|
||||
This package is trusted by many companies and thousands of open-source packages. Here are just a few sample users of this package.
|
||||
|
||||
* Notable projects/companies using this package
|
||||
* Ethereum
|
||||
* Docker
|
||||
* 1Password
|
||||
* Hashicorp
|
||||
|
||||
## Star History
|
||||
|
||||
[![Star History Chart](https://api.star-history.com/svg?repos=deckarep/golang-set&type=Date)](https://star-history.com/#deckarep/golang-set&Date)
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
The code below demonstrates how a Set collection can better manage data and actually minimize boilerplate and needless loops in code. This package now fully supports *generic* syntax so you are now able to instantiate a collection for any [comparable](https://flaviocopes.com/golang-comparing-values/) type object.
|
||||
|
||||
What is considered comparable in Go?
|
||||
* `Booleans`, `integers`, `strings`, `floats` or basically primitive types.
|
||||
* `Pointers`
|
||||
* `Arrays`
|
||||
* `Structs` if *all of their fields* are also comparable independently
|
||||
|
||||
Using this library is as simple as creating either a threadsafe or non-threadsafe set and providing a `comparable` type for instantiation of the collection.
|
||||
|
||||
```go
|
||||
// Syntax example, doesn't compile.
|
||||
mySet := mapset.NewSet[T]() // where T is some concrete comparable type.
|
||||
|
||||
// Therefore this code creates an int set
|
||||
mySet := mapset.NewSet[int]()
|
||||
|
||||
// Or perhaps you want a string set
|
||||
mySet := mapset.NewSet[string]()
|
||||
|
||||
type myStruct {
|
||||
name string
|
||||
age uint8
|
||||
}
|
||||
|
||||
// Alternatively a set of structs
|
||||
mySet := mapset.NewSet[myStruct]()
|
||||
|
||||
// Lastly a set that can hold anything using the any or empty interface keyword: interface{}. This is effectively removes type safety.
|
||||
mySet := mapset.NewSet[any]()
|
||||
```
|
||||
|
||||
## Comprehensive Example
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
mapset "github.com/deckarep/golang-set/v2"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Create a string-based set of required classes.
|
||||
required := mapset.NewSet[string]()
|
||||
required.Add("cooking")
|
||||
required.Add("english")
|
||||
required.Add("math")
|
||||
required.Add("biology")
|
||||
|
||||
// Create a string-based set of science classes.
|
||||
sciences := mapset.NewSet[string]()
|
||||
sciences.Add("biology")
|
||||
sciences.Add("chemistry")
|
||||
|
||||
// Create a string-based set of electives.
|
||||
electives := mapset.NewSet[string]()
|
||||
electives.Add("welding")
|
||||
electives.Add("music")
|
||||
electives.Add("automotive")
|
||||
|
||||
// Create a string-based set of bonus programming classes.
|
||||
bonus := mapset.NewSet[string]()
|
||||
bonus.Add("beginner go")
|
||||
bonus.Add("python for dummies")
|
||||
}
|
||||
```
|
||||
|
||||
Create a set of all unique classes.
|
||||
Sets will *automatically* deduplicate the same data.
|
||||
|
||||
```go
|
||||
all := required
|
||||
.Union(sciences)
|
||||
.Union(electives)
|
||||
.Union(bonus)
|
||||
|
||||
fmt.Println(all)
|
||||
```
|
||||
|
||||
Output:
|
||||
```sh
|
||||
Set{cooking, english, math, chemistry, welding, biology, music, automotive, beginner go, python for dummies}
|
||||
```
|
||||
|
||||
Is cooking considered a science class?
|
||||
```go
|
||||
result := sciences.Contains("cooking")
|
||||
fmt.Println(result)
|
||||
```
|
||||
|
||||
Output:
|
||||
```false
|
||||
false
|
||||
```
|
||||
|
||||
Show me all classes that are not science classes, since I don't enjoy science.
|
||||
```go
|
||||
notScience := all.Difference(sciences)
|
||||
fmt.Println(notScience)
|
||||
```
|
||||
|
||||
```sh
|
||||
Set{ music, automotive, beginner go, python for dummies, cooking, english, math, welding }
|
||||
```
|
||||
|
||||
Which science classes are also required classes?
|
||||
```go
|
||||
reqScience := sciences.Intersect(required)
|
||||
```
|
||||
|
||||
Output:
|
||||
```sh
|
||||
Set{biology}
|
||||
```
|
||||
|
||||
How many bonus classes do you offer?
|
||||
```go
|
||||
fmt.Println(bonus.Cardinality())
|
||||
```
|
||||
Output:
|
||||
```sh
|
||||
2
|
||||
```
|
||||
|
||||
Thanks for visiting!
|
||||
|
||||
-deckarep
|
58
vendor/github.com/deckarep/golang-set/v2/iterator.go
generated
vendored
Normal file
58
vendor/github.com/deckarep/golang-set/v2/iterator.go
generated
vendored
Normal file
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
Open Source Initiative OSI - The MIT License (MIT):Licensing
|
||||
|
||||
The MIT License (MIT)
|
||||
Copyright (c) 2013 - 2022 Ralph Caraveo (deckarep@gmail.com)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
package mapset
|
||||
|
||||
// Iterator defines an iterator over a Set, its C channel can be used to range over the Set's
|
||||
// elements.
|
||||
type Iterator[T comparable] struct {
|
||||
C <-chan T
|
||||
stop chan struct{}
|
||||
}
|
||||
|
||||
// Stop stops the Iterator, no further elements will be received on C, C will be closed.
|
||||
func (i *Iterator[T]) Stop() {
|
||||
// Allows for Stop() to be called multiple times
|
||||
// (close() panics when called on already closed channel)
|
||||
defer func() {
|
||||
recover()
|
||||
}()
|
||||
|
||||
close(i.stop)
|
||||
|
||||
// Exhaust any remaining elements.
|
||||
for range i.C {
|
||||
}
|
||||
}
|
||||
|
||||
// newIterator returns a new Iterator instance together with its item and stop channels.
|
||||
func newIterator[T comparable]() (*Iterator[T], chan<- T, <-chan struct{}) {
|
||||
itemChan := make(chan T)
|
||||
stopChan := make(chan struct{})
|
||||
return &Iterator[T]{
|
||||
C: itemChan,
|
||||
stop: stopChan,
|
||||
}, itemChan, stopChan
|
||||
}
|
BIN
vendor/github.com/deckarep/golang-set/v2/new_improved.jpeg
generated
vendored
Normal file
BIN
vendor/github.com/deckarep/golang-set/v2/new_improved.jpeg
generated
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 118 KiB |
241
vendor/github.com/deckarep/golang-set/v2/set.go
generated
vendored
Normal file
241
vendor/github.com/deckarep/golang-set/v2/set.go
generated
vendored
Normal file
|
@ -0,0 +1,241 @@
|
|||
/*
|
||||
Open Source Initiative OSI - The MIT License (MIT):Licensing
|
||||
|
||||
The MIT License (MIT)
|
||||
Copyright (c) 2013 - 2022 Ralph Caraveo (deckarep@gmail.com)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
// Package mapset implements a simple and set collection.
|
||||
// Items stored within it are unordered and unique. It supports
|
||||
// typical set operations: membership testing, intersection, union,
|
||||
// difference, symmetric difference and cloning.
|
||||
//
|
||||
// Package mapset provides two implementations of the Set
|
||||
// interface. The default implementation is safe for concurrent
|
||||
// access, but a non-thread-safe implementation is also provided for
|
||||
// programs that can benefit from the slight speed improvement and
|
||||
// that can enforce mutual exclusion through other means.
|
||||
package mapset
|
||||
|
||||
// Set is the primary interface provided by the mapset package. It
|
||||
// represents an unordered set of data and a large number of
|
||||
// operations that can be applied to that set.
|
||||
type Set[T comparable] interface {
|
||||
// Add adds an element to the set. Returns whether
|
||||
// the item was added.
|
||||
Add(val T) bool
|
||||
|
||||
// Append multiple elements to the set. Returns
|
||||
// the number of elements added.
|
||||
Append(val ...T) int
|
||||
|
||||
// Cardinality returns the number of elements in the set.
|
||||
Cardinality() int
|
||||
|
||||
// Clear removes all elements from the set, leaving
|
||||
// the empty set.
|
||||
Clear()
|
||||
|
||||
// Clone returns a clone of the set using the same
|
||||
// implementation, duplicating all keys.
|
||||
Clone() Set[T]
|
||||
|
||||
// Contains returns whether the given items
|
||||
// are all in the set.
|
||||
Contains(val ...T) bool
|
||||
|
||||
// Difference returns the difference between this set
|
||||
// and other. The returned set will contain
|
||||
// all elements of this set that are not also
|
||||
// elements of other.
|
||||
//
|
||||
// Note that the argument to Difference
|
||||
// must be of the same type as the receiver
|
||||
// of the method. Otherwise, Difference will
|
||||
// panic.
|
||||
Difference(other Set[T]) Set[T]
|
||||
|
||||
// Equal determines if two sets are equal to each
|
||||
// other. If they have the same cardinality
|
||||
// and contain the same elements, they are
|
||||
// considered equal. The order in which
|
||||
// the elements were added is irrelevant.
|
||||
//
|
||||
// Note that the argument to Equal must be
|
||||
// of the same type as the receiver of the
|
||||
// method. Otherwise, Equal will panic.
|
||||
Equal(other Set[T]) bool
|
||||
|
||||
// Intersect returns a new set containing only the elements
|
||||
// that exist only in both sets.
|
||||
//
|
||||
// Note that the argument to Intersect
|
||||
// must be of the same type as the receiver
|
||||
// of the method. Otherwise, Intersect will
|
||||
// panic.
|
||||
Intersect(other Set[T]) Set[T]
|
||||
|
||||
// IsProperSubset determines if every element in this set is in
|
||||
// the other set but the two sets are not equal.
|
||||
//
|
||||
// Note that the argument to IsProperSubset
|
||||
// must be of the same type as the receiver
|
||||
// of the method. Otherwise, IsProperSubset
|
||||
// will panic.
|
||||
IsProperSubset(other Set[T]) bool
|
||||
|
||||
// IsProperSuperset determines if every element in the other set
|
||||
// is in this set but the two sets are not
|
||||
// equal.
|
||||
//
|
||||
// Note that the argument to IsSuperset
|
||||
// must be of the same type as the receiver
|
||||
// of the method. Otherwise, IsSuperset will
|
||||
// panic.
|
||||
IsProperSuperset(other Set[T]) bool
|
||||
|
||||
// IsSubset determines if every element in this set is in
|
||||
// the other set.
|
||||
//
|
||||
// Note that the argument to IsSubset
|
||||
// must be of the same type as the receiver
|
||||
// of the method. Otherwise, IsSubset will
|
||||
// panic.
|
||||
IsSubset(other Set[T]) bool
|
||||
|
||||
// IsSuperset determines if every element in the other set
|
||||
// is in this set.
|
||||
//
|
||||
// Note that the argument to IsSuperset
|
||||
// must be of the same type as the receiver
|
||||
// of the method. Otherwise, IsSuperset will
|
||||
// panic.
|
||||
IsSuperset(other Set[T]) bool
|
||||
|
||||
// Each iterates over elements and executes the passed func against each element.
|
||||
// If passed func returns true, stop iteration at the time.
|
||||
Each(func(T) bool)
|
||||
|
||||
// Iter returns a channel of elements that you can
|
||||
// range over.
|
||||
Iter() <-chan T
|
||||
|
||||
// Iterator returns an Iterator object that you can
|
||||
// use to range over the set.
|
||||
Iterator() *Iterator[T]
|
||||
|
||||
// Remove removes a single element from the set.
|
||||
Remove(i T)
|
||||
|
||||
// RemoveAll removes multiple elements from the set.
|
||||
RemoveAll(i ...T)
|
||||
|
||||
// String provides a convenient string representation
|
||||
// of the current state of the set.
|
||||
String() string
|
||||
|
||||
// SymmetricDifference returns a new set with all elements which are
|
||||
// in either this set or the other set but not in both.
|
||||
//
|
||||
// Note that the argument to SymmetricDifference
|
||||
// must be of the same type as the receiver
|
||||
// of the method. Otherwise, SymmetricDifference
|
||||
// will panic.
|
||||
SymmetricDifference(other Set[T]) Set[T]
|
||||
|
||||
// Union returns a new set with all elements in both sets.
|
||||
//
|
||||
// Note that the argument to Union must be of the
|
||||
// same type as the receiver of the method.
|
||||
// Otherwise, IsSuperset will panic.
|
||||
Union(other Set[T]) Set[T]
|
||||
|
||||
// Pop removes and returns an arbitrary item from the set.
|
||||
Pop() (T, bool)
|
||||
|
||||
// ToSlice returns the members of the set as a slice.
|
||||
ToSlice() []T
|
||||
|
||||
// MarshalJSON will marshal the set into a JSON-based representation.
|
||||
MarshalJSON() ([]byte, error)
|
||||
|
||||
// UnmarshalJSON will unmarshal a JSON-based byte slice into a full Set datastructure.
|
||||
// For this to work, set subtypes must implemented the Marshal/Unmarshal interface.
|
||||
UnmarshalJSON(b []byte) error
|
||||
}
|
||||
|
||||
// NewSet creates and returns a new set with the given elements.
|
||||
// Operations on the resulting set are thread-safe.
|
||||
func NewSet[T comparable](vals ...T) Set[T] {
|
||||
s := newThreadSafeSetWithSize[T](len(vals))
|
||||
for _, item := range vals {
|
||||
s.Add(item)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// NewSetWithSize creates and returns a reference to an empty set with a specified
|
||||
// capacity. Operations on the resulting set are thread-safe.
|
||||
func NewSetWithSize[T comparable](cardinality int) Set[T] {
|
||||
s := newThreadSafeSetWithSize[T](cardinality)
|
||||
return s
|
||||
}
|
||||
|
||||
// NewThreadUnsafeSet creates and returns a new set with the given elements.
|
||||
// Operations on the resulting set are not thread-safe.
|
||||
func NewThreadUnsafeSet[T comparable](vals ...T) Set[T] {
|
||||
s := newThreadUnsafeSetWithSize[T](len(vals))
|
||||
for _, item := range vals {
|
||||
s.Add(item)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// NewThreadUnsafeSetWithSize creates and returns a reference to an empty set with
|
||||
// a specified capacity. Operations on the resulting set are not thread-safe.
|
||||
func NewThreadUnsafeSetWithSize[T comparable](cardinality int) Set[T] {
|
||||
s := newThreadUnsafeSetWithSize[T](cardinality)
|
||||
return s
|
||||
}
|
||||
|
||||
// NewSetFromMapKeys creates and returns a new set with the given keys of the map.
|
||||
// Operations on the resulting set are thread-safe.
|
||||
func NewSetFromMapKeys[T comparable, V any](val map[T]V) Set[T] {
|
||||
s := NewSetWithSize[T](len(val))
|
||||
|
||||
for k := range val {
|
||||
s.Add(k)
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// NewThreadUnsafeSetFromMapKeys creates and returns a new set with the given keys of the map.
|
||||
// Operations on the resulting set are not thread-safe.
|
||||
func NewThreadUnsafeSetFromMapKeys[T comparable, V any](val map[T]V) Set[T] {
|
||||
s := NewThreadUnsafeSetWithSize[T](len(val))
|
||||
|
||||
for k := range val {
|
||||
s.Add(k)
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
279
vendor/github.com/deckarep/golang-set/v2/threadsafe.go
generated
vendored
Normal file
279
vendor/github.com/deckarep/golang-set/v2/threadsafe.go
generated
vendored
Normal file
|
@ -0,0 +1,279 @@
|
|||
/*
|
||||
Open Source Initiative OSI - The MIT License (MIT):Licensing
|
||||
|
||||
The MIT License (MIT)
|
||||
Copyright (c) 2013 - 2022 Ralph Caraveo (deckarep@gmail.com)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
package mapset
|
||||
|
||||
import "sync"
|
||||
|
||||
type threadSafeSet[T comparable] struct {
|
||||
sync.RWMutex
|
||||
uss threadUnsafeSet[T]
|
||||
}
|
||||
|
||||
func newThreadSafeSet[T comparable]() *threadSafeSet[T] {
|
||||
return &threadSafeSet[T]{
|
||||
uss: newThreadUnsafeSet[T](),
|
||||
}
|
||||
}
|
||||
|
||||
func newThreadSafeSetWithSize[T comparable](cardinality int) *threadSafeSet[T] {
|
||||
return &threadSafeSet[T]{
|
||||
uss: newThreadUnsafeSetWithSize[T](cardinality),
|
||||
}
|
||||
}
|
||||
|
||||
func (t *threadSafeSet[T]) Add(v T) bool {
|
||||
t.Lock()
|
||||
ret := t.uss.Add(v)
|
||||
t.Unlock()
|
||||
return ret
|
||||
}
|
||||
|
||||
func (t *threadSafeSet[T]) Append(v ...T) int {
|
||||
t.Lock()
|
||||
ret := t.uss.Append(v...)
|
||||
t.Unlock()
|
||||
return ret
|
||||
}
|
||||
|
||||
func (t *threadSafeSet[T]) Contains(v ...T) bool {
|
||||
t.RLock()
|
||||
ret := t.uss.Contains(v...)
|
||||
t.RUnlock()
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func (t *threadSafeSet[T]) IsSubset(other Set[T]) bool {
|
||||
o := other.(*threadSafeSet[T])
|
||||
|
||||
t.RLock()
|
||||
o.RLock()
|
||||
|
||||
ret := t.uss.IsSubset(o.uss)
|
||||
t.RUnlock()
|
||||
o.RUnlock()
|
||||
return ret
|
||||
}
|
||||
|
||||
func (t *threadSafeSet[T]) IsProperSubset(other Set[T]) bool {
|
||||
o := other.(*threadSafeSet[T])
|
||||
|
||||
t.RLock()
|
||||
defer t.RUnlock()
|
||||
o.RLock()
|
||||
defer o.RUnlock()
|
||||
|
||||
return t.uss.IsProperSubset(o.uss)
|
||||
}
|
||||
|
||||
func (t *threadSafeSet[T]) IsSuperset(other Set[T]) bool {
|
||||
return other.IsSubset(t)
|
||||
}
|
||||
|
||||
func (t *threadSafeSet[T]) IsProperSuperset(other Set[T]) bool {
|
||||
return other.IsProperSubset(t)
|
||||
}
|
||||
|
||||
func (t *threadSafeSet[T]) Union(other Set[T]) Set[T] {
|
||||
o := other.(*threadSafeSet[T])
|
||||
|
||||
t.RLock()
|
||||
o.RLock()
|
||||
|
||||
unsafeUnion := t.uss.Union(o.uss).(threadUnsafeSet[T])
|
||||
ret := &threadSafeSet[T]{uss: unsafeUnion}
|
||||
t.RUnlock()
|
||||
o.RUnlock()
|
||||
return ret
|
||||
}
|
||||
|
||||
func (t *threadSafeSet[T]) Intersect(other Set[T]) Set[T] {
|
||||
o := other.(*threadSafeSet[T])
|
||||
|
||||
t.RLock()
|
||||
o.RLock()
|
||||
|
||||
unsafeIntersection := t.uss.Intersect(o.uss).(threadUnsafeSet[T])
|
||||
ret := &threadSafeSet[T]{uss: unsafeIntersection}
|
||||
t.RUnlock()
|
||||
o.RUnlock()
|
||||
return ret
|
||||
}
|
||||
|
||||
func (t *threadSafeSet[T]) Difference(other Set[T]) Set[T] {
|
||||
o := other.(*threadSafeSet[T])
|
||||
|
||||
t.RLock()
|
||||
o.RLock()
|
||||
|
||||
unsafeDifference := t.uss.Difference(o.uss).(threadUnsafeSet[T])
|
||||
ret := &threadSafeSet[T]{uss: unsafeDifference}
|
||||
t.RUnlock()
|
||||
o.RUnlock()
|
||||
return ret
|
||||
}
|
||||
|
||||
func (t *threadSafeSet[T]) SymmetricDifference(other Set[T]) Set[T] {
|
||||
o := other.(*threadSafeSet[T])
|
||||
|
||||
t.RLock()
|
||||
o.RLock()
|
||||
|
||||
unsafeDifference := t.uss.SymmetricDifference(o.uss).(threadUnsafeSet[T])
|
||||
ret := &threadSafeSet[T]{uss: unsafeDifference}
|
||||
t.RUnlock()
|
||||
o.RUnlock()
|
||||
return ret
|
||||
}
|
||||
|
||||
func (t *threadSafeSet[T]) Clear() {
|
||||
t.Lock()
|
||||
t.uss.Clear()
|
||||
t.Unlock()
|
||||
}
|
||||
|
||||
func (t *threadSafeSet[T]) Remove(v T) {
|
||||
t.Lock()
|
||||
delete(t.uss, v)
|
||||
t.Unlock()
|
||||
}
|
||||
|
||||
func (t *threadSafeSet[T]) RemoveAll(i ...T) {
|
||||
t.Lock()
|
||||
t.uss.RemoveAll(i...)
|
||||
t.Unlock()
|
||||
}
|
||||
|
||||
func (t *threadSafeSet[T]) Cardinality() int {
|
||||
t.RLock()
|
||||
defer t.RUnlock()
|
||||
return len(t.uss)
|
||||
}
|
||||
|
||||
func (t *threadSafeSet[T]) Each(cb func(T) bool) {
|
||||
t.RLock()
|
||||
for elem := range t.uss {
|
||||
if cb(elem) {
|
||||
break
|
||||
}
|
||||
}
|
||||
t.RUnlock()
|
||||
}
|
||||
|
||||
func (t *threadSafeSet[T]) Iter() <-chan T {
|
||||
ch := make(chan T)
|
||||
go func() {
|
||||
t.RLock()
|
||||
|
||||
for elem := range t.uss {
|
||||
ch <- elem
|
||||
}
|
||||
close(ch)
|
||||
t.RUnlock()
|
||||
}()
|
||||
|
||||
return ch
|
||||
}
|
||||
|
||||
func (t *threadSafeSet[T]) Iterator() *Iterator[T] {
|
||||
iterator, ch, stopCh := newIterator[T]()
|
||||
|
||||
go func() {
|
||||
t.RLock()
|
||||
L:
|
||||
for elem := range t.uss {
|
||||
select {
|
||||
case <-stopCh:
|
||||
break L
|
||||
case ch <- elem:
|
||||
}
|
||||
}
|
||||
close(ch)
|
||||
t.RUnlock()
|
||||
}()
|
||||
|
||||
return iterator
|
||||
}
|
||||
|
||||
func (t *threadSafeSet[T]) Equal(other Set[T]) bool {
|
||||
o := other.(*threadSafeSet[T])
|
||||
|
||||
t.RLock()
|
||||
o.RLock()
|
||||
|
||||
ret := t.uss.Equal(o.uss)
|
||||
t.RUnlock()
|
||||
o.RUnlock()
|
||||
return ret
|
||||
}
|
||||
|
||||
func (t *threadSafeSet[T]) Clone() Set[T] {
|
||||
t.RLock()
|
||||
|
||||
unsafeClone := t.uss.Clone().(threadUnsafeSet[T])
|
||||
ret := &threadSafeSet[T]{uss: unsafeClone}
|
||||
t.RUnlock()
|
||||
return ret
|
||||
}
|
||||
|
||||
func (t *threadSafeSet[T]) String() string {
|
||||
t.RLock()
|
||||
ret := t.uss.String()
|
||||
t.RUnlock()
|
||||
return ret
|
||||
}
|
||||
|
||||
func (t *threadSafeSet[T]) Pop() (T, bool) {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
return t.uss.Pop()
|
||||
}
|
||||
|
||||
func (t *threadSafeSet[T]) ToSlice() []T {
|
||||
keys := make([]T, 0, t.Cardinality())
|
||||
t.RLock()
|
||||
for elem := range t.uss {
|
||||
keys = append(keys, elem)
|
||||
}
|
||||
t.RUnlock()
|
||||
return keys
|
||||
}
|
||||
|
||||
func (t *threadSafeSet[T]) MarshalJSON() ([]byte, error) {
|
||||
t.RLock()
|
||||
b, err := t.uss.MarshalJSON()
|
||||
t.RUnlock()
|
||||
|
||||
return b, err
|
||||
}
|
||||
|
||||
func (t *threadSafeSet[T]) UnmarshalJSON(p []byte) error {
|
||||
t.RLock()
|
||||
err := t.uss.UnmarshalJSON(p)
|
||||
t.RUnlock()
|
||||
|
||||
return err
|
||||
}
|
325
vendor/github.com/deckarep/golang-set/v2/threadunsafe.go
generated
vendored
Normal file
325
vendor/github.com/deckarep/golang-set/v2/threadunsafe.go
generated
vendored
Normal file
|
@ -0,0 +1,325 @@
|
|||
/*
|
||||
Open Source Initiative OSI - The MIT License (MIT):Licensing
|
||||
|
||||
The MIT License (MIT)
|
||||
Copyright (c) 2013 - 2022 Ralph Caraveo (deckarep@gmail.com)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
package mapset
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type threadUnsafeSet[T comparable] map[T]struct{}
|
||||
|
||||
// Assert concrete type:threadUnsafeSet adheres to Set interface.
|
||||
var _ Set[string] = (threadUnsafeSet[string])(nil)
|
||||
|
||||
func newThreadUnsafeSet[T comparable]() threadUnsafeSet[T] {
|
||||
return make(threadUnsafeSet[T])
|
||||
}
|
||||
|
||||
func newThreadUnsafeSetWithSize[T comparable](cardinality int) threadUnsafeSet[T] {
|
||||
return make(threadUnsafeSet[T], cardinality)
|
||||
}
|
||||
|
||||
func (s threadUnsafeSet[T]) Add(v T) bool {
|
||||
prevLen := len(s)
|
||||
s[v] = struct{}{}
|
||||
return prevLen != len(s)
|
||||
}
|
||||
|
||||
func (s threadUnsafeSet[T]) Append(v ...T) int {
|
||||
prevLen := len(s)
|
||||
for _, val := range v {
|
||||
(s)[val] = struct{}{}
|
||||
}
|
||||
return len(s) - prevLen
|
||||
}
|
||||
|
||||
// private version of Add which doesn't return a value
|
||||
func (s threadUnsafeSet[T]) add(v T) {
|
||||
s[v] = struct{}{}
|
||||
}
|
||||
|
||||
func (s threadUnsafeSet[T]) Cardinality() int {
|
||||
return len(s)
|
||||
}
|
||||
|
||||
func (s threadUnsafeSet[T]) Clear() {
|
||||
// Constructions like this are optimised by compiler, and replaced by
|
||||
// mapclear() function, defined in
|
||||
// https://github.com/golang/go/blob/29bbca5c2c1ad41b2a9747890d183b6dd3a4ace4/src/runtime/map.go#L993)
|
||||
for key := range s {
|
||||
delete(s, key)
|
||||
}
|
||||
}
|
||||
|
||||
func (s threadUnsafeSet[T]) Clone() Set[T] {
|
||||
clonedSet := newThreadUnsafeSetWithSize[T](s.Cardinality())
|
||||
for elem := range s {
|
||||
clonedSet.add(elem)
|
||||
}
|
||||
return clonedSet
|
||||
}
|
||||
|
||||
func (s threadUnsafeSet[T]) Contains(v ...T) bool {
|
||||
for _, val := range v {
|
||||
if _, ok := s[val]; !ok {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// private version of Contains for a single element v
|
||||
func (s threadUnsafeSet[T]) contains(v T) (ok bool) {
|
||||
_, ok = s[v]
|
||||
return ok
|
||||
}
|
||||
|
||||
func (s threadUnsafeSet[T]) Difference(other Set[T]) Set[T] {
|
||||
o := other.(threadUnsafeSet[T])
|
||||
|
||||
diff := newThreadUnsafeSet[T]()
|
||||
for elem := range s {
|
||||
if !o.contains(elem) {
|
||||
diff.add(elem)
|
||||
}
|
||||
}
|
||||
return diff
|
||||
}
|
||||
|
||||
func (s threadUnsafeSet[T]) Each(cb func(T) bool) {
|
||||
for elem := range s {
|
||||
if cb(elem) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s threadUnsafeSet[T]) Equal(other Set[T]) bool {
|
||||
o := other.(threadUnsafeSet[T])
|
||||
|
||||
if s.Cardinality() != other.Cardinality() {
|
||||
return false
|
||||
}
|
||||
for elem := range s {
|
||||
if !o.contains(elem) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (s threadUnsafeSet[T]) Intersect(other Set[T]) Set[T] {
|
||||
o := other.(threadUnsafeSet[T])
|
||||
|
||||
intersection := newThreadUnsafeSet[T]()
|
||||
// loop over smaller set
|
||||
if s.Cardinality() < other.Cardinality() {
|
||||
for elem := range s {
|
||||
if o.contains(elem) {
|
||||
intersection.add(elem)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for elem := range o {
|
||||
if s.contains(elem) {
|
||||
intersection.add(elem)
|
||||
}
|
||||
}
|
||||
}
|
||||
return intersection
|
||||
}
|
||||
|
||||
func (s threadUnsafeSet[T]) IsProperSubset(other Set[T]) bool {
|
||||
return s.Cardinality() < other.Cardinality() && s.IsSubset(other)
|
||||
}
|
||||
|
||||
func (s threadUnsafeSet[T]) IsProperSuperset(other Set[T]) bool {
|
||||
return s.Cardinality() > other.Cardinality() && s.IsSuperset(other)
|
||||
}
|
||||
|
||||
func (s threadUnsafeSet[T]) IsSubset(other Set[T]) bool {
|
||||
o := other.(threadUnsafeSet[T])
|
||||
if s.Cardinality() > other.Cardinality() {
|
||||
return false
|
||||
}
|
||||
for elem := range s {
|
||||
if !o.contains(elem) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (s threadUnsafeSet[T]) IsSuperset(other Set[T]) bool {
|
||||
return other.IsSubset(s)
|
||||
}
|
||||
|
||||
func (s threadUnsafeSet[T]) Iter() <-chan T {
|
||||
ch := make(chan T)
|
||||
go func() {
|
||||
for elem := range s {
|
||||
ch <- elem
|
||||
}
|
||||
close(ch)
|
||||
}()
|
||||
|
||||
return ch
|
||||
}
|
||||
|
||||
func (s threadUnsafeSet[T]) Iterator() *Iterator[T] {
|
||||
iterator, ch, stopCh := newIterator[T]()
|
||||
|
||||
go func() {
|
||||
L:
|
||||
for elem := range s {
|
||||
select {
|
||||
case <-stopCh:
|
||||
break L
|
||||
case ch <- elem:
|
||||
}
|
||||
}
|
||||
close(ch)
|
||||
}()
|
||||
|
||||
return iterator
|
||||
}
|
||||
|
||||
// Pop returns a popped item in case set is not empty, or nil-value of T
|
||||
// if set is already empty
|
||||
func (s threadUnsafeSet[T]) Pop() (v T, ok bool) {
|
||||
for item := range s {
|
||||
delete(s, item)
|
||||
return item, true
|
||||
}
|
||||
return v, false
|
||||
}
|
||||
|
||||
func (s threadUnsafeSet[T]) Remove(v T) {
|
||||
delete(s, v)
|
||||
}
|
||||
|
||||
func (s threadUnsafeSet[T]) RemoveAll(i ...T) {
|
||||
for _, elem := range i {
|
||||
delete(s, elem)
|
||||
}
|
||||
}
|
||||
|
||||
func (s threadUnsafeSet[T]) String() string {
|
||||
items := make([]string, 0, len(s))
|
||||
|
||||
for elem := range s {
|
||||
items = append(items, fmt.Sprintf("%v", elem))
|
||||
}
|
||||
return fmt.Sprintf("Set{%s}", strings.Join(items, ", "))
|
||||
}
|
||||
|
||||
func (s threadUnsafeSet[T]) SymmetricDifference(other Set[T]) Set[T] {
|
||||
o := other.(threadUnsafeSet[T])
|
||||
|
||||
sd := newThreadUnsafeSet[T]()
|
||||
for elem := range s {
|
||||
if !o.contains(elem) {
|
||||
sd.add(elem)
|
||||
}
|
||||
}
|
||||
for elem := range o {
|
||||
if !s.contains(elem) {
|
||||
sd.add(elem)
|
||||
}
|
||||
}
|
||||
return sd
|
||||
}
|
||||
|
||||
func (s threadUnsafeSet[T]) ToSlice() []T {
|
||||
keys := make([]T, 0, s.Cardinality())
|
||||
for elem := range s {
|
||||
keys = append(keys, elem)
|
||||
}
|
||||
|
||||
return keys
|
||||
}
|
||||
|
||||
func (s threadUnsafeSet[T]) Union(other Set[T]) Set[T] {
|
||||
o := other.(threadUnsafeSet[T])
|
||||
|
||||
n := s.Cardinality()
|
||||
if o.Cardinality() > n {
|
||||
n = o.Cardinality()
|
||||
}
|
||||
unionedSet := make(threadUnsafeSet[T], n)
|
||||
|
||||
for elem := range s {
|
||||
unionedSet.add(elem)
|
||||
}
|
||||
for elem := range o {
|
||||
unionedSet.add(elem)
|
||||
}
|
||||
return unionedSet
|
||||
}
|
||||
|
||||
// MarshalJSON creates a JSON array from the set, it marshals all elements
|
||||
func (s threadUnsafeSet[T]) MarshalJSON() ([]byte, error) {
|
||||
items := make([]string, 0, s.Cardinality())
|
||||
|
||||
for elem := range s {
|
||||
b, err := json.Marshal(elem)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
items = append(items, string(b))
|
||||
}
|
||||
|
||||
return []byte(fmt.Sprintf("[%s]", strings.Join(items, ","))), nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON recreates a set from a JSON array, it only decodes
|
||||
// primitive types. Numbers are decoded as json.Number.
|
||||
func (s threadUnsafeSet[T]) UnmarshalJSON(b []byte) error {
|
||||
var i []any
|
||||
|
||||
d := json.NewDecoder(bytes.NewReader(b))
|
||||
d.UseNumber()
|
||||
err := d.Decode(&i)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, v := range i {
|
||||
switch t := v.(type) {
|
||||
case T:
|
||||
s.add(t)
|
||||
default:
|
||||
// anything else must be skipped.
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
6
vendor/modules.txt
vendored
6
vendor/modules.txt
vendored
|
@ -355,9 +355,9 @@ github.com/creack/pty
|
|||
# github.com/cyphar/filepath-securejoin v0.2.3
|
||||
## explicit; go 1.13
|
||||
github.com/cyphar/filepath-securejoin
|
||||
# github.com/deckarep/golang-set v0.0.0-20141123011944-ef32fa3046d9
|
||||
## explicit
|
||||
github.com/deckarep/golang-set
|
||||
# github.com/deckarep/golang-set/v2 v2.3.0
|
||||
## explicit; go 1.18
|
||||
github.com/deckarep/golang-set/v2
|
||||
# github.com/dimchansky/utfbom v1.1.1
|
||||
## explicit
|
||||
github.com/dimchansky/utfbom
|
||||
|
|
Loading…
Reference in a new issue