1
0
mirror of https://github.com/golang/go synced 2024-11-05 16:56:16 -07:00
go/container/intsets/sparse_test.go
Andrew M Bursavich ac8637e9fa container/intsets: Intersects, SubsetOf, SymmetricDifference(With)
Just reading through intsets and decided to knock out a few TODOs.

Change-Id: I677dbcc5ff934fbe0f0af09a4741e708a893f8db
Reviewed-on: https://go-review.googlesource.com/2733
Reviewed-by: Alan Donovan <adonovan@google.com>
2015-01-14 20:51:28 +00:00

639 lines
14 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Copyright 2014 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.
package intsets_test
import (
"fmt"
"log"
"math/rand"
"sort"
"strings"
"testing"
"golang.org/x/tools/container/intsets"
)
func TestBasics(t *testing.T) {
var s intsets.Sparse
if len := s.Len(); len != 0 {
t.Errorf("Len({}): got %d, want 0", len)
}
if s := s.String(); s != "{}" {
t.Errorf("String({}): got %q, want \"{}\"", s)
}
if s.Has(3) {
t.Errorf("Has(3): got true, want false")
}
if err := s.Check(); err != nil {
t.Error(err)
}
if !s.Insert(3) {
t.Errorf("Insert(3): got false, want true")
}
if max := s.Max(); max != 3 {
t.Errorf("Max: got %d, want 3", max)
}
if !s.Insert(435) {
t.Errorf("Insert(435): got false, want true")
}
if s := s.String(); s != "{3 435}" {
t.Errorf("String({3 435}): got %q, want \"{3 435}\"", s)
}
if max := s.Max(); max != 435 {
t.Errorf("Max: got %d, want 435", max)
}
if len := s.Len(); len != 2 {
t.Errorf("Len: got %d, want 2", len)
}
if !s.Remove(435) {
t.Errorf("Remove(435): got false, want true")
}
if s := s.String(); s != "{3}" {
t.Errorf("String({3}): got %q, want \"{3}\"", s)
}
}
// Insert, Len, IsEmpty, Hash, Clear, AppendTo.
func TestMoreBasics(t *testing.T) {
set := new(intsets.Sparse)
set.Insert(456)
set.Insert(123)
set.Insert(789)
if set.Len() != 3 {
t.Errorf("%s.Len: got %d, want 3", set, set.Len())
}
if set.IsEmpty() {
t.Errorf("%s.IsEmpty: got true", set)
}
if !set.Has(123) {
t.Errorf("%s.Has(123): got false", set)
}
if set.Has(1234) {
t.Errorf("%s.Has(1234): got true", set)
}
got := set.AppendTo([]int{-1})
if want := []int{-1, 123, 456, 789}; fmt.Sprint(got) != fmt.Sprint(want) {
t.Errorf("%s.AppendTo: got %v, want %v", set, got, want)
}
set.Clear()
if set.Len() != 0 {
t.Errorf("Clear: got %d, want 0", set.Len())
}
if !set.IsEmpty() {
t.Errorf("IsEmpty: got false")
}
if set.Has(123) {
t.Errorf("%s.Has: got false", set)
}
}
func TestTakeMin(t *testing.T) {
var set intsets.Sparse
set.Insert(456)
set.Insert(123)
set.Insert(789)
set.Insert(-123)
var got int
for i, want := range []int{-123, 123, 456, 789} {
if !set.TakeMin(&got) || got != want {
t.Errorf("TakeMin #%d: got %d, want %d", i, got, want)
}
}
if set.TakeMin(&got) {
t.Errorf("%s.TakeMin returned true", &set)
}
if err := set.Check(); err != nil {
t.Fatalf("check: %s: %#v", err, &set)
}
}
func TestMinAndMax(t *testing.T) {
values := []int{0, 456, 123, 789, -123} // elt 0 => empty set
wantMax := []int{intsets.MinInt, 456, 456, 789, 789}
wantMin := []int{intsets.MaxInt, 456, 123, 123, -123}
var set intsets.Sparse
for i, x := range values {
if i != 0 {
set.Insert(x)
}
if got, want := set.Min(), wantMin[i]; got != want {
t.Errorf("Min #%d: got %d, want %d", i, got, want)
}
if got, want := set.Max(), wantMax[i]; got != want {
t.Errorf("Max #%d: got %d, want %d", i, got, want)
}
}
set.Insert(intsets.MinInt)
if got, want := set.Min(), intsets.MinInt; got != want {
t.Errorf("Min: got %d, want %d", got, want)
}
set.Insert(intsets.MaxInt)
if got, want := set.Max(), intsets.MaxInt; got != want {
t.Errorf("Max: got %d, want %d", got, want)
}
}
func TestEquals(t *testing.T) {
var setX intsets.Sparse
setX.Insert(456)
setX.Insert(123)
setX.Insert(789)
if !setX.Equals(&setX) {
t.Errorf("Equals(%s, %s): got false", &setX, &setX)
}
var setY intsets.Sparse
setY.Insert(789)
setY.Insert(456)
setY.Insert(123)
if !setX.Equals(&setY) {
t.Errorf("Equals(%s, %s): got false", &setX, &setY)
}
setY.Insert(1)
if setX.Equals(&setY) {
t.Errorf("Equals(%s, %s): got true", &setX, &setY)
}
var empty intsets.Sparse
if setX.Equals(&empty) {
t.Errorf("Equals(%s, %s): got true", &setX, &empty)
}
// Edge case: some block (with offset=0) appears in X but not Y.
setY.Remove(123)
if setX.Equals(&setY) {
t.Errorf("Equals(%s, %s): got true", &setX, &setY)
}
}
// A pset is a parallel implementation of a set using both an intsets.Sparse
// and a built-in hash map.
type pset struct {
hash map[int]bool
bits intsets.Sparse
}
func makePset() *pset {
return &pset{hash: make(map[int]bool)}
}
func (set *pset) add(n int) {
prev := len(set.hash)
set.hash[n] = true
grewA := len(set.hash) > prev
grewB := set.bits.Insert(n)
if grewA != grewB {
panic(fmt.Sprintf("add(%d): grewA=%t grewB=%t", n, grewA, grewB))
}
}
func (set *pset) remove(n int) {
prev := len(set.hash)
delete(set.hash, n)
shrankA := len(set.hash) < prev
shrankB := set.bits.Remove(n)
if shrankA != shrankB {
panic(fmt.Sprintf("remove(%d): shrankA=%t shrankB=%t", n, shrankA, shrankB))
}
}
func (set *pset) check(t *testing.T, msg string) {
var eltsA []int
for elt := range set.hash {
eltsA = append(eltsA, int(elt))
}
sort.Ints(eltsA)
eltsB := set.bits.AppendTo(nil)
if a, b := fmt.Sprint(eltsA), fmt.Sprint(eltsB); a != b {
t.Errorf("check(%s): hash=%s bits=%s (%s)", msg, a, b, &set.bits)
}
if err := set.bits.Check(); err != nil {
t.Fatalf("Check(%s): %s: %#v", msg, err, &set.bits)
}
}
// randomPset returns a parallel set of random size and elements.
func randomPset(prng *rand.Rand, maxSize int) *pset {
set := makePset()
size := int(prng.Int()) % maxSize
for i := 0; i < size; i++ {
// TODO(adonovan): benchmark how performance varies
// with this sparsity parameter.
n := int(prng.Int()) % 10000
set.add(n)
}
return set
}
// TestRandomMutations performs the same random adds/removes on two
// set implementations and ensures that they compute the same result.
func TestRandomMutations(t *testing.T) {
const debug = false
set := makePset()
prng := rand.New(rand.NewSource(0))
for i := 0; i < 10000; i++ {
n := int(prng.Int())%2000 - 1000
if i%2 == 0 {
if debug {
log.Printf("add %d", n)
}
set.add(n)
} else {
if debug {
log.Printf("remove %d", n)
}
set.remove(n)
}
if debug {
set.check(t, "post mutation")
}
}
set.check(t, "final")
if debug {
log.Print(&set.bits)
}
}
// TestSetOperations exercises classic set operations: ∩ , , \.
func TestSetOperations(t *testing.T) {
prng := rand.New(rand.NewSource(0))
// Use random sets of sizes from 0 to about 1000.
// For each operator, we test variations such as
// Z.op(X, Y), Z.op(X, Z) and Z.op(Z, Y) to exercise
// the degenerate cases of each method implementation.
for i := uint(0); i < 12; i++ {
X := randomPset(prng, 1<<i)
Y := randomPset(prng, 1<<i)
// TODO(adonovan): minimise dependencies between stanzas below.
// Copy(X)
C := makePset()
C.bits.Copy(&Y.bits) // no effect on result
C.bits.Copy(&X.bits)
C.hash = X.hash
C.check(t, "C.Copy(X)")
C.bits.Copy(&C.bits)
C.check(t, "C.Copy(C)")
// U.Union(X, Y)
U := makePset()
U.bits.Union(&X.bits, &Y.bits)
for n := range X.hash {
U.hash[n] = true
}
for n := range Y.hash {
U.hash[n] = true
}
U.check(t, "U.Union(X, Y)")
// U.Union(X, X)
U.bits.Union(&X.bits, &X.bits)
U.hash = X.hash
U.check(t, "U.Union(X, X)")
// U.Union(U, Y)
U = makePset()
U.bits.Copy(&X.bits)
U.bits.Union(&U.bits, &Y.bits)
for n := range X.hash {
U.hash[n] = true
}
for n := range Y.hash {
U.hash[n] = true
}
U.check(t, "U.Union(U, Y)")
// U.Union(X, U)
U.bits.Copy(&Y.bits)
U.bits.Union(&X.bits, &U.bits)
U.check(t, "U.Union(X, U)")
// U.UnionWith(U)
U.bits.UnionWith(&U.bits)
U.check(t, "U.UnionWith(U)")
// I.Intersection(X, Y)
I := makePset()
I.bits.Intersection(&X.bits, &Y.bits)
for n := range X.hash {
if Y.hash[n] {
I.hash[n] = true
}
}
I.check(t, "I.Intersection(X, Y)")
// I.Intersection(X, X)
I.bits.Intersection(&X.bits, &X.bits)
I.hash = X.hash
I.check(t, "I.Intersection(X, X)")
// I.Intersection(I, X)
I.bits.Intersection(&I.bits, &X.bits)
I.check(t, "I.Intersection(I, X)")
// I.Intersection(X, I)
I.bits.Intersection(&X.bits, &I.bits)
I.check(t, "I.Intersection(X, I)")
// I.Intersection(I, I)
I.bits.Intersection(&I.bits, &I.bits)
I.check(t, "I.Intersection(I, I)")
// D.Difference(X, Y)
D := makePset()
D.bits.Difference(&X.bits, &Y.bits)
for n := range X.hash {
if !Y.hash[n] {
D.hash[n] = true
}
}
D.check(t, "D.Difference(X, Y)")
// D.Difference(D, Y)
D.bits.Copy(&X.bits)
D.bits.Difference(&D.bits, &Y.bits)
D.check(t, "D.Difference(D, Y)")
// D.Difference(Y, D)
D.bits.Copy(&X.bits)
D.bits.Difference(&Y.bits, &D.bits)
D.hash = make(map[int]bool)
for n := range Y.hash {
if !X.hash[n] {
D.hash[n] = true
}
}
D.check(t, "D.Difference(Y, D)")
// D.Difference(X, X)
D.bits.Difference(&X.bits, &X.bits)
D.hash = nil
D.check(t, "D.Difference(X, X)")
// D.DifferenceWith(D)
D.bits.Copy(&X.bits)
D.bits.DifferenceWith(&D.bits)
D.check(t, "D.DifferenceWith(D)")
// SD.SymmetricDifference(X, Y)
SD := makePset()
SD.bits.SymmetricDifference(&X.bits, &Y.bits)
for n := range X.hash {
if !Y.hash[n] {
SD.hash[n] = true
}
}
for n := range Y.hash {
if !X.hash[n] {
SD.hash[n] = true
}
}
SD.check(t, "SD.SymmetricDifference(X, Y)")
// X.SymmetricDifferenceWith(Y)
SD.bits.Copy(&X.bits)
SD.bits.SymmetricDifferenceWith(&Y.bits)
SD.check(t, "X.SymmetricDifference(Y)")
// Y.SymmetricDifferenceWith(X)
SD.bits.Copy(&Y.bits)
SD.bits.SymmetricDifferenceWith(&X.bits)
SD.check(t, "Y.SymmetricDifference(X)")
// SD.SymmetricDifference(X, X)
SD.bits.SymmetricDifference(&X.bits, &X.bits)
SD.hash = nil
SD.check(t, "SD.SymmetricDifference(X, X)")
// SD.SymmetricDifference(X, Copy(X))
X2 := makePset()
X2.bits.Copy(&X.bits)
SD.bits.SymmetricDifference(&X.bits, &X2.bits)
SD.check(t, "SD.SymmetricDifference(X, Copy(X))")
// Copy(X).SymmetricDifferenceWith(X)
SD.bits.Copy(&X.bits)
SD.bits.SymmetricDifferenceWith(&X.bits)
SD.check(t, "Copy(X).SymmetricDifferenceWith(X)")
}
}
func TestIntersectionWith(t *testing.T) {
// Edge cases: the pairs (1,1), (1000,2000), (8000,4000)
// exercise the <, >, == cases in IntersectionWith that the
// TestSetOperations data is too dense to cover.
var X, Y intsets.Sparse
X.Insert(1)
X.Insert(1000)
X.Insert(8000)
Y.Insert(1)
Y.Insert(2000)
Y.Insert(4000)
X.IntersectionWith(&Y)
if got, want := X.String(), "{1}"; got != want {
t.Errorf("IntersectionWith: got %s, want %s", got, want)
}
}
func TestIntersects(t *testing.T) {
prng := rand.New(rand.NewSource(0))
for i := uint(0); i < 12; i++ {
X, Y := randomPset(prng, 1<<i), randomPset(prng, 1<<i)
x, y := &X.bits, &Y.bits
// test the slow way
var z intsets.Sparse
z.Copy(x)
z.IntersectionWith(y)
if got, want := x.Intersects(y), !z.IsEmpty(); got != want {
t.Errorf("Intersects: got %v, want %v", got, want)
}
// make it false
a := x.AppendTo(nil)
for _, v := range a {
y.Remove(v)
}
if got, want := x.Intersects(y), false; got != want {
t.Errorf("Intersects: got %v, want %v", got, want)
}
// make it true
if x.IsEmpty() {
continue
}
i := prng.Intn(len(a))
y.Insert(a[i])
if got, want := x.Intersects(y), true; got != want {
t.Errorf("Intersects: got %v, want %v", got, want)
}
}
}
func TestSubsetOf(t *testing.T) {
prng := rand.New(rand.NewSource(0))
for i := uint(0); i < 12; i++ {
X, Y := randomPset(prng, 1<<i), randomPset(prng, 1<<i)
x, y := &X.bits, &Y.bits
// test the slow way
var z intsets.Sparse
z.Copy(x)
z.DifferenceWith(y)
if got, want := x.SubsetOf(y), z.IsEmpty(); got != want {
t.Errorf("SubsetOf: got %v, want %v", got, want)
}
// make it true
y.UnionWith(x)
if got, want := x.SubsetOf(y), true; got != want {
t.Errorf("SubsetOf: got %v, want %v", got, want)
}
// make it false
if x.IsEmpty() {
continue
}
a := x.AppendTo(nil)
i := prng.Intn(len(a))
y.Remove(a[i])
if got, want := x.SubsetOf(y), false; got != want {
t.Errorf("SubsetOf: got %v, want %v", got, want)
}
}
}
func TestBitString(t *testing.T) {
for _, test := range []struct {
input []int
want string
}{
{nil, "0"},
{[]int{0}, "1"},
{[]int{0, 4, 5}, "110001"},
{[]int{0, 7, 177}, "1" + strings.Repeat("0", 169) + "10000001"},
{[]int{-3, 0, 4, 5}, "110001.001"},
{[]int{-3}, "0.001"},
} {
var set intsets.Sparse
for _, x := range test.input {
set.Insert(x)
}
if got := set.BitString(); got != test.want {
t.Errorf("BitString(%s) = %s, want %s", set.String(), got, test.want)
}
}
}
func TestFailFastOnShallowCopy(t *testing.T) {
var x intsets.Sparse
x.Insert(1)
y := x // shallow copy (breaks representation invariants)
defer func() {
got := fmt.Sprint(recover())
want := "A Sparse has been copied without (*Sparse).Copy()"
if got != want {
t.Errorf("shallow copy: recover() = %q, want %q", got, want)
}
}()
y.String() // panics
t.Error("didn't panic as expected")
}
// -- Benchmarks -------------------------------------------------------
// TODO(adonovan):
// - Add benchmarks of each method.
// - Gather set distributions from pointer analysis.
// - Measure memory usage.
func BenchmarkSparseBitVector(b *testing.B) {
prng := rand.New(rand.NewSource(0))
for tries := 0; tries < b.N; tries++ {
var x, y, z intsets.Sparse
for i := 0; i < 1000; i++ {
n := int(prng.Int()) % 100000
if i%2 == 0 {
x.Insert(n)
} else {
y.Insert(n)
}
}
z.Union(&x, &y)
z.Difference(&x, &y)
}
}
func BenchmarkHashTable(b *testing.B) {
prng := rand.New(rand.NewSource(0))
for tries := 0; tries < b.N; tries++ {
x, y, z := make(map[int]bool), make(map[int]bool), make(map[int]bool)
for i := 0; i < 1000; i++ {
n := int(prng.Int()) % 100000
if i%2 == 0 {
x[n] = true
} else {
y[n] = true
}
}
// union
for n := range x {
z[n] = true
}
for n := range y {
z[n] = true
}
// difference
z = make(map[int]bool)
for n := range y {
if !x[n] {
z[n] = true
}
}
}
}
func BenchmarkAppendTo(b *testing.B) {
prng := rand.New(rand.NewSource(0))
var x intsets.Sparse
for i := 0; i < 1000; i++ {
x.Insert(int(prng.Int()) % 10000)
}
var space [1000]int
for tries := 0; tries < b.N; tries++ {
x.AppendTo(space[:0])
}
}