├── LICENSE ├── README.md ├── apply.go ├── apply_test.go ├── bench_test.go ├── doc.go ├── example_test.go ├── helpers.go ├── internal ├── mapset │ ├── bench_test.go │ ├── mapset.go │ └── mapset_test.go ├── sliceset │ └── sliceset.go └── testdata │ ├── data.go │ ├── funcs.go │ ├── seq.go │ ├── seq_test.go │ └── uniq.go ├── mutators.go ├── primitives.go ├── readonly.go ├── set_test.go └── types.go /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Kevin Gillette. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 15 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 16 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 17 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 18 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 19 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 20 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 21 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 22 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # set [![GoDoc](https://godoc.org/github.com/xtgo/set?status.png)](https://godoc.org/github.com/xtgo/set) [![Coverage Status](https://coveralls.io/repos/xtgo/set/badge.svg?branch=master&service=github)](https://coveralls.io/github/xtgo/set?branch=master) 2 | 3 | Package set provides type-safe, polymorphic mathematical set operations that operate on any sort.Interface implementation. 4 | 5 | Documentation: http://godoc.org/github.com/xtgo/set 6 | 7 | Talk: https://go-talks.appspot.com/github.com/xtblog/gotalks/sets.slide 8 | -------------------------------------------------------------------------------- /apply.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Kevin Gillette. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package set 6 | 7 | import "sort" 8 | 9 | // Pivots transforms set-relative sizes into data-absolute pivots. Pivots is 10 | // mostly only useful in conjunction with Apply. The sizes slice sizes may 11 | // be modified by the call. 12 | func Pivots(sizes ...int) []int { 13 | n := 0 14 | for i, l := range sizes { 15 | n += l 16 | sizes[i] = n 17 | } 18 | return sizes 19 | } 20 | 21 | // Apply concurrently applies op to all the sets terminated by pivots. 22 | // pivots must contain one higher than the final index in each set, with the 23 | // final element of pivots being equal to data.Len(); this deviates from the 24 | // pivot semantics of other functions (which treat pivot as a delimiter) in 25 | // order to make initializing the pivots slice simpler. 26 | // 27 | // data.Swap and data.Less are assumed to be concurrent-safe. Only 28 | // associative operations should be used (Diff is not associative); see the 29 | // Apply (Diff) example for a workaround. The result of applying SymDiff 30 | // will contain elements that exist in an odd number of sets. 31 | // 32 | // The implementation runs op concurrently on pairs of neighbor sets 33 | // in-place; when any pair has been merged, the resulting set is re-paired 34 | // with one of its neighbor sets and the process repeats until only one set 35 | // remains. The process is adaptive (large sets will not prevent small pairs 36 | // from being processed), and strives for data-locality (only adjacent 37 | // neighbors are paired and data shifts toward the zero index). 38 | func Apply(op Op, data sort.Interface, pivots []int) (size int) { 39 | switch len(pivots) { 40 | case 0: 41 | return 0 42 | case 1: 43 | return pivots[0] 44 | case 2: 45 | return op(data, pivots[0]) 46 | } 47 | 48 | spans := make([]span, 0, len(pivots)+1) 49 | 50 | // convert pivots into spans (index intervals that represent sets) 51 | i := 0 52 | for _, j := range pivots { 53 | spans = append(spans, span{i, j}) 54 | i = j 55 | } 56 | 57 | n := len(spans) // original number of spans 58 | m := n / 2 // original number of span pairs (rounded down) 59 | 60 | // true if the span is being used 61 | inuse := make([]bool, n) 62 | 63 | ch := make(chan span, m) 64 | 65 | // reverse iterate over every other span, starting with the last; 66 | // concurrent algo (further below) will pick available pairs operate on 67 | for i := range spans[:m] { 68 | i = len(spans) - 1 - i*2 69 | ch <- spans[i] 70 | } 71 | 72 | for s := range ch { 73 | if len(spans) == 1 { 74 | if s.i != 0 { 75 | panic("impossible final span") 76 | } 77 | // this was the last operation 78 | return s.j 79 | } 80 | 81 | // locate the span we received (match on start of span only) 82 | i := sort.Search(len(spans), func(i int) bool { return spans[i].i >= s.i }) 83 | 84 | // store the result (this may change field j but not field i) 85 | spans[i] = s 86 | 87 | // mark the span as available for use 88 | inuse[i] = false 89 | 90 | // check the immediate neighbors for availability (prefer left) 91 | j, k := i-1, i+1 92 | switch { 93 | case j >= 0 && !inuse[j]: 94 | i, j = j, i 95 | case k < len(spans) && !inuse[k]: 96 | j = k 97 | default: 98 | // nothing to do right now. wait for something else to finish 99 | continue 100 | } 101 | 102 | s, t := spans[i], spans[j] 103 | 104 | go func(s, t span) { 105 | // sizes of the respective sets 106 | k, l := s.j-s.i, t.j-t.i 107 | 108 | // shift the right-hand span to be adjacent to the left 109 | slide(data, s.j, t.i, l) 110 | 111 | // prepare a view of the data (abs -> rel indices) 112 | b := boundspan{data, span{s.i, s.j + l}} 113 | 114 | // store result of op, adjusting for view (rel -> abs) 115 | s.j = s.i + op(b, k) 116 | 117 | // send the result back to the coordinating goroutine 118 | ch <- s 119 | }(s, t) 120 | 121 | // account for the spawn merging that will occur 122 | s.j += t.j - t.i 123 | 124 | k = j + 1 125 | 126 | // shrink the lists to account for the merger 127 | spans = append(append(spans[:i], s), spans[k:]...) 128 | 129 | // (and the merged span is now in use as well) 130 | inuse = append(append(inuse[:i], true), inuse[k:]...) 131 | } 132 | panic("unreachable") 133 | } 134 | -------------------------------------------------------------------------------- /apply_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Kevin Gillette. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package set_test 6 | 7 | import ( 8 | "fmt" 9 | "sort" 10 | 11 | "github.com/xtgo/set" 12 | ) 13 | 14 | func ExampleApply() { 15 | sets := []sort.IntSlice{ 16 | {1, 3, 5, 7, 9}, // odds from 1 17 | {3, 5, 7, 9, 11}, // odds from 3 18 | {5, 10, 15, 20}, // 5-multiples 19 | {2, 3, 5, 7, 11}, // primes 20 | } 21 | 22 | pivots := make([]int, len(sets)) 23 | var orig, data sort.IntSlice 24 | 25 | // concatenate the sets together for use with the set package 26 | for i, set := range sets { 27 | pivots[i] = len(set) 28 | orig = append(orig, set...) 29 | } 30 | 31 | // transform set sizes into pivots 32 | pivots = set.Pivots(pivots...) 33 | 34 | tasks := []struct { 35 | name string 36 | op set.Op 37 | }{ 38 | {"union", set.Union}, 39 | {"inter", set.Inter}, 40 | {"sdiff", set.SymDiff}, 41 | } 42 | 43 | for _, task := range tasks { 44 | // make a copy of the original data (Apply rearranges the input) 45 | data = append(data[:0], orig...) 46 | size := set.Apply(task.op, data, pivots) 47 | data = data[:size] 48 | fmt.Printf("%s: %v\n", task.name, data) 49 | } 50 | 51 | // Output: 52 | // union: [1 2 3 5 7 9 10 11 15 20] 53 | // inter: [5] 54 | // sdiff: [1 2 3 7 10 15 20] 55 | } 56 | 57 | func ExampleApply_diff() { 58 | // a - b - c - d cannot be used with Apply (Diff is non-associative) 59 | // a - (b + c + d) equivalent, using Apply (Union is associative) 60 | 61 | sets := []sort.IntSlice{ 62 | {0, 2, 4, 6, 8, 10}, // positive evens 63 | {0, 1, 2, 3, 5, 8}, // set of fibonacci numbers 64 | {5, 10, 15}, // positive 5-multiples 65 | {2, 3, 5, 7, 11, 13}, // primes 66 | } 67 | 68 | var data sort.IntSlice 69 | 70 | // for use with (b + c + d) 71 | exprsets := sets[1:] 72 | pivots := make([]int, len(exprsets)) 73 | 74 | // concatenate the sets together for use with the set package 75 | for i, set := range exprsets { 76 | pivots[i] = len(set) 77 | data = append(data, set...) 78 | } 79 | 80 | // transform set sizes into pivots 81 | pivots = set.Pivots(pivots...) 82 | 83 | // calculate x = (b + c + d) 84 | size := set.Apply(set.Union, data, pivots) 85 | 86 | // concatenate the result to the end of a 87 | data = append(sets[0], data[:size]...) 88 | 89 | // calculate a - x 90 | size = set.Diff(data, len(sets[0])) 91 | data = data[:size] 92 | 93 | fmt.Println("diff:", data) 94 | 95 | // Output: 96 | // diff: [4 6] 97 | } 98 | -------------------------------------------------------------------------------- /bench_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Kevin Gillette. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package set_test 6 | 7 | import ( 8 | "sort" 9 | "testing" 10 | 11 | "github.com/xtgo/set" 12 | "github.com/xtgo/set/internal/sliceset" 13 | td "github.com/xtgo/set/internal/testdata" 14 | ) 15 | 16 | func BenchmarkUnion64K_revcat(b *testing.B) { benchMut(b, "Union", td.RevCat(2, td.Large)) } 17 | func BenchmarkUnion32(b *testing.B) { benchMut(b, "Union", td.Overlap(2, td.Small)) } 18 | func BenchmarkUnion64K(b *testing.B) { benchMut(b, "Union", td.Overlap(2, td.Large)) } 19 | func BenchmarkUnion_alt32(b *testing.B) { benchMut(b, "Union", td.Alternate(2, td.Small)) } 20 | func BenchmarkUnion_alt64K(b *testing.B) { benchMut(b, "Union", td.Alternate(2, td.Large)) } 21 | func BenchmarkInter32(b *testing.B) { benchMut(b, "Inter", td.Overlap(2, td.Small)) } 22 | func BenchmarkInter64K(b *testing.B) { benchMut(b, "Inter", td.Overlap(2, td.Large)) } 23 | func BenchmarkInter_alt32(b *testing.B) { benchMut(b, "Inter", td.Alternate(2, td.Small)) } 24 | func BenchmarkInter_alt64K(b *testing.B) { benchMut(b, "Inter", td.Alternate(2, td.Large)) } 25 | func BenchmarkDiff32(b *testing.B) { benchMut(b, "Diff", td.Overlap(2, td.Small)) } 26 | func BenchmarkDiff64K(b *testing.B) { benchMut(b, "Diff", td.Overlap(2, td.Large)) } 27 | func BenchmarkDiff_alt32(b *testing.B) { benchMut(b, "Diff", td.Alternate(2, td.Small)) } 28 | func BenchmarkDiff_alt64K(b *testing.B) { benchMut(b, "Diff", td.Alternate(2, td.Large)) } 29 | func BenchmarkSymDiff32(b *testing.B) { benchMut(b, "SymDiff", td.Overlap(2, td.Small)) } 30 | func BenchmarkSymDiff64K(b *testing.B) { benchMut(b, "SymDiff", td.Overlap(2, td.Large)) } 31 | func BenchmarkSymDiff_alt32(b *testing.B) { benchMut(b, "SymDiff", td.Alternate(2, td.Small)) } 32 | func BenchmarkSymDiff_alt64K(b *testing.B) { benchMut(b, "SymDiff", td.Alternate(2, td.Large)) } 33 | 34 | func BenchmarkIsInter32(b *testing.B) { benchBool(b, "IsInter", td.Overlap(2, td.Small)) } 35 | func BenchmarkIsInter64K(b *testing.B) { benchBool(b, "IsInter", td.Overlap(2, td.Large)) } 36 | func BenchmarkIsInter_alt32(b *testing.B) { benchBool(b, "IsInter", td.Alternate(2, td.Small)) } 37 | func BenchmarkIsInter_alt64K(b *testing.B) { benchBool(b, "IsInter", td.Alternate(2, td.Large)) } 38 | 39 | func BenchmarkApply256_64K(b *testing.B) { benchApply(b, td.Rand(256, td.Large)) } 40 | 41 | func benchMut(b *testing.B, name string, sets [][]int) { 42 | var op mutOp 43 | td.ConvMethod(&op, sliceset.Set(nil), name) 44 | bench(b, func(a, b sliceset.Set) { op(a, b) }, sets) 45 | } 46 | 47 | func benchBool(b *testing.B, name string, sets [][]int) { 48 | var op boolOp 49 | td.ConvMethod(&op, sliceset.Set(nil), name) 50 | bench(b, func(a, b sliceset.Set) { op(a, b) }, sets) 51 | } 52 | 53 | func bench(b *testing.B, op func(a, b sliceset.Set), sets [][]int) { 54 | s, t := sets[0], sets[1] 55 | data := make([]int, 0, len(s)+len(t)) 56 | 57 | b.ResetTimer() 58 | for i := 0; i < b.N; i++ { 59 | data = append(data[:0], s...) 60 | op(data, t) 61 | } 62 | } 63 | 64 | func pivots(sets [][]int) []int { 65 | lengths := make([]int, len(sets)) 66 | for i, set := range sets { 67 | lengths[i] = len(set) 68 | } 69 | return set.Pivots(lengths...) 70 | } 71 | 72 | func benchApply(b *testing.B, sets [][]int) { 73 | pivots := pivots(sets) 74 | n := len(sets) - 1 75 | data := make(sort.IntSlice, 0, pivots[n]) 76 | 77 | b.ResetTimer() 78 | for i := 0; i < b.N; i++ { 79 | data = data[:0] 80 | for _, set := range sets { 81 | data = append(data, set...) 82 | } 83 | set.Apply(set.Inter, data, pivots) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Kevin Gillette. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package set implements type-safe, non-allocating algorithms that operate 6 | // on ordered sets. 7 | // 8 | // Most functions take a data parameter of type sort.Interface and a pivot 9 | // parameter of type int; data represents two sets covering the ranges 10 | // [0:pivot] and [pivot:Len], each of which is expected to be sorted and 11 | // free of duplicates. sort.Sort may be used for sorting, and Uniq may be 12 | // used to filter away duplicates. 13 | // 14 | // All mutating functions swap elements as necessary from the two input sets 15 | // to form a single output set, returning its size: the output set will be 16 | // in the range [0:size], and will be in sorted order and free of 17 | // duplicates. Elements which were moved into the range [size:Len] will have 18 | // undefined order and may contain duplicates. 19 | // 20 | // All pivots must be in the range [0:Len]. A panic may occur when invalid 21 | // pivots are passed into any of the functions. 22 | // 23 | // Convenience functions exist for slices of int, float64, and string 24 | // element types, and also serve as examples for implementing utility 25 | // functions for other types. 26 | // 27 | // Elements will be considered equal if `!Less(i,j) && !Less(j,i)`. An 28 | // implication of this is that NaN values are equal to each other. 29 | package set 30 | 31 | // BUG(extemporalgenome): All ops should use binary search when runs are detected 32 | -------------------------------------------------------------------------------- /example_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Kevin Gillette. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package set_test 6 | 7 | import ( 8 | "fmt" 9 | "sort" 10 | 11 | "github.com/xtgo/set" 12 | ) 13 | 14 | func Example() { 15 | s := set.Strings([]string{"alpha", "gamma", "alpha"}) 16 | fmt.Println("set:", s) 17 | 18 | s = set.StringsDo(set.Union, s, "beta") 19 | fmt.Println("set + beta:", s) 20 | 21 | fmt.Println(s, "contains any [alpha delta]:", 22 | set.StringsChk(set.IsInter, s, "alpha", "delta")) 23 | 24 | fmt.Println(s, "contains all [alpha delta]:", 25 | set.StringsChk(set.IsSuper, s, "alpha", "delta")) 26 | 27 | // Output: 28 | // set: [alpha gamma] 29 | // set + beta: [alpha beta gamma] 30 | // [alpha beta gamma] contains any [alpha delta]: true 31 | // [alpha beta gamma] contains all [alpha delta]: false 32 | } 33 | 34 | func ExampleUniq() { 35 | data := sort.IntSlice{5, 7, 3, 3, 5} 36 | 37 | sort.Sort(data) // sort the data first 38 | n := set.Uniq(data) // Uniq returns the size of the set 39 | data = data[:n] // trim the duplicate elements 40 | 41 | fmt.Println(data) 42 | // Output: [3 5 7] 43 | } 44 | 45 | func ExampleInter() { 46 | data := sort.IntSlice{3, 5, 7} // create a set (it must be sorted) 47 | pivot := len(data) // store the length of our first set 48 | 49 | data = append(data, 1, 3, 5) // append a second set (which also must be sorted) 50 | size := set.Inter(data, pivot) // perform set intersection 51 | 52 | // trim data to contain just the result set 53 | data = data[:size] 54 | 55 | fmt.Println("inter:", data) 56 | 57 | // Output: 58 | // inter: [3 5] 59 | } 60 | 61 | func ExampleIsSuper() { 62 | data := sort.StringSlice{"b", "c", "d"} // create a set (it must be sorted) 63 | pivot := len(data) // store the length of our first set 64 | 65 | data = append(data, "c", "d") // append a second set (which also must be sorted) 66 | contained := set.IsSuper(data, pivot) // check the first set is a superset of the second 67 | 68 | fmt.Printf("%v superset of %v = %t\n", data[:pivot], data[pivot:], contained) 69 | 70 | data = data[:pivot] // trim off the second set 71 | 72 | data = append(data, "s") // append a new singleton set to compare against 73 | contained = set.IsSuper(data, pivot) // check for membership 74 | 75 | fmt.Printf("%v superset of %v = %t\n", data[:pivot], data[pivot:], contained) 76 | 77 | // Output: 78 | // [b c d] superset of [c d] = true 79 | // [b c d] superset of [s] = false 80 | } 81 | 82 | func ExampleIsInter() { 83 | data := sort.StringSlice{"b", "c", "d"} // create a set (it must be sorted) 84 | pivot := len(data) // store the length of our first set 85 | 86 | data = append(data, "d", "z") // append a second set (which also must be sorted) 87 | contained := set.IsInter(data, pivot) // check the first set is a superset of the second 88 | 89 | fmt.Printf("%v intersects %v = %t\n", data[:pivot], data[pivot:], contained) 90 | 91 | data = data[:pivot] // trim off the second set 92 | 93 | data = append(data, "s") // append a new singleton set to compare against 94 | contained = set.IsInter(data, pivot) // check for membership 95 | 96 | fmt.Printf("%v intersects %v = %t\n", data[:pivot], data[pivot:], contained) 97 | 98 | // Output: 99 | // [b c d] intersects [d z] = true 100 | // [b c d] intersects [s] = false 101 | } 102 | -------------------------------------------------------------------------------- /helpers.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Kevin Gillette. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package set 6 | 7 | import "sort" 8 | 9 | // Ints sorts and deduplicates a slice of ints in place, returning the 10 | // resulting set. 11 | func Ints(data []int) []int { 12 | sort.Ints(data) 13 | n := Uniq(sort.IntSlice(data)) 14 | return data[:n] 15 | } 16 | 17 | // Float64s sorts and deduplicates a slice of float64s in place, returning 18 | // the resulting set. 19 | func Float64s(data []float64) []float64 { 20 | sort.Float64s(data) 21 | n := Uniq(sort.Float64Slice(data)) 22 | return data[:n] 23 | } 24 | 25 | // Strings sorts and deduplicates a slice of strings in place, returning 26 | // the resulting set. 27 | func Strings(data []string) []string { 28 | sort.Strings(data) 29 | n := Uniq(sort.StringSlice(data)) 30 | return data[:n] 31 | } 32 | 33 | // IntsDo applies op to the int sets, s and t, returning the result. 34 | // s and t must already be individually sorted and free of duplicates. 35 | func IntsDo(op Op, s []int, t ...int) []int { 36 | data := sort.IntSlice(append(s, t...)) 37 | n := op(data, len(s)) 38 | return data[:n] 39 | } 40 | 41 | // Float64sDo applies op to the float64 sets, s and t, returning the result. 42 | // s and t must already be individually sorted and free of duplicates. 43 | func Float64sDo(op Op, s []float64, t ...float64) []float64 { 44 | data := sort.Float64Slice(append(s, t...)) 45 | n := op(data, len(s)) 46 | return data[:n] 47 | } 48 | 49 | // StringsDo applies op to the string sets, s and t, returning the result. 50 | // s and t must already be individually sorted and free of duplicates. 51 | func StringsDo(op Op, s []string, t ...string) []string { 52 | data := sort.StringSlice(append(s, t...)) 53 | n := op(data, len(s)) 54 | return data[:n] 55 | } 56 | 57 | // IntsChk compares s and t according to cmp. 58 | func IntsChk(cmp Cmp, s []int, t ...int) bool { 59 | data := sort.IntSlice(append(s, t...)) 60 | return cmp(data, len(s)) 61 | } 62 | 63 | // Float64sChk compares s and t according to cmp. 64 | func Float64sChk(cmp Cmp, s []float64, t ...float64) bool { 65 | data := sort.Float64Slice(append(s, t...)) 66 | return cmp(data, len(s)) 67 | } 68 | 69 | // StringsChk compares s and t according to cmp. 70 | func StringsChk(cmp Cmp, s []string, t ...string) bool { 71 | data := sort.StringSlice(append(s, t...)) 72 | return cmp(data, len(s)) 73 | } 74 | -------------------------------------------------------------------------------- /internal/mapset/bench_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Kevin Gillette. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package mapset_test 6 | 7 | import ( 8 | "testing" 9 | 10 | "github.com/xtgo/set/internal/mapset" 11 | td "github.com/xtgo/set/internal/testdata" 12 | ) 13 | 14 | func BenchmarkUnion64K_revcat(b *testing.B) { benchMut(b, "Union", td.RevCat(2, td.Large)) } 15 | func BenchmarkUnion32(b *testing.B) { benchMut(b, "Union", td.Overlap(2, td.Small)) } 16 | func BenchmarkUnion64K(b *testing.B) { benchMut(b, "Union", td.Overlap(2, td.Large)) } 17 | func BenchmarkUnion_alt32(b *testing.B) { benchMut(b, "Union", td.Alternate(2, td.Small)) } 18 | func BenchmarkUnion_alt64K(b *testing.B) { benchMut(b, "Union", td.Alternate(2, td.Large)) } 19 | func BenchmarkInter32(b *testing.B) { benchMut(b, "Inter", td.Overlap(2, td.Small)) } 20 | func BenchmarkInter64K(b *testing.B) { benchMut(b, "Inter", td.Overlap(2, td.Large)) } 21 | func BenchmarkInter_alt32(b *testing.B) { benchMut(b, "Inter", td.Alternate(2, td.Small)) } 22 | func BenchmarkInter_alt64K(b *testing.B) { benchMut(b, "Inter", td.Alternate(2, td.Large)) } 23 | func BenchmarkDiff32(b *testing.B) { benchMut(b, "Diff", td.Overlap(2, td.Small)) } 24 | func BenchmarkDiff64K(b *testing.B) { benchMut(b, "Diff", td.Overlap(2, td.Large)) } 25 | func BenchmarkDiff_alt32(b *testing.B) { benchMut(b, "Diff", td.Alternate(2, td.Small)) } 26 | func BenchmarkDiff_alt64K(b *testing.B) { benchMut(b, "Diff", td.Alternate(2, td.Large)) } 27 | func BenchmarkSymDiff32(b *testing.B) { benchMut(b, "SymDiff", td.Overlap(2, td.Small)) } 28 | func BenchmarkSymDiff64K(b *testing.B) { benchMut(b, "SymDiff", td.Overlap(2, td.Large)) } 29 | func BenchmarkSymDiff_alt32(b *testing.B) { benchMut(b, "SymDiff", td.Alternate(2, td.Small)) } 30 | func BenchmarkSymDiff_alt64K(b *testing.B) { benchMut(b, "SymDiff", td.Alternate(2, td.Large)) } 31 | 32 | func BenchmarkIsInter32(b *testing.B) { benchBool(b, "IsInter", td.Overlap(2, td.Small)) } 33 | func BenchmarkIsInter64K(b *testing.B) { benchBool(b, "IsInter", td.Overlap(2, td.Large)) } 34 | func BenchmarkIsInter_alt32(b *testing.B) { benchBool(b, "IsInter", td.Alternate(2, td.Small)) } 35 | func BenchmarkIsInter_alt64K(b *testing.B) { benchBool(b, "IsInter", td.Alternate(2, td.Large)) } 36 | 37 | func benchMut(b *testing.B, name string, sets [][]int) { 38 | var op mutOp 39 | td.ConvMethod(&op, mapset.Set(nil), name) 40 | bench(b, func(a, b mapset.Set) { op(a, b) }, sets) 41 | } 42 | 43 | func benchBool(b *testing.B, name string, sets [][]int) { 44 | var op boolOp 45 | td.ConvMethod(&op, mapset.Set(nil), name) 46 | bench(b, func(a, b mapset.Set) { op(a, b) }, sets) 47 | } 48 | 49 | func bench(b *testing.B, op func(a, b mapset.Set), sets [][]int) { 50 | s, t := mapset.New(sets[0]), mapset.New(sets[1]) 51 | 52 | b.ResetTimer() 53 | for i := 0; i < b.N; i++ { 54 | x, y := s.Copy(), t.Copy() 55 | op(x, y) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /internal/mapset/mapset.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Kevin Gillette. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package mapset provides a reasonable map-based set implementation for 6 | // use in comparative benchmarks, and to check arbitrary fuzz outputs. 7 | // mapset is not intended for reuse. 8 | package mapset 9 | 10 | import "sort" 11 | 12 | func New(s []int) Set { 13 | t := make(Set, len(s)) 14 | for _, v := range s { 15 | t[v] = struct{}{} 16 | } 17 | return t 18 | } 19 | 20 | type Set map[int]struct{} 21 | 22 | func (s Set) Union(t Set) Set { 23 | for v := range t { 24 | s[v] = struct{}{} 25 | } 26 | return s 27 | } 28 | 29 | func (s Set) Inter(t Set) Set { 30 | for v := range s { 31 | _, ok := t[v] 32 | if !ok { 33 | delete(s, v) 34 | } 35 | } 36 | return s 37 | } 38 | 39 | func (s Set) Diff(t Set) Set { 40 | for v := range t { 41 | delete(s, v) 42 | } 43 | return s 44 | } 45 | 46 | func (s Set) SymDiff(t Set) Set { 47 | for v := range t { 48 | _, ok := s[v] 49 | if ok { 50 | delete(s, v) 51 | } else { 52 | s[v] = struct{}{} 53 | } 54 | } 55 | return s 56 | } 57 | 58 | func (s Set) IsSub(t Set) bool { 59 | if len(s) > len(t) { 60 | return false 61 | } 62 | for k := range s { 63 | _, ok := t[k] 64 | if !ok { 65 | return false 66 | } 67 | } 68 | return true 69 | } 70 | 71 | func (s Set) IsSuper(t Set) bool { 72 | return t.IsSub(s) 73 | } 74 | 75 | func (s Set) IsInter(t Set) bool { 76 | for k := range s { 77 | _, ok := t[k] 78 | if ok { 79 | return true 80 | } 81 | } 82 | return false 83 | } 84 | 85 | func (s Set) IsEqual(t Set) bool { 86 | if len(s) != len(t) { 87 | return false 88 | } 89 | return s.IsSub(t) 90 | } 91 | 92 | func (s Set) Elems() []int { 93 | t := make([]int, 0, len(s)) 94 | for v := range s { 95 | t = append(t, v) 96 | } 97 | sort.Ints(t) 98 | return t 99 | } 100 | 101 | func (s Set) Copy() Set { 102 | t := make(Set, len(s)) 103 | t.Union(s) 104 | return t 105 | } 106 | -------------------------------------------------------------------------------- /internal/mapset/mapset_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Kevin Gillette. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package mapset_test 6 | 7 | import ( 8 | "testing" 9 | 10 | "github.com/xtgo/set/internal/mapset" 11 | "github.com/xtgo/set/internal/testdata" 12 | ) 13 | 14 | func TestUnion(t *testing.T) { testMut(t, "Union") } 15 | func TestInter(t *testing.T) { testMut(t, "Inter") } 16 | func TestDiff(t *testing.T) { testMut(t, "Diff") } 17 | func TestSymDiff(t *testing.T) { testMut(t, "SymDiff") } 18 | func TestIsSub(t *testing.T) { testBool(t, "IsSub") } 19 | func TestIsSuper(t *testing.T) { testBool(t, "IsSuper") } 20 | func TestIsInter(t *testing.T) { testBool(t, "IsInter") } 21 | func TestIsEqual(t *testing.T) { testBool(t, "IsEqual") } 22 | 23 | const format = "%s(%v, %v) = %v, want %v" 24 | 25 | type ( 26 | mutOp func(a, b mapset.Set) mapset.Set 27 | boolOp func(a, b mapset.Set) bool 28 | ) 29 | 30 | func testMut(t *testing.T, name string) { 31 | var op mutOp 32 | testdata.ConvMethod(&op, mapset.Set(nil), name) 33 | 34 | for _, tt := range testdata.BinTests { 35 | a, b := mapset.New(tt.A), mapset.New(tt.B) 36 | c := op(a, b).Elems() 37 | want := tt.SelSlice(name) 38 | 39 | if !testdata.IsEqual(c, want) { 40 | t.Errorf(format, name, tt.A, tt.B, c, want) 41 | } 42 | } 43 | } 44 | 45 | func testBool(t *testing.T, name string) { 46 | var op boolOp 47 | testdata.ConvMethod(&op, mapset.Set(nil), name) 48 | 49 | for _, tt := range testdata.BinTests { 50 | a, b := mapset.New(tt.A), mapset.New(tt.B) 51 | ok := op(a, b) 52 | want := tt.SelBool(name) 53 | 54 | if ok != want { 55 | t.Errorf(format, name, tt.A, tt.B, ok, want) 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /internal/sliceset/sliceset.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Kevin Gillette. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package sliceset provides a convenient []int set wrapper to aid in 6 | // testing and benchmarks, and to serve as an example for those in need of 7 | // a (concrete) abstraction for simplifying code. It is not intended for 8 | // direct reuse. 9 | package sliceset 10 | 11 | import ( 12 | "sort" 13 | 14 | "github.com/xtgo/set" 15 | ) 16 | 17 | type Set []int 18 | 19 | func (s Set) Len() int { return len(s) } 20 | func (s Set) Less(i, j int) bool { return s[i] < s[j] } 21 | func (s Set) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 22 | 23 | func (s Set) Copy() Set { return append(Set(nil), s...) } 24 | 25 | func (s Set) Union(t Set) Set { return s.Do(set.Union, t) } 26 | func (s Set) Inter(t Set) Set { return s.Do(set.Inter, t) } 27 | func (s Set) Diff(t Set) Set { return s.Do(set.Diff, t) } 28 | func (s Set) SymDiff(t Set) Set { return s.Do(set.SymDiff, t) } 29 | func (s Set) IsSub(t Set) bool { return s.DoBool(set.IsSub, t) } 30 | func (s Set) IsSuper(t Set) bool { return s.DoBool(set.IsSuper, t) } 31 | func (s Set) IsInter(t Set) bool { return s.DoBool(set.IsInter, t) } 32 | func (s Set) IsEqual(t Set) bool { return s.DoBool(set.IsEqual, t) } 33 | 34 | func (s Set) Uniq() Set { 35 | n := set.Uniq(s) 36 | return s[:n] 37 | } 38 | 39 | func (s Set) Do(op set.Op, t Set) Set { 40 | data := append(s, t...) 41 | n := op(data, len(s)) 42 | return data[:n] 43 | } 44 | 45 | type BoolOp func(sort.Interface, int) bool 46 | 47 | func (s Set) DoBool(op BoolOp, t Set) bool { 48 | data := append(s, t...) 49 | return op(data, len(s)) 50 | } 51 | -------------------------------------------------------------------------------- /internal/testdata/data.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Kevin Gillette. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package testdata 6 | 7 | import "reflect" 8 | 9 | type BinTest struct { 10 | A, B []int 11 | Inter []int 12 | Union []int 13 | Diff []int 14 | RevDiff []int 15 | SymDiff []int 16 | IsSub bool 17 | IsSuper bool 18 | IsInter bool 19 | IsEqual bool 20 | } 21 | 22 | func (t BinTest) sel(name string) interface{} { return reflect.ValueOf(t).FieldByName(name).Interface() } 23 | func (t BinTest) SelSlice(name string) []int { return t.sel(name).([]int) } 24 | func (t BinTest) SelBool(name string) bool { return t.sel(name).(bool) } 25 | 26 | var BinTests = []BinTest{ 27 | { 28 | // empty sets 29 | A: nil, 30 | B: nil, 31 | Inter: nil, 32 | Union: nil, 33 | Diff: nil, 34 | RevDiff: nil, 35 | SymDiff: nil, 36 | IsSub: true, 37 | IsSuper: true, 38 | IsInter: false, 39 | IsEqual: true, 40 | }, 41 | { 42 | // identical sets 43 | A: []int{1, 2, 3}, 44 | B: []int{1, 2, 3}, 45 | Inter: []int{1, 2, 3}, 46 | Union: []int{1, 2, 3}, 47 | Diff: nil, 48 | RevDiff: nil, 49 | SymDiff: nil, 50 | IsSub: true, 51 | IsSuper: true, 52 | IsInter: true, 53 | IsEqual: true, 54 | }, 55 | { 56 | // non-disjoint sets 57 | A: []int{1, 2, 3}, 58 | B: []int{2, 3, 4}, 59 | Inter: []int{2, 3}, 60 | Union: []int{1, 2, 3, 4}, 61 | Diff: []int{1}, 62 | RevDiff: []int{4}, 63 | SymDiff: []int{1, 4}, 64 | IsSub: false, 65 | IsSuper: false, 66 | IsInter: true, 67 | IsEqual: false, 68 | }, 69 | { 70 | // inverse non-disjoint sets 71 | A: []int{2, 3, 4}, 72 | B: []int{1, 2, 3}, 73 | Inter: []int{2, 3}, 74 | Union: []int{1, 2, 3, 4}, 75 | Diff: []int{4}, 76 | RevDiff: []int{1}, 77 | SymDiff: []int{1, 4}, 78 | IsSub: false, 79 | IsSuper: false, 80 | IsInter: true, 81 | IsEqual: false, 82 | }, 83 | { 84 | // disjoint sets 85 | A: []int{1, 2, 3}, 86 | B: []int{4, 5, 6}, 87 | Inter: nil, 88 | Union: []int{1, 2, 3, 4, 5, 6}, 89 | Diff: []int{1, 2, 3}, 90 | RevDiff: []int{4, 5, 6}, 91 | SymDiff: []int{1, 2, 3, 4, 5, 6}, 92 | IsSub: false, 93 | IsSuper: false, 94 | IsInter: false, 95 | IsEqual: false, 96 | }, 97 | { 98 | // inverse disjoint sets 99 | A: []int{4, 5, 6}, 100 | B: []int{1, 2, 3}, 101 | Inter: nil, 102 | Union: []int{1, 2, 3, 4, 5, 6}, 103 | Diff: []int{4, 5, 6}, 104 | RevDiff: []int{1, 2, 3}, 105 | SymDiff: []int{1, 2, 3, 4, 5, 6}, 106 | IsSub: false, 107 | IsSuper: false, 108 | IsInter: false, 109 | IsEqual: false, 110 | }, 111 | { 112 | // alternating disjoint sets 113 | A: []int{1, 3, 5}, 114 | B: []int{2, 4, 6}, 115 | Inter: nil, 116 | Union: []int{1, 2, 3, 4, 5, 6}, 117 | Diff: []int{1, 3, 5}, 118 | RevDiff: []int{2, 4, 6}, 119 | SymDiff: []int{1, 2, 3, 4, 5, 6}, 120 | IsSub: false, 121 | IsSuper: false, 122 | IsInter: false, 123 | IsEqual: false, 124 | }, 125 | { 126 | // inverse alternating disjoint sets 127 | A: []int{2, 4, 6}, 128 | B: []int{1, 3, 5}, 129 | Inter: nil, 130 | Union: []int{1, 2, 3, 4, 5, 6}, 131 | Diff: []int{2, 4, 6}, 132 | RevDiff: []int{1, 3, 5}, 133 | SymDiff: []int{1, 2, 3, 4, 5, 6}, 134 | IsSub: false, 135 | IsSuper: false, 136 | IsInter: false, 137 | IsEqual: false, 138 | }, 139 | { 140 | // subset 141 | A: []int{2}, 142 | B: []int{1, 2, 3}, 143 | Inter: []int{2}, 144 | Union: []int{1, 2, 3}, 145 | Diff: nil, 146 | RevDiff: []int{1, 3}, 147 | SymDiff: []int{1, 3}, 148 | IsSub: true, 149 | IsSuper: false, 150 | IsInter: true, 151 | IsEqual: false, 152 | }, 153 | { 154 | // superset 155 | A: []int{1, 2, 3}, 156 | B: []int{2}, 157 | Inter: []int{2}, 158 | Union: []int{1, 2, 3}, 159 | Diff: []int{1, 3}, 160 | RevDiff: nil, 161 | SymDiff: []int{1, 3}, 162 | IsSub: false, 163 | IsSuper: true, 164 | IsInter: true, 165 | IsEqual: false, 166 | }, 167 | } 168 | -------------------------------------------------------------------------------- /internal/testdata/funcs.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Kevin Gillette. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package testdata 6 | 7 | import "reflect" 8 | 9 | // IsEqual is a simple int slice equality implementation. 10 | func IsEqual(a, b []int) bool { 11 | if len(a) != len(b) { 12 | return false 13 | } 14 | for i := range a { 15 | if a[i] != b[i] { 16 | return false 17 | } 18 | } 19 | return true 20 | } 21 | 22 | // ConvField converts and stores the value of src.field into dst. dst must 23 | // be a pointer. 24 | func ConvField(dst interface{}, src interface{}, field string) { 25 | v1 := reflect.ValueOf(dst).Elem() 26 | v2 := reflect.ValueOf(src) 27 | v2 = v2.FieldByName(field) 28 | v2 = v2.Convert(v1.Type()) 29 | v1.Set(v2) 30 | } 31 | 32 | // ConvMethod converts and stores the method expression of type(src).method 33 | // into dst. dst must be a pointer to function type. 34 | func ConvMethod(dst interface{}, src interface{}, method string) { 35 | v1 := reflect.ValueOf(dst).Elem() 36 | t := reflect.TypeOf(src) 37 | op, _ := t.MethodByName(method) 38 | v2 := op.Func.Convert(v1.Type()) 39 | v1.Set(v2) 40 | } 41 | -------------------------------------------------------------------------------- /internal/testdata/seq.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Kevin Gillette. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package testdata 6 | 7 | import "math/rand" 8 | 9 | const ( 10 | Small = 32 11 | Large = 64 * 1024 12 | ) 13 | 14 | func Seq(start, stop, skip int) []int { 15 | n := (stop - start) / skip 16 | s := make([]int, n) 17 | for i := range s { 18 | s[i] = start + (i * skip) 19 | } 20 | return s 21 | } 22 | 23 | func Interleave(n, k int) [][]int { 24 | l := n * k 25 | sets := make([][]int, n) 26 | for i := range sets { 27 | sets[i] = Seq(i, i+l, n) 28 | } 29 | return sets 30 | } 31 | 32 | func Concat(n, k, gap int) [][]int { 33 | l := k + gap 34 | sets := make([][]int, n) 35 | for i := range sets { 36 | start := i * l 37 | sets[i] = Seq(start, start+k, 1) 38 | } 39 | return sets 40 | } 41 | 42 | func Reverse(sets [][]int) [][]int { 43 | n := len(sets) 44 | for i := range sets[:n/2] { 45 | j := n - i - 1 46 | sets[i], sets[j] = sets[j], sets[i] 47 | } 48 | return sets 49 | } 50 | 51 | func RevCat(n int, size int) [][]int { 52 | // union, inter: requires most Swap calls, fewest Less calls 53 | return Reverse(Concat(n, size, 0)) 54 | } 55 | 56 | func Alternate(n int, size int) [][]int { 57 | // union, inter: requires ~most Swap calls, most Less calls 58 | return Interleave(n, size) 59 | } 60 | 61 | func Overlap(n int, size int) [][]int { 62 | return Concat(n, size, -size/2) 63 | } 64 | 65 | func Rand(n int, size int) [][]int { 66 | rand.Seed(0) 67 | sets := make([][]int, n) 68 | for i := range sets { 69 | start, l := rand.Intn(size), rand.Intn(size)+1 70 | stop := start + l 71 | sets[i] = Seq(start, stop, 1) 72 | } 73 | return sets 74 | } 75 | -------------------------------------------------------------------------------- /internal/testdata/seq_test.go: -------------------------------------------------------------------------------- 1 | package testdata 2 | 3 | import "fmt" 4 | 5 | func ExampleAlternate() { 6 | fmt.Println(Alternate(2, 4)) 7 | // Output: [[0 2 4 6] [1 3 5 7]] 8 | } 9 | 10 | func ExampleOverlap() { 11 | fmt.Println(Overlap(2, 4)) 12 | // Output: [[0 1 2 3] [2 3 4 5]] 13 | } 14 | -------------------------------------------------------------------------------- /internal/testdata/uniq.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Kevin Gillette. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package testdata 6 | 7 | type UniqTest struct { 8 | In, Out []int 9 | } 10 | 11 | var UniqTests = []UniqTest{ 12 | { 13 | In: nil, 14 | Out: nil, 15 | }, 16 | { 17 | In: []int{0, 1, 2, 3, 4, 5}, 18 | Out: []int{0, 1, 2, 3, 4, 5}, 19 | }, 20 | { 21 | In: []int{0, 0, 1, 2, 3, 3}, 22 | Out: []int{0, 1, 2, 3}, 23 | }, 24 | { 25 | In: []int{0, 1, 1, 1, 1, 2}, 26 | Out: []int{0, 1, 2}, 27 | }, 28 | } 29 | -------------------------------------------------------------------------------- /mutators.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Kevin Gillette. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package set 6 | 7 | import "sort" 8 | 9 | // The Op type can be used to represent any of the mutating functions, such 10 | // as Inter. 11 | type Op func(data sort.Interface, pivot int) (size int) 12 | 13 | // Uniq swaps away duplicate elements in data, returning the size of the 14 | // unique set. data is expected to be pre-sorted, and the resulting set in 15 | // the range [0:size] will remain in sorted order. Uniq, following a 16 | // sort.Sort call, can be used to prepare arbitrary inputs for use as sets. 17 | func Uniq(data sort.Interface) (size int) { 18 | p, l := 0, data.Len() 19 | if l <= 1 { 20 | return l 21 | } 22 | for i := 1; i < l; i++ { 23 | if !data.Less(p, i) { 24 | continue 25 | } 26 | p++ 27 | if p < i { 28 | data.Swap(p, i) 29 | } 30 | } 31 | return p + 1 32 | } 33 | 34 | // Inter performs an in-place intersection on the two sets [0:pivot] and 35 | // [pivot:Len]; the resulting set will occupy [0:size]. Inter is both 36 | // associative and commutative. 37 | func Inter(data sort.Interface, pivot int) (size int) { 38 | k, l := pivot, data.Len() 39 | p, i, j := 0, 0, k 40 | for i < k && j < l { 41 | switch { 42 | case data.Less(i, j): 43 | i++ 44 | case data.Less(j, i): 45 | j++ 46 | case p < i: 47 | data.Swap(p, i) 48 | fallthrough 49 | default: 50 | p, i, j = p+1, i+1, j+1 51 | } 52 | } 53 | return p 54 | } 55 | 56 | // Union performs an in-place union on the two sets [0:pivot] and 57 | // [pivot:Len]; the resulting set will occupy [0:size]. Union is both 58 | // associative and commutative. 59 | func Union(data sort.Interface, pivot int) (size int) { 60 | // BUG(extemporalgenome): Union currently uses a multi-pass implementation 61 | 62 | sort.Sort(data) 63 | return Uniq(data) 64 | } 65 | 66 | // Diff performs an in-place difference on the two sets [0:pivot] and 67 | // [pivot:Len]; the resulting set will occupy [0:size]. Diff is neither 68 | // associative nor commutative. 69 | func Diff(data sort.Interface, pivot int) (size int) { 70 | k, l := pivot, data.Len() 71 | p, i, j := 0, 0, k 72 | for i < k && j < l { 73 | switch { 74 | case data.Less(i, j): 75 | if p < i { 76 | data.Swap(p, i) 77 | } 78 | p, i = p+1, i+1 79 | case data.Less(j, i): 80 | j++ 81 | default: 82 | i, j = i+1, j+1 83 | } 84 | } 85 | return xcopy(data, p, i, k, k) 86 | } 87 | 88 | // SymDiff performs an in-place symmetric difference on the two sets 89 | // [0:pivot] and [pivot:Len]; the resulting set will occupy [0:size]. 90 | // SymDiff is both associative and commutative. 91 | func SymDiff(data sort.Interface, pivot int) (size int) { 92 | // BUG(extemporalgenome): SymDiff currently uses a multi-pass implementation 93 | 94 | i := Inter(data, pivot) 95 | l := data.Len() 96 | b := boundspan{data, span{i, l}} 97 | sort.Sort(b) 98 | size = Uniq(b) 99 | slide(data, 0, i, size) 100 | l = i + size 101 | sort.Sort(boundspan{data, span{size, l}}) 102 | return Diff(data, size) 103 | } 104 | -------------------------------------------------------------------------------- /primitives.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Kevin Gillette. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package set 6 | 7 | import "sort" 8 | 9 | func xcopy(data sort.Interface, i, j, k, l int) int { 10 | for i < k && j < l { 11 | data.Swap(i, j) 12 | i, j = i+1, j+1 13 | } 14 | return i 15 | } 16 | 17 | func slide(data sort.Interface, i, j, n int) { 18 | xcopy(data, i, j, i+n, j+n) 19 | } 20 | 21 | /* 22 | func find(data sort.Interface, x, i, j int) int { 23 | return sort.Search(j-i, func(y int) bool { 24 | return !data.Less(x, i+y) 25 | }) 26 | } 27 | */ 28 | -------------------------------------------------------------------------------- /readonly.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Kevin Gillette. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package set 6 | 7 | import "sort" 8 | 9 | // The Cmp type can be used to represent any of the comparison functions, 10 | // such as IsInter. 11 | type Cmp func(data sort.Interface, pivot int) bool 12 | 13 | // IsSub returns true only if all elements in the range [0:pivot] are 14 | // also present in the range [pivot:Len]. 15 | func IsSub(data sort.Interface, pivot int) bool { 16 | i, j, k, l := 0, pivot, pivot, data.Len() 17 | for i < k && j < l { 18 | switch { 19 | case data.Less(i, j): 20 | return false 21 | case data.Less(j, i): 22 | j++ 23 | default: 24 | i, j = i+1, j+1 25 | } 26 | } 27 | return i == k 28 | } 29 | 30 | // IsSuper returns true only if all elements in the range [pivot:Len] are 31 | // also present in the range [0:pivot]. IsSuper is especially useful for 32 | // full membership testing. 33 | func IsSuper(data sort.Interface, pivot int) bool { 34 | i, j, k, l := 0, pivot, pivot, data.Len() 35 | for i < k && j < l { 36 | switch { 37 | case data.Less(i, j): 38 | i++ 39 | case data.Less(j, i): 40 | return false 41 | default: 42 | i, j = i+1, j+1 43 | } 44 | } 45 | return j == l 46 | } 47 | 48 | // IsInter returns true if any element in the range [0:pivot] is also 49 | // present in the range [pivot:Len]. IsInter is especially useful for 50 | // partial membership testing. 51 | func IsInter(data sort.Interface, pivot int) bool { 52 | i, j, k, l := 0, pivot, pivot, data.Len() 53 | for i < k && j < l { 54 | switch { 55 | case data.Less(i, j): 56 | i++ 57 | case data.Less(j, i): 58 | j++ 59 | default: 60 | return true 61 | } 62 | } 63 | return false 64 | } 65 | 66 | // IsEqual returns true if the sets [0:pivot] and [pivot:Len] are equal. 67 | func IsEqual(data sort.Interface, pivot int) bool { 68 | k, l := pivot, data.Len() 69 | if k*2 != l { 70 | return false 71 | } 72 | for i := 0; i < k; i++ { 73 | p, q := k-i-1, l-i-1 74 | if data.Less(p, q) || data.Less(q, p) { 75 | return false 76 | } 77 | } 78 | return true 79 | } 80 | -------------------------------------------------------------------------------- /set_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Kevin Gillette. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package set_test 6 | 7 | import ( 8 | "testing" 9 | 10 | "github.com/xtgo/set/internal/sliceset" 11 | "github.com/xtgo/set/internal/testdata" 12 | ) 13 | 14 | func TestUniq(t *testing.T) { 15 | for _, tt := range testdata.UniqTests { 16 | s := sliceset.Set(tt.In).Copy() 17 | s = s.Uniq() 18 | 19 | if !testdata.IsEqual(s, tt.Out) { 20 | t.Errorf("Uniq(%v) = %v, want %v", tt.In, s, tt.Out) 21 | } 22 | } 23 | } 24 | 25 | func TestUnion(t *testing.T) { testMut(t, "Union") } 26 | func TestInter(t *testing.T) { testMut(t, "Inter") } 27 | func TestDiff(t *testing.T) { testMut(t, "Diff") } 28 | func TestSymDiff(t *testing.T) { testMut(t, "SymDiff") } 29 | func TestIsSub(t *testing.T) { testBool(t, "IsSub") } 30 | func TestIsSuper(t *testing.T) { testBool(t, "IsSuper") } 31 | func TestIsInter(t *testing.T) { testBool(t, "IsInter") } 32 | func TestIsEqual(t *testing.T) { testBool(t, "IsEqual") } 33 | 34 | const format = "%s(%v, %v) = %v, want %v" 35 | 36 | type ( 37 | mutOp func(a, b sliceset.Set) sliceset.Set 38 | boolOp func(a, b sliceset.Set) bool 39 | ) 40 | 41 | func testMut(t *testing.T, name string) { 42 | var op mutOp 43 | testdata.ConvMethod(&op, sliceset.Set(nil), name) 44 | 45 | for _, tt := range testdata.BinTests { 46 | a := sliceset.Set(tt.A).Copy() 47 | c := op(a, tt.B) 48 | want := tt.SelSlice(name) 49 | 50 | if !testdata.IsEqual(c, want) { 51 | t.Errorf(format, name, tt.A, tt.B, c, want) 52 | } 53 | } 54 | } 55 | 56 | func testBool(t *testing.T, name string) { 57 | var op boolOp 58 | testdata.ConvMethod(&op, sliceset.Set(nil), name) 59 | 60 | for _, tt := range testdata.BinTests { 61 | ok := op(tt.A, tt.B) 62 | want := tt.SelBool(name) 63 | 64 | if ok != want { 65 | t.Errorf(format, name, tt.A, tt.B, ok, want) 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /types.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Kevin Gillette. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package set 6 | 7 | import "sort" 8 | 9 | type span struct{ i, j int } 10 | 11 | type boundspan struct { 12 | data sort.Interface 13 | span 14 | } 15 | 16 | func (b boundspan) Len() int { return b.j - b.i } 17 | func (b boundspan) Less(i, j int) bool { return b.data.Less(b.i+i, b.i+j) } 18 | func (b boundspan) Swap(i, j int) { b.data.Swap(b.i+i, b.i+j) } 19 | --------------------------------------------------------------------------------