From d55639e713786fef74c53b7c3451dcabac7e3863 Mon Sep 17 00:00:00 2001 From: Joe Tsai Date: Thu, 9 Aug 2018 13:35:22 -0700 Subject: [PATCH] internal/set: add set package for set data structures Package set provides simple set data structures for uint64 and string types. High-level API: type Set(T {}) xxx func (Set) Len() int func (Set) Has(T) bool func (Set) Set(T) func (Set) Clear(T) These data structures are useful for implementing required fields efficiently or ensuring that protobuf identifiers do not conflict. Change-Id: If846630a9034909a43121b3e0f6720275f4b7aaf Reviewed-on: https://go-review.googlesource.com/128898 Reviewed-by: Chris Manghane --- internal/set/doc.go | 21 ++++++++ internal/set/ints.go | 73 +++++++++++++++++++++++++++ internal/set/ints_test.go | 96 ++++++++++++++++++++++++++++++++++++ internal/set/strings.go | 25 ++++++++++ internal/set/strings_test.go | 63 +++++++++++++++++++++++ 5 files changed, 278 insertions(+) create mode 100644 internal/set/doc.go create mode 100644 internal/set/ints.go create mode 100644 internal/set/ints_test.go create mode 100644 internal/set/strings.go create mode 100644 internal/set/strings_test.go diff --git a/internal/set/doc.go b/internal/set/doc.go new file mode 100644 index 00000000..d803514f --- /dev/null +++ b/internal/set/doc.go @@ -0,0 +1,21 @@ +// Copyright 2018 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 set provides simple set data structures for uint64 and string types. +// +// The API for every set is: +// type Set(T {}) opaque +// +// // Len reports the number of elements in the set. +// func (Set) Len() int +// +// // Has reports whether an item is in the set. +// func (Set) Has(T) bool +// +// // Set inserts the item into the set. +// func (Set) Set(T) +// +// // Clear removes the item from the set. +// func (Set) Clear(T) +package set diff --git a/internal/set/ints.go b/internal/set/ints.go new file mode 100644 index 00000000..0632e5aa --- /dev/null +++ b/internal/set/ints.go @@ -0,0 +1,73 @@ +// Copyright 2018 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 set + +import "math/bits" + +// Int32s represents a set of integers within the range of 0..31. +type Int32s uint32 + +func (bs *Int32s) Len() int { + return bits.OnesCount32(uint32(*bs)) +} +func (bs *Int32s) Has(n uint64) bool { + return uint32(*bs)&(uint32(1)< 0 +} +func (bs *Int32s) Set(n uint64) { + *(*uint32)(bs) |= uint32(1) << n +} +func (bs *Int32s) Clear(n uint64) { + *(*uint32)(bs) &^= uint32(1) << n +} + +// Int64s represents a set of integers within the range of 0..63. +type Int64s uint64 + +func (bs *Int64s) Len() int { + return bits.OnesCount64(uint64(*bs)) +} +func (bs *Int64s) Has(n uint64) bool { + return uint64(*bs)&(uint64(1)< 0 +} +func (bs *Int64s) Set(n uint64) { + *(*uint64)(bs) |= uint64(1) << n +} +func (bs *Int64s) Clear(n uint64) { + *(*uint64)(bs) &^= uint64(1) << n +} + +// Ints represents a set of integers within the range of 0..math.MaxUint64. +type Ints struct { + lo Int64s + hi map[uint64]struct{} +} + +func (bs *Ints) Len() int { + return bs.lo.Len() + len(bs.hi) +} +func (bs *Ints) Has(n uint64) bool { + if n < 64 { + return bs.lo.Has(n) + } + _, ok := bs.hi[n] + return ok +} +func (bs *Ints) Set(n uint64) { + if n < 64 { + bs.lo.Set(n) + return + } + if bs.hi == nil { + bs.hi = make(map[uint64]struct{}) + } + bs.hi[n] = struct{}{} +} +func (bs *Ints) Clear(n uint64) { + if n < 64 { + bs.lo.Clear(n) + return + } + delete(bs.hi, n) +} diff --git a/internal/set/ints_test.go b/internal/set/ints_test.go new file mode 100644 index 00000000..b370d327 --- /dev/null +++ b/internal/set/ints_test.go @@ -0,0 +1,96 @@ +// Copyright 2018 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 set + +import ( + "math/rand" + "testing" +) + +const maxLimit = 1024 + +var toSet, toClear [maxLimit]bool + +func init() { + r := rand.New(rand.NewSource(0)) + for i := 0; i < maxLimit; i++ { + toSet[i] = r.Intn(2) == 0 + toClear[i] = r.Intn(2) == 0 + } +} + +func TestInts(t *testing.T) { + type set interface { + Len() int + Has(n uint64) bool + Set(n uint64) + Clear(n uint64) + } + + tests := []struct { + label string + makeSet func() set + limit int + }{ + {label: "Int32s", makeSet: func() set { return new(Int32s) }, limit: 32}, + {label: "Int64s", makeSet: func() set { return new(Int64s) }, limit: 64}, + {label: "Ints", makeSet: func() set { return new(Ints) }, limit: maxLimit}, + } + + for _, tt := range tests { + t.Run(tt.label, func(t *testing.T) { + ns := tt.makeSet() + + // Check that set starts empty. + wantLen := 0 + if ns.Len() != wantLen { + t.Errorf("init: Len() = %d, want %d", ns.Len(), wantLen) + } + for i := 0; i < tt.limit; i++ { + if ns.Has(uint64(i)) { + t.Errorf("init: Has(%d) = true, want false", i) + } + } + + // Set some numbers. + for i, b := range toSet[:tt.limit] { + if b { + ns.Set(uint64(i)) + wantLen++ + } + } + + // Check that integers were set. + if ns.Len() != wantLen { + t.Errorf("after Set: Len() = %d, want %d", ns.Len(), wantLen) + } + for i := 0; i < tt.limit; i++ { + if got := ns.Has(uint64(i)); got != toSet[i] { + t.Errorf("after Set: Has(%d) = %v, want %v", i, got, !got) + } + } + + // Clear some numbers. + for i, b := range toClear[:tt.limit] { + if b { + ns.Clear(uint64(i)) + if toSet[i] { + wantLen-- + } + } + } + + // Check that integers were cleared. + if ns.Len() != wantLen { + t.Errorf("after Clear: Len() = %d, want %d", ns.Len(), wantLen) + } + for i := 0; i < tt.limit; i++ { + if got := ns.Has(uint64(i)); got != toSet[i] && !toClear[i] { + t.Errorf("after Clear: Has(%d) = %v, want %v", i, got, !got) + } + } + }) + } +} diff --git a/internal/set/strings.go b/internal/set/strings.go new file mode 100644 index 00000000..e81dc10b --- /dev/null +++ b/internal/set/strings.go @@ -0,0 +1,25 @@ +// Copyright 2018 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 set + +// Strings represents a set of strings. +type Strings map[string]struct{} + +func (ss *Strings) Len() int { + return len(*ss) +} +func (ss *Strings) Has(s string) bool { + _, ok := (*ss)[s] + return ok +} +func (ss *Strings) Set(s string) { + if *ss == nil { + *ss = make(map[string]struct{}) + } + (*ss)[s] = struct{}{} +} +func (ss *Strings) Clear(s string) { + delete(*ss, s) +} diff --git a/internal/set/strings_test.go b/internal/set/strings_test.go new file mode 100644 index 00000000..0b99bf76 --- /dev/null +++ b/internal/set/strings_test.go @@ -0,0 +1,63 @@ +// Copyright 2018 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 set + +import ( + "strconv" + "testing" +) + +func TestStrings(t *testing.T) { + var ss Strings + + // Check that set starts empty. + wantLen := 0 + if ss.Len() != wantLen { + t.Errorf("init: Len() = %d, want %d", ss.Len(), wantLen) + } + for i := 0; i < maxLimit; i++ { + if ss.Has(strconv.Itoa(i)) { + t.Errorf("init: Has(%d) = true, want false", i) + } + } + + // Set some strings. + for i, b := range toSet[:maxLimit] { + if b { + ss.Set(strconv.Itoa(i)) + wantLen++ + } + } + + // Check that strings were set. + if ss.Len() != wantLen { + t.Errorf("after Set: Len() = %d, want %d", ss.Len(), wantLen) + } + for i := 0; i < maxLimit; i++ { + if got := ss.Has(strconv.Itoa(i)); got != toSet[i] { + t.Errorf("after Set: Has(%d) = %v, want %v", i, got, !got) + } + } + + // Clear some strings. + for i, b := range toClear[:maxLimit] { + if b { + ss.Clear(strconv.Itoa(i)) + if toSet[i] { + wantLen-- + } + } + } + + // Check that strings were cleared. + if ss.Len() != wantLen { + t.Errorf("after Clear: Len() = %d, want %d", ss.Len(), wantLen) + } + for i := 0; i < maxLimit; i++ { + if got := ss.Has(strconv.Itoa(i)); got != toSet[i] && !toClear[i] { + t.Errorf("after Clear: Has(%d) = %v, want %v", i, got, !got) + } + } +}