parent
bda5c8a176
commit
8f245f20e4
@ -1,22 +0,0 @@
|
|||||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
|
||||||
*.o
|
|
||||||
*.a
|
|
||||||
*.so
|
|
||||||
|
|
||||||
# Folders
|
|
||||||
_obj
|
|
||||||
_test
|
|
||||||
|
|
||||||
# Architecture specific extensions/prefixes
|
|
||||||
*.[568vq]
|
|
||||||
[568vq].out
|
|
||||||
|
|
||||||
*.cgo1.go
|
|
||||||
*.cgo2.c
|
|
||||||
_cgo_defun.c
|
|
||||||
_cgo_gotypes.go
|
|
||||||
_cgo_export.*
|
|
||||||
|
|
||||||
_testmain.go
|
|
||||||
|
|
||||||
*.exe
|
|
@ -1,22 +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.
|
|
@ -1,95 +0,0 @@
|
|||||||
[![Build Status](https://travis-ci.org/deckarep/golang-set.svg?branch=master)](https://travis-ci.org/deckarep/golang-set)
|
|
||||||
[![Go Report Card](https://goreportcard.com/badge/github.com/deckarep/golang-set)](https://goreportcard.com/report/github.com/deckarep/golang-set)
|
|
||||||
[![GoDoc](https://godoc.org/github.com/deckarep/golang-set?status.svg)](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 CartesianProduct() method has been added with unit-tests: [Read more about the cartesian 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)
|
|
@ -1,58 +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
|
|
||||||
|
|
||||||
// Iterator defines an iterator over a Set, its C channel can be used to range over the Set's
|
|
||||||
// elements.
|
|
||||||
type Iterator struct {
|
|
||||||
C <-chan interface{}
|
|
||||||
stop chan struct{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stop stops the Iterator, no further elements will be received on C, C will be closed.
|
|
||||||
func (i *Iterator) 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() (*Iterator, chan<- interface{}, <-chan struct{}) {
|
|
||||||
itemChan := make(chan interface{})
|
|
||||||
stopChan := make(chan struct{})
|
|
||||||
return &Iterator{
|
|
||||||
C: itemChan,
|
|
||||||
stop: stopChan,
|
|
||||||
}, itemChan, stopChan
|
|
||||||
}
|
|
@ -1,217 +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 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 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 empty 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 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) bool
|
|
||||||
|
|
||||||
// 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) bool
|
|
||||||
|
|
||||||
// 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) bool
|
|
||||||
|
|
||||||
// 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) bool
|
|
||||||
|
|
||||||
// Iterates over elements and executes the passed func against each element.
|
|
||||||
// If passed func returns true, stop iteration at the time.
|
|
||||||
Each(func(interface{}) bool)
|
|
||||||
|
|
||||||
// Returns a channel of elements that you can
|
|
||||||
// range over.
|
|
||||||
Iter() <-chan interface{}
|
|
||||||
|
|
||||||
// Returns an Iterator object that you can
|
|
||||||
// use to range over the set.
|
|
||||||
Iterator() *Iterator
|
|
||||||
|
|
||||||
// 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
|
|
||||||
|
|
||||||
// Pop removes and returns an arbitrary item from the set.
|
|
||||||
Pop() interface{}
|
|
||||||
|
|
||||||
// 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{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewSet creates and returns a reference to an empty set. Operations
|
|
||||||
// on the resulting set are thread-safe.
|
|
||||||
func NewSet(s ...interface{}) Set {
|
|
||||||
set := newThreadSafeSet()
|
|
||||||
for _, item := range s {
|
|
||||||
set.Add(item)
|
|
||||||
}
|
|
||||||
return &set
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewSetWith creates and returns a new set with the given elements.
|
|
||||||
// Operations on the resulting set are thread-safe.
|
|
||||||
func NewSetWith(elts ...interface{}) Set {
|
|
||||||
return NewSetFromSlice(elts)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewSetFromSlice creates and returns a reference to a set from an
|
|
||||||
// existing slice. Operations on the resulting set are thread-safe.
|
|
||||||
func NewSetFromSlice(s []interface{}) Set {
|
|
||||||
a := NewSet(s...)
|
|
||||||
return a
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewThreadUnsafeSet creates and returns a reference to an empty set.
|
|
||||||
// Operations on the resulting set are not thread-safe.
|
|
||||||
func NewThreadUnsafeSet() Set {
|
|
||||||
set := newThreadUnsafeSet()
|
|
||||||
return &set
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewThreadUnsafeSetFromSlice creates and returns a reference to a
|
|
||||||
// set from an existing slice. Operations on the resulting set are
|
|
||||||
// not thread-safe.
|
|
||||||
func NewThreadUnsafeSetFromSlice(s []interface{}) Set {
|
|
||||||
a := NewThreadUnsafeSet()
|
|
||||||
for _, item := range s {
|
|
||||||
a.Add(item)
|
|
||||||
}
|
|
||||||
return a
|
|
||||||
}
|
|
@ -1,283 +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) IsProperSubset(other Set) bool {
|
|
||||||
o := other.(*threadSafeSet)
|
|
||||||
|
|
||||||
set.RLock()
|
|
||||||
defer set.RUnlock()
|
|
||||||
o.RLock()
|
|
||||||
defer o.RUnlock()
|
|
||||||
|
|
||||||
return set.s.IsProperSubset(&o.s)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (set *threadSafeSet) IsSuperset(other Set) bool {
|
|
||||||
return other.IsSubset(set)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (set *threadSafeSet) IsProperSuperset(other Set) bool {
|
|
||||||
return other.IsProperSubset(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)
|
|
||||||
|
|
||||||
set.RLock()
|
|
||||||
o.RLock()
|
|
||||||
|
|
||||||
unsafeDifference := set.s.SymmetricDifference(&o.s).(*threadUnsafeSet)
|
|
||||||
ret := &threadSafeSet{s: *unsafeDifference}
|
|
||||||
set.RUnlock()
|
|
||||||
o.RUnlock()
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
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) Each(cb func(interface{}) bool) {
|
|
||||||
set.RLock()
|
|
||||||
for elem := range set.s {
|
|
||||||
if cb(elem) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
set.RUnlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
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) Iterator() *Iterator {
|
|
||||||
iterator, ch, stopCh := newIterator()
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
set.RLock()
|
|
||||||
L:
|
|
||||||
for elem := range set.s {
|
|
||||||
select {
|
|
||||||
case <-stopCh:
|
|
||||||
break L
|
|
||||||
case ch <- elem:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
close(ch)
|
|
||||||
set.RUnlock()
|
|
||||||
}()
|
|
||||||
|
|
||||||
return iterator
|
|
||||||
}
|
|
||||||
|
|
||||||
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()
|
|
||||||
unsafePowerSet := set.s.PowerSet().(*threadUnsafeSet)
|
|
||||||
set.RUnlock()
|
|
||||||
|
|
||||||
ret := &threadSafeSet{s: newThreadUnsafeSet()}
|
|
||||||
for subset := range unsafePowerSet.Iter() {
|
|
||||||
unsafeSubset := subset.(*threadUnsafeSet)
|
|
||||||
ret.Add(&threadSafeSet{s: *unsafeSubset})
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
func (set *threadSafeSet) Pop() interface{} {
|
|
||||||
set.Lock()
|
|
||||||
defer set.Unlock()
|
|
||||||
return set.s.Pop()
|
|
||||||
}
|
|
||||||
|
|
||||||
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{} {
|
|
||||||
keys := make([]interface{}, 0, set.Cardinality())
|
|
||||||
set.RLock()
|
|
||||||
for elem := range set.s {
|
|
||||||
keys = append(keys, elem)
|
|
||||||
}
|
|
||||||
set.RUnlock()
|
|
||||||
return keys
|
|
||||||
}
|
|
||||||
|
|
||||||
func (set *threadSafeSet) MarshalJSON() ([]byte, error) {
|
|
||||||
set.RLock()
|
|
||||||
b, err := set.s.MarshalJSON()
|
|
||||||
set.RUnlock()
|
|
||||||
|
|
||||||
return b, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (set *threadSafeSet) UnmarshalJSON(p []byte) error {
|
|
||||||
set.RLock()
|
|
||||||
err := set.s.UnmarshalJSON(p)
|
|
||||||
set.RUnlock()
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
@ -1,340 +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 (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
type threadUnsafeSet map[interface{}]struct{}
|
|
||||||
|
|
||||||
// An OrderedPair represents a 2-tuple of values.
|
|
||||||
type OrderedPair struct {
|
|
||||||
First interface{}
|
|
||||||
Second interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func newThreadUnsafeSet() threadUnsafeSet {
|
|
||||||
return make(threadUnsafeSet)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Equal says whether two 2-tuples contain the same values in the same order.
|
|
||||||
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]
|
|
||||||
if found {
|
|
||||||
return false //False if it existed already
|
|
||||||
}
|
|
||||||
|
|
||||||
(*set)[i] = struct{}{}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
if set.Cardinality() > other.Cardinality() {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
for elem := range *set {
|
|
||||||
if !other.Contains(elem) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (set *threadUnsafeSet) IsProperSubset(other Set) bool {
|
|
||||||
return set.IsSubset(other) && !set.Equal(other)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (set *threadUnsafeSet) IsSuperset(other Set) bool {
|
|
||||||
return other.IsSubset(set)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (set *threadUnsafeSet) IsProperSuperset(other Set) bool {
|
|
||||||
return set.IsSuperset(other) && !set.Equal(other)
|
|
||||||
}
|
|
||||||
|
|
||||||
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) Each(cb func(interface{}) bool) {
|
|
||||||
for elem := range *set {
|
|
||||||
if cb(elem) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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) Iterator() *Iterator {
|
|
||||||
iterator, ch, stopCh := newIterator()
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
L:
|
|
||||||
for elem := range *set {
|
|
||||||
select {
|
|
||||||
case <-stopCh:
|
|
||||||
break L
|
|
||||||
case ch <- elem:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
close(ch)
|
|
||||||
}()
|
|
||||||
|
|
||||||
return iterator
|
|
||||||
}
|
|
||||||
|
|
||||||
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, ", "))
|
|
||||||
}
|
|
||||||
|
|
||||||
// String outputs a 2-tuple in the form "(A, B)".
|
|
||||||
func (pair OrderedPair) String() string {
|
|
||||||
return fmt.Sprintf("(%v, %v)", pair.First, pair.Second)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (set *threadUnsafeSet) Pop() interface{} {
|
|
||||||
for item := range *set {
|
|
||||||
delete(*set, item)
|
|
||||||
return item
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalJSON creates a JSON array from the set, it marshals all elements
|
|
||||||
func (set *threadUnsafeSet) MarshalJSON() ([]byte, error) {
|
|
||||||
items := make([]string, 0, set.Cardinality())
|
|
||||||
|
|
||||||
for elem := range *set {
|
|
||||||
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 (set *threadUnsafeSet) UnmarshalJSON(b []byte) error {
|
|
||||||
var i []interface{}
|
|
||||||
|
|
||||||
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 []interface{}, map[string]interface{}:
|
|
||||||
continue
|
|
||||||
default:
|
|
||||||
set.Add(t)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -0,0 +1,371 @@
|
|||||||
|
// Package v4 implements signing for AWS V4 signer
|
||||||
|
package v4
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/hmac"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ks3sdklib/aws-sdk-go/aws/credentials"
|
||||||
|
"github.com/ks3sdklib/aws-sdk-go/internal/protocol/rest"
|
||||||
|
|
||||||
|
"github.com/ks3sdklib/aws-sdk-go/aws"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
authHeaderPrefix = "AWS4-HMAC-SHA256"
|
||||||
|
timeFormat = "20060102T150405Z"
|
||||||
|
shortTimeFormat = "20060102"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ignoredHeaders = map[string]bool{
|
||||||
|
"Authorization": true,
|
||||||
|
"Content-Type": true,
|
||||||
|
"Content-Length": true,
|
||||||
|
"User-Agent": true,
|
||||||
|
}
|
||||||
|
|
||||||
|
type signer struct {
|
||||||
|
Request *http.Request
|
||||||
|
Time time.Time
|
||||||
|
ExpireTime time.Duration
|
||||||
|
ServiceName string
|
||||||
|
Region string
|
||||||
|
CredValues credentials.Value
|
||||||
|
Credentials *credentials.Credentials
|
||||||
|
Query url.Values
|
||||||
|
Body io.ReadSeeker
|
||||||
|
Debug uint
|
||||||
|
Logger io.Writer
|
||||||
|
|
||||||
|
isPresign bool
|
||||||
|
isSignBody bool
|
||||||
|
formattedTime string
|
||||||
|
formattedShortTime string
|
||||||
|
|
||||||
|
signedHeaders string
|
||||||
|
canonicalHeaders string
|
||||||
|
canonicalString string
|
||||||
|
credentialString string
|
||||||
|
stringToSign string
|
||||||
|
signature string
|
||||||
|
authorization string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sign requests with signature version 4.
|
||||||
|
//
|
||||||
|
// Will sign the requests with the service config's Credentials object
|
||||||
|
// Signing is skipped if the credentials is the credentials.AnonymousCredentials
|
||||||
|
// object.
|
||||||
|
func Sign(req *aws.Request) {
|
||||||
|
// If the request does not need to be signed ignore the signing of the
|
||||||
|
// request if the AnonymousCredentials object is used.
|
||||||
|
if req.Service.Config.Credentials == credentials.AnonymousCredentials {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
region := req.Service.SigningRegion
|
||||||
|
if region == "" {
|
||||||
|
region = req.Service.Config.Region
|
||||||
|
}
|
||||||
|
|
||||||
|
name := req.Service.SigningName
|
||||||
|
if name == "" {
|
||||||
|
name = req.Service.ServiceName
|
||||||
|
}
|
||||||
|
|
||||||
|
s := signer{
|
||||||
|
Request: req.HTTPRequest,
|
||||||
|
Time: req.Time,
|
||||||
|
ExpireTime: req.ExpireTime,
|
||||||
|
Query: req.HTTPRequest.URL.Query(),
|
||||||
|
Body: req.Body,
|
||||||
|
ServiceName: name,
|
||||||
|
Region: region,
|
||||||
|
Credentials: req.Service.Config.Credentials,
|
||||||
|
Debug: req.Service.Config.LogLevel,
|
||||||
|
Logger: req.Service.Config.Logger,
|
||||||
|
}
|
||||||
|
if req.Service.Config.SignerVersion == "V4_UNSIGNED_PAYLOAD_SIGNER" {
|
||||||
|
s.isSignBody = false
|
||||||
|
} else {
|
||||||
|
s.isSignBody = true
|
||||||
|
}
|
||||||
|
req.Error = s.sign()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v4 *signer) sign() error {
|
||||||
|
if v4.ExpireTime != 0 {
|
||||||
|
v4.isPresign = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if v4.isRequestSigned() {
|
||||||
|
if !v4.Credentials.IsExpired() {
|
||||||
|
// If the request is already signed, and the credentials have not
|
||||||
|
// expired yet ignore the signing request.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// The credentials have expired for this request. The current signing
|
||||||
|
// is invalid, and needs to be request because the request will fail.
|
||||||
|
if v4.isPresign {
|
||||||
|
v4.removePresign()
|
||||||
|
// Update the request's query string to ensure the values stays in
|
||||||
|
// sync in the case retrieving the new credentials fails.
|
||||||
|
v4.Request.URL.RawQuery = v4.Query.Encode()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
v4.CredValues, err = v4.Credentials.Get()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if v4.isPresign {
|
||||||
|
v4.Query.Set("X-Amz-Algorithm", authHeaderPrefix)
|
||||||
|
if v4.CredValues.SessionToken != "" {
|
||||||
|
v4.Query.Set("X-Amz-Security-Token", v4.CredValues.SessionToken)
|
||||||
|
} else {
|
||||||
|
v4.Query.Del("X-Amz-Security-Token")
|
||||||
|
}
|
||||||
|
} else if v4.CredValues.SessionToken != "" {
|
||||||
|
v4.Request.Header.Set("X-Amz-Security-Token", v4.CredValues.SessionToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
v4.build()
|
||||||
|
|
||||||
|
if v4.Debug > 0 {
|
||||||
|
v4.logSigningInfo()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v4 *signer) logSigningInfo() {
|
||||||
|
out := v4.Logger
|
||||||
|
fmt.Fprintf(out, "---[ CANONICAL STRING ]-----------------------------\n")
|
||||||
|
fmt.Fprintln(out, v4.canonicalString)
|
||||||
|
fmt.Fprintf(out, "---[ STRING TO SIGN ]--------------------------------\n")
|
||||||
|
fmt.Fprintln(out, v4.stringToSign)
|
||||||
|
if v4.isPresign {
|
||||||
|
fmt.Fprintf(out, "---[ SIGNED URL ]--------------------------------\n")
|
||||||
|
fmt.Fprintln(out, v4.Request.URL)
|
||||||
|
}
|
||||||
|
fmt.Fprintf(out, "-----------------------------------------------------\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v4 *signer) build() {
|
||||||
|
|
||||||
|
v4.buildTime() // no depends
|
||||||
|
v4.buildCredentialString() // no depends
|
||||||
|
if v4.isPresign {
|
||||||
|
v4.buildQuery() // no depends
|
||||||
|
}
|
||||||
|
v4.buildCanonicalHeaders() // depends on cred string
|
||||||
|
v4.buildCanonicalString() // depends on canon headers / signed headers
|
||||||
|
v4.buildStringToSign() // depends on canon string
|
||||||
|
v4.buildSignature() // depends on string to sign
|
||||||
|
|
||||||
|
if v4.isPresign {
|
||||||
|
v4.Request.URL.RawQuery += "&X-Amz-Signature=" + v4.signature
|
||||||
|
} else {
|
||||||
|
parts := []string{
|
||||||
|
authHeaderPrefix + " Credential=" + v4.CredValues.AccessKeyID + "/" + v4.credentialString,
|
||||||
|
"SignedHeaders=" + v4.signedHeaders,
|
||||||
|
"Signature=" + v4.signature,
|
||||||
|
}
|
||||||
|
v4.Request.Header.Set("Authorization", strings.Join(parts, ", "))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v4 *signer) buildTime() {
|
||||||
|
v4.formattedTime = v4.Time.UTC().Format(timeFormat)
|
||||||
|
v4.formattedShortTime = v4.Time.UTC().Format(shortTimeFormat)
|
||||||
|
|
||||||
|
if v4.isPresign {
|
||||||
|
duration := int64(v4.ExpireTime / time.Second)
|
||||||
|
v4.Query.Set("X-Amz-Date", v4.formattedTime)
|
||||||
|
v4.Query.Set("X-Amz-Expires", strconv.FormatInt(duration, 10))
|
||||||
|
} else {
|
||||||
|
v4.Request.Header.Set("X-Amz-Date", v4.formattedTime)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v4 *signer) buildCredentialString() {
|
||||||
|
v4.credentialString = strings.Join([]string{
|
||||||
|
v4.formattedShortTime,
|
||||||
|
v4.Region,
|
||||||
|
v4.ServiceName,
|
||||||
|
"aws4_request",
|
||||||
|
}, "/")
|
||||||
|
|
||||||
|
if v4.isPresign {
|
||||||
|
v4.Query.Set("X-Amz-Credential", v4.CredValues.AccessKeyID+"/"+v4.credentialString)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v4 *signer) buildQuery() {
|
||||||
|
for k, h := range v4.Request.Header {
|
||||||
|
if strings.HasPrefix(http.CanonicalHeaderKey(k), "X-Amz-") {
|
||||||
|
continue // never hoist x-amz-* headers, they must be signed
|
||||||
|
}
|
||||||
|
if _, ok := ignoredHeaders[http.CanonicalHeaderKey(k)]; ok {
|
||||||
|
continue // never hoist ignored headers
|
||||||
|
}
|
||||||
|
|
||||||
|
v4.Request.Header.Del(k)
|
||||||
|
v4.Query.Del(k)
|
||||||
|
for _, v := range h {
|
||||||
|
v4.Query.Add(k, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v4 *signer) buildCanonicalHeaders() {
|
||||||
|
var headers []string
|
||||||
|
headers = append(headers, "host")
|
||||||
|
for k := range v4.Request.Header {
|
||||||
|
if _, ok := ignoredHeaders[http.CanonicalHeaderKey(k)]; ok {
|
||||||
|
continue // ignored header
|
||||||
|
}
|
||||||
|
headers = append(headers, strings.ToLower(k))
|
||||||
|
}
|
||||||
|
sort.Strings(headers)
|
||||||
|
|
||||||
|
v4.signedHeaders = strings.Join(headers, ";")
|
||||||
|
|
||||||
|
if v4.isPresign {
|
||||||
|
v4.Query.Set("X-Amz-SignedHeaders", v4.signedHeaders)
|
||||||
|
}
|
||||||
|
|
||||||
|
headerValues := make([]string, len(headers))
|
||||||
|
for i, k := range headers {
|
||||||
|
if k == "host" {
|
||||||
|
headerValues[i] = "host:" + v4.Request.URL.Host
|
||||||
|
} else {
|
||||||
|
headerValues[i] = k + ":" +
|
||||||
|
strings.Join(v4.Request.Header[http.CanonicalHeaderKey(k)], ",")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
v4.canonicalHeaders = strings.Join(headerValues, "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v4 *signer) buildCanonicalString() {
|
||||||
|
v4.Request.URL.RawQuery = strings.Replace(v4.Query.Encode(), "+", "%20", -1)
|
||||||
|
uri := v4.Request.URL.Opaque
|
||||||
|
if uri != "" {
|
||||||
|
uri = "/" + strings.Join(strings.Split(uri, "/")[3:], "/")
|
||||||
|
} else {
|
||||||
|
uri = v4.Request.URL.Path
|
||||||
|
}
|
||||||
|
if uri == "" {
|
||||||
|
uri = "/"
|
||||||
|
}
|
||||||
|
|
||||||
|
if v4.ServiceName != "s3" {
|
||||||
|
uri = rest.EscapePath(uri, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
v4.canonicalString = strings.Join([]string{
|
||||||
|
v4.Request.Method,
|
||||||
|
uri,
|
||||||
|
v4.Request.URL.RawQuery,
|
||||||
|
v4.canonicalHeaders + "\n",
|
||||||
|
v4.signedHeaders,
|
||||||
|
v4.bodyDigest(),
|
||||||
|
}, "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v4 *signer) buildStringToSign() {
|
||||||
|
v4.stringToSign = strings.Join([]string{
|
||||||
|
authHeaderPrefix,
|
||||||
|
v4.formattedTime,
|
||||||
|
v4.credentialString,
|
||||||
|
hex.EncodeToString(makeSha256([]byte(v4.canonicalString))),
|
||||||
|
}, "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v4 *signer) buildSignature() {
|
||||||
|
secret := v4.CredValues.SecretAccessKey
|
||||||
|
date := makeHmac([]byte("AWS4"+secret), []byte(v4.formattedShortTime))
|
||||||
|
region := makeHmac(date, []byte(v4.Region))
|
||||||
|
service := makeHmac(region, []byte(v4.ServiceName))
|
||||||
|
credentials := makeHmac(service, []byte("aws4_request"))
|
||||||
|
signature := makeHmac(credentials, []byte(v4.stringToSign))
|
||||||
|
v4.signature = hex.EncodeToString(signature)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v4 *signer) bodyDigest() string {
|
||||||
|
hash := v4.Request.Header.Get("X-Amz-Content-Sha256")
|
||||||
|
if hash == "" {
|
||||||
|
if v4.isPresign && v4.ServiceName == "s3" {
|
||||||
|
hash = "UNSIGNED-PAYLOAD"
|
||||||
|
} else {
|
||||||
|
if v4.isSignBody {
|
||||||
|
if v4.Body == nil {
|
||||||
|
hash = hex.EncodeToString(makeSha256([]byte{}))
|
||||||
|
} else {
|
||||||
|
hash = hex.EncodeToString(makeSha256Reader(v4.Body))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
hash = "UNSIGNED-PAYLOAD"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
v4.Request.Header.Add("X-Amz-Content-Sha256", hash)
|
||||||
|
}
|
||||||
|
return hash
|
||||||
|
}
|
||||||
|
|
||||||
|
// isRequestSigned returns if the request is currently signed or presigned
|
||||||
|
func (v4 *signer) isRequestSigned() bool {
|
||||||
|
if v4.isPresign && v4.Query.Get("X-Amz-Signature") != "" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if v4.Request.Header.Get("Authorization") != "" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// unsign removes signing flags for both signed and presigned requests.
|
||||||
|
func (v4 *signer) removePresign() {
|
||||||
|
v4.Query.Del("X-Amz-Algorithm")
|
||||||
|
v4.Query.Del("X-Amz-Signature")
|
||||||
|
v4.Query.Del("X-Amz-Security-Token")
|
||||||
|
v4.Query.Del("X-Amz-Date")
|
||||||
|
v4.Query.Del("X-Amz-Expires")
|
||||||
|
v4.Query.Del("X-Amz-Credential")
|
||||||
|
v4.Query.Del("X-Amz-SignedHeaders")
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeHmac(key []byte, data []byte) []byte {
|
||||||
|
hash := hmac.New(sha256.New, key)
|
||||||
|
hash.Write(data)
|
||||||
|
return hash.Sum(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeSha256(data []byte) []byte {
|
||||||
|
hash := sha256.New()
|
||||||
|
hash.Write(data)
|
||||||
|
return hash.Sum(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeSha256Reader(reader io.ReadSeeker) []byte {
|
||||||
|
hash := sha256.New()
|
||||||
|
start, _ := reader.Seek(0, 1)
|
||||||
|
defer reader.Seek(start, 0)
|
||||||
|
|
||||||
|
io.Copy(hash, reader)
|
||||||
|
return hash.Sum(nil)
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
// Copyright 2022 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build !go1.20
|
||||||
|
|
||||||
|
package sasl
|
||||||
|
|
||||||
|
// TODO: remove all the specialized XOR code and use "crypto/subtle".XORBytes
|
||||||
|
// when Go v1.21 comes out. For more information see:
|
||||||
|
// https://mellium.im/issue/338
|
||||||
|
|
||||||
|
func goXORBytes(dst, x, y []byte) int {
|
||||||
|
n := len(x)
|
||||||
|
if len(y) < n {
|
||||||
|
n = len(y)
|
||||||
|
}
|
||||||
|
if n == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
if n > len(dst) {
|
||||||
|
panic("subtle.XORBytes: dst too short")
|
||||||
|
}
|
||||||
|
xorBytes(&dst[0], &x[0], &y[0], n) // arch-specific
|
||||||
|
return n
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
// Copyright 2022 The Mellium Contributors.
|
||||||
|
// Use of this source code is governed by the BSD 2-clause
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build go1.20
|
||||||
|
|
||||||
|
package sasl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/subtle"
|
||||||
|
)
|
||||||
|
|
||||||
|
func goXORBytes(dst, x, y []byte) int {
|
||||||
|
return subtle.XORBytes(dst, x, y)
|
||||||
|
}
|
@ -1,10 +0,0 @@
|
|||||||
// Copyright 2022 The Mellium Contributors.
|
|
||||||
// Use of this source code is governed by the BSD 2-clause
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package sasl
|
|
||||||
|
|
||||||
// Use the xorWords function to avoid the linter complaining about it being
|
|
||||||
// unused. We import these files from Go and don't want to have to go through
|
|
||||||
// and remove this function every single time.
|
|
||||||
var _ = xorWords
|
|
Loading…
Reference in new issue