From eb4de1843a159f3ddf35bae1a3a1a64dd557c122 Mon Sep 17 00:00:00 2001 From: Ben Johnson Date: Thu, 7 Jan 2016 13:43:57 -0700 Subject: [PATCH] support uint64 in roaring bitmaps This commit refactors the roaring bitmaps to use uint64 values instead of uint32 values. This is required for the size of values we need in pilosa. This change is not backwards compatible with the previous data format so any existing data directories need to be removed before using this code. --- fragment.go | 8 +- roaring/roaring.go | 137 +++++++++++++++++++++------------ roaring/roaring_test.go | 166 ++++++++++++++++++++-------------------- 3 files changed, 174 insertions(+), 137 deletions(-) diff --git a/fragment.go b/fragment.go index 1157a6142..680e01737 100644 --- a/fragment.go +++ b/fragment.go @@ -201,8 +201,8 @@ func (f *Fragment) bitmap(bitmapID uint64) *Bitmap { // Read bitmap from storage. bm := NewBitmap() - f.storage.ForEachRange(uint32(bitmapID)*SliceWidth, uint32(bitmapID+1)*SliceWidth, func(i uint32) { - profileID := (f.slice * SliceWidth) + (uint64(i) % SliceWidth) + f.storage.ForEachRange(bitmapID*SliceWidth, (bitmapID+1)*SliceWidth, func(i uint64) { + profileID := (f.slice * SliceWidth) + (i % SliceWidth) bm.setBit(profileID) }) @@ -292,14 +292,14 @@ func (f *Fragment) ClearBit(bitmapID, profileID uint64) error { } // pos translates the bitmap ID and profile ID into a position in the storage bitmap. -func (f *Fragment) pos(bitmapID, profileID uint64) (uint32, error) { +func (f *Fragment) pos(bitmapID, profileID uint64) (uint64, error) { // Return an error if the profile ID is out of the range of the fragment's slice. minProfileID := f.slice * SliceWidth if profileID < minProfileID || profileID >= minProfileID+SliceWidth { return 0, errors.New("profile out of bounds") } - return uint32((bitmapID * SliceWidth) + (profileID % SliceWidth)), nil + return (bitmapID * SliceWidth) + (profileID % SliceWidth), nil } func (f *Fragment) TopN(src *Bitmap, n int, categories []uint64) []Pair { diff --git a/roaring/roaring.go b/roaring/roaring.go index f831072fe..a86ed6863 100644 --- a/roaring/roaring.go +++ b/roaring/roaring.go @@ -23,7 +23,7 @@ const ( // Bitmap represents a roaring bitmap. type Bitmap struct { - keys []uint16 // keys for containers + keys []uint64 // keys for containers containers []*container // array and bitmap containers // Number of operations written to the writer. @@ -34,14 +34,14 @@ type Bitmap struct { } // NewBitmap returns a Bitmap with an initial set of values. -func NewBitmap(a ...uint32) *Bitmap { +func NewBitmap(a ...uint64) *Bitmap { b := &Bitmap{} b.Add(a...) return b } // Add adds values to the bitmap. -func (b *Bitmap) Add(a ...uint32) error { +func (b *Bitmap) Add(a ...uint64) error { for _, v := range a { // Create an add operation. op := &op{typ: opTypeAdd, value: v} @@ -58,9 +58,9 @@ func (b *Bitmap) Add(a ...uint32) error { return nil } -func (b *Bitmap) add(v uint32) { +func (b *Bitmap) add(v uint64) { hb := highbits(v) - i := search(b.keys, hb) + i := search64(b.keys, hb) // If index is negative then there's not an exact match // and a container needs to be added. @@ -73,7 +73,7 @@ func (b *Bitmap) add(v uint32) { } // Contains returns true if v is in the bitmap. -func (b *Bitmap) Contains(v uint32) bool { +func (b *Bitmap) Contains(v uint64) bool { c := b.container(highbits(v)) if c == nil { return false @@ -82,7 +82,7 @@ func (b *Bitmap) Contains(v uint32) bool { } // Remove removes values from the bitmap. -func (b *Bitmap) Remove(a ...uint32) error { +func (b *Bitmap) Remove(a ...uint64) error { for _, v := range a { // Create an add operation. op := &op{typ: opTypeRemove, value: v} @@ -98,9 +98,9 @@ func (b *Bitmap) Remove(a ...uint32) error { return nil } -func (b *Bitmap) remove(v uint32) { +func (b *Bitmap) remove(v uint64) { hb := highbits(v) - i := search(b.keys, hb) + i := search64(b.keys, hb) if i < 0 { return } @@ -108,8 +108,8 @@ func (b *Bitmap) remove(v uint32) { } // Slice returns a slice of all integers in the bitmap. -func (b *Bitmap) Slice() []uint32 { - var a []uint32 +func (b *Bitmap) Slice() []uint64 { + var a []uint64 itr := b.iterator() for v := itr.Seek(0); !itr.EOF(); v = itr.Next() { a = append(a, v) @@ -118,8 +118,8 @@ func (b *Bitmap) Slice() []uint32 { } // SliceRange returns a slice of integers between [start, end). -func (b *Bitmap) SliceRange(start, end uint32) []uint32 { - var a []uint32 +func (b *Bitmap) SliceRange(start, end uint64) []uint64 { + var a []uint64 itr := b.iterator() for v := itr.Seek(start); !itr.EOF() && v < end; v = itr.Next() { a = append(a, v) @@ -128,7 +128,7 @@ func (b *Bitmap) SliceRange(start, end uint32) []uint32 { } // ForEach executes fn for each value in the bitmap. -func (b *Bitmap) ForEach(fn func(uint32)) { +func (b *Bitmap) ForEach(fn func(uint64)) { itr := b.iterator() for v := itr.Seek(0); !itr.EOF(); v = itr.Next() { fn(v) @@ -136,7 +136,7 @@ func (b *Bitmap) ForEach(fn func(uint32)) { } // ForEachRange executes fn for each value in the bitmap between [start, end). -func (b *Bitmap) ForEachRange(start, end uint32, fn func(uint32)) { +func (b *Bitmap) ForEachRange(start, end uint64, fn func(uint64)) { itr := b.iterator() for v := itr.Seek(start); !itr.EOF() && v < end; v = itr.Next() { fn(v) @@ -144,15 +144,15 @@ func (b *Bitmap) ForEachRange(start, end uint32, fn func(uint32)) { } // container returns the container with the given key. -func (b *Bitmap) container(key uint16) *container { - i := search(b.keys, key) +func (b *Bitmap) container(key uint64) *container { + i := search64(b.keys, key) if i < 0 { return nil } return b.containers[i] } -func (b *Bitmap) insertAt(key uint16, c *container, i int) { +func (b *Bitmap) insertAt(key uint64, c *container, i int) { b.keys = append(b.keys, 0) copy(b.keys[i+1:], b.keys[i:]) b.keys[i] = key @@ -165,20 +165,20 @@ func (b *Bitmap) insertAt(key uint16, c *container, i int) { // WriteTo writes b to w. func (b *Bitmap) WriteTo(w io.Writer) (n int64, err error) { // Build header before writing individual container blocks. - buf := make([]byte, headerSize+(len(b.keys)*(2+2+4))) + buf := make([]byte, headerSize+(len(b.keys)*(2+8+4))) binary.LittleEndian.PutUint32(buf[0:], cookie) binary.LittleEndian.PutUint32(buf[4:], uint32(len(b.keys))) // Encode keys and cardinality. for i, key := range b.keys { - binary.LittleEndian.PutUint16(buf[headerSize+i*4:], uint16(key)) - binary.LittleEndian.PutUint16(buf[headerSize+i*4+2:], uint16(b.containers[i].n-1)) + binary.LittleEndian.PutUint64(buf[headerSize+i*10:], uint64(key)) + binary.LittleEndian.PutUint16(buf[headerSize+i*10+8:], uint16(b.containers[i].n-1)) } // Write the offset for each container block. offset := uint32(len(buf)) for i, c := range b.containers { - binary.LittleEndian.PutUint32(buf[headerSize+(len(b.keys)*4)+(i*4):], uint32(offset)) + binary.LittleEndian.PutUint32(buf[headerSize+(len(b.keys)*10)+(i*4):], uint32(offset)) offset += uint32(c.size()) } @@ -214,21 +214,21 @@ func (b *Bitmap) UnmarshalBinary(data []byte) error { // Read key count. keyN := binary.LittleEndian.Uint32(data[4:8]) - b.keys = make([]uint16, keyN) + b.keys = make([]uint64, keyN) b.containers = make([]*container, keyN) // Read container key headers. - for i, buf := 0, data[8:]; i < int(keyN); i, buf = i+1, buf[4:] { - b.keys[i] = binary.LittleEndian.Uint16(buf[0:2]) + for i, buf := 0, data[8:]; i < int(keyN); i, buf = i+1, buf[10:] { + b.keys[i] = binary.LittleEndian.Uint64(buf[0:8]) b.containers[i] = &container{ - n: int(binary.LittleEndian.Uint16(buf[2:4])) + 1, + n: int(binary.LittleEndian.Uint16(buf[8:10])) + 1, mapped: true, } } // Read container offsets and attach data. - opsOffset := 8 + int(keyN)*4 - for i, buf := 0, data[8+int(keyN)*4:]; i < int(keyN); i, buf = i+1, buf[4:] { + opsOffset := 8 + int(keyN)*10 + for i, buf := 0, data[opsOffset:]; i < int(keyN); i, buf = i+1, buf[4:] { offset := binary.LittleEndian.Uint32(buf[0:4]) // Verify the offset is within the bounds of the input data. @@ -297,9 +297,9 @@ type iterator struct { func (itr *iterator) EOF() bool { return itr.i >= len(itr.bitmap.containers) } // Seek moves to the first value equal to or greater than v. -func (itr *iterator) Seek(seek uint32) uint32 { +func (itr *iterator) Seek(seek uint64) uint64 { // Move to the correct container. - itr.i = search(itr.bitmap.keys, highbits(seek)) + itr.i = search64(itr.bitmap.keys, highbits(seek)) if itr.i < 0 { itr.i = -itr.i - 1 } @@ -311,7 +311,7 @@ func (itr *iterator) Seek(seek uint32) uint32 { lb := lowbits(seek) if c := itr.bitmap.containers[itr.i]; c.isArray() { // Find index in the container. - itr.j = search(c.array, lb) + itr.j = search16(c.array, lb) if itr.j < 0 { itr.j = -itr.j - 1 } @@ -330,7 +330,7 @@ func (itr *iterator) Seek(seek uint32) uint32 { } // Next returns the next value in the bitmap. -func (itr *iterator) Next() uint32 { +func (itr *iterator) Next() uint64 { // Iterate over containers until we find the next value or EOF. for { if itr.EOF() { @@ -373,13 +373,13 @@ func (itr *iterator) Next() uint32 { } // peek returns the current value. -func (itr *iterator) peek() uint32 { +func (itr *iterator) peek() uint64 { key := itr.bitmap.keys[itr.i] c := itr.bitmap.containers[itr.i] if c.isArray() { - return uint32(key)<<16 | uint32(c.array[itr.j]) + return uint64(key)<<16 | uint64(c.array[itr.j]) } - return uint32(key)<<16 | uint32(itr.j) + return uint64(key)<<16 | uint64(itr.j) } // The maximum size of array containers. @@ -447,7 +447,7 @@ func (c *container) arrayAdd(v uint16) { } // Find index of the integer in the container. Exit if it already exists. - i := search(c.array, v) + i := search16(c.array, v) if i >= 0 { return } @@ -486,7 +486,7 @@ func (c *container) contains(v uint16) bool { } func (c *container) arrayContains(v uint16) bool { - return search(c.array, v) >= 0 + return search16(c.array, v) >= 0 } func (c *container) bitmapContains(v uint16) bool { @@ -503,7 +503,7 @@ func (c *container) remove(v uint16) { } func (c *container) arrayRemove(v uint16) { - i := search(c.array, v) + i := search16(c.array, v) if i < 0 { return } @@ -590,7 +590,7 @@ const ( // op represents an operation on the bitmap. type op struct { typ opType - value uint32 + value uint64 } // apply executes the operation against a bitmap. @@ -611,12 +611,12 @@ func (op *op) WriteTo(w io.Writer) (n int64, err error) { // Write type and value. buf[0] = byte(op.typ) - binary.LittleEndian.PutUint32(buf[1:5], op.value) + binary.LittleEndian.PutUint64(buf[1:9], op.value) // Add checksum at the end. h := fnv.New32a() - h.Write(buf[0:5]) - binary.LittleEndian.PutUint32(buf[5:9], h.Sum32()) + h.Write(buf[0:9]) + binary.LittleEndian.PutUint32(buf[9:13], h.Sum32()) // Write to writer. nn, err := w.Write(buf) @@ -631,26 +631,63 @@ func (op *op) UnmarshalBinary(data []byte) error { // Verify checksum. h := fnv.New32a() - h.Write(data[0:5]) - if chk := binary.LittleEndian.Uint32(data[5:9]); chk != h.Sum32() { + h.Write(data[0:9]) + if chk := binary.LittleEndian.Uint32(data[9:13]); chk != h.Sum32() { return fmt.Errorf("checksum mismatch: exp=%08x, got=%08x", h.Sum32(), chk) } // Read type and value. op.typ = opType(data[0]) - op.value = binary.LittleEndian.Uint32(data[1:5]) + op.value = binary.LittleEndian.Uint64(data[1:9]) return nil } // size returns the encoded size of the op, in bytes. -func (*op) size() int { return 1 + 4 + 4 } +func (*op) size() int { return 1 + 8 + 4 } -func highbits(v uint32) uint16 { return uint16(v >> 16) } -func lowbits(v uint32) uint16 { return uint16(v & 0xFFFF) } +func highbits(v uint64) uint64 { return uint64(v >> 16) } +func lowbits(v uint64) uint16 { return uint16(v & 0xFFFF) } -// search returns the index of v in a. -func search(a []uint16, value uint16) int { +// search16 returns the index of v in a. +func search16(a []uint16, value uint16) int { + // Optimize for elements and the last element. + n := len(a) + if n == 0 { + return -1 + } else if a[n-1] == value { + return n - 1 + } + + // Otherwise perform binary search for exact match. + lo, hi := 0, n-1 + for lo+16 <= hi { + i := int(uint((lo + hi)) >> 1) + v := a[i] + + if v < value { + lo = i + 1 + } else if v > value { + hi = i - 1 + } else { + return i + } + } + + // If an exact match isn't found then return a negative index. + for ; lo <= hi; lo++ { + v := a[lo] + if v == value { + return lo + } else if v > value { + break + } + } + return -(lo + 1) +} + +// search64 returns the index of v in a. +func search64(a []uint64, value uint64) int { // Optimize for elements and the last element. n := len(a) if n == 0 { diff --git a/roaring/roaring_test.go b/roaring/roaring_test.go index 5bd4dd490..8e008845d 100644 --- a/roaring/roaring_test.go +++ b/roaring/roaring_test.go @@ -13,17 +13,72 @@ import ( "github.com/umbel/pilosa/roaring" ) +// Ensure an empty bitmap returns false if checking for existence. +func TestBitmap_Contains_Empty(t *testing.T) { + if roaring.NewBitmap().Contains(1000) { + t.Fatal("expected false") + } +} + +// Ensure an empty bitmap does nothing when removing an element. +func TestBitmap_Remove_Empty(t *testing.T) { + roaring.NewBitmap().Remove(1000) +} + +// Ensure a bitmap can return a slice of values. +func TestBitmap_Slice(t *testing.T) { + if a := roaring.NewBitmap(1, 2, 3).Slice(); !reflect.DeepEqual(a, []uint64{1, 2, 3}) { + t.Fatalf("unexpected slice: %+v", a) + } +} + +// Ensure an empty bitmap returns an empty slice of values. +func TestBitmap_Slice_Empty(t *testing.T) { + if a := roaring.NewBitmap().Slice(); len(a) != 0 { + t.Fatalf("unexpected slice: %+v", a) + } +} + +// Ensure a bitmap can return a slice of values within a range. +func TestBitmap_SliceRange(t *testing.T) { + if a := roaring.NewBitmap(0, 1000001, 1000002, 1000003).SliceRange(1, 1000003); !reflect.DeepEqual(a, []uint64{1000001, 1000002}) { + t.Fatalf("unexpected slice: %+v", a) + } +} + +// Ensure a bitmap can loop over a set of values. +func TestBitmap_ForEach(t *testing.T) { + var a []uint64 + roaring.NewBitmap(1, 2, 3).ForEach(func(v uint64) { + a = append(a, v) + }) + if !reflect.DeepEqual(a, []uint64{1, 2, 3}) { + t.Fatalf("unexpected values: %+v", a) + } +} + +// Ensure a bitmap can loop over a set of values in a range. +func TestBitmap_ForEachRange(t *testing.T) { + var a []uint64 + roaring.NewBitmap(1, 2, 3, 4).ForEachRange(2, 4, func(v uint64) { + a = append(a, v) + }) + if !reflect.DeepEqual(a, []uint64{2, 3}) { + t.Fatalf("unexpected values: %+v", a) + } +} + func TestBitmap_Quick_Array1(t *testing.T) { testBitmapQuick(t, 1000, 1000, 2000) } func TestBitmap_Quick_Array2(t *testing.T) { testBitmapQuick(t, 10000, 0, 1000) } func TestBitmap_Quick_Bitmap1(t *testing.T) { testBitmapQuick(t, 10000, 0, 10000) } func TestBitmap_Quick_Bitmap2(t *testing.T) { testBitmapQuick(t, 10000, 10000, 20000) } -func TestBitmap_Quick_LargeValue(t *testing.T) { testBitmapQuick(t, 10000, 0, math.MaxUint32) } +func TestBitmap_Quick_LargeValue(t *testing.T) { testBitmapQuick(t, 10000, 0, math.MaxInt64) } // Ensure a bitmap can perform basic operations on randomly generated values. -func testBitmapQuick(t *testing.T, n int, min, max uint32) { - quick.Check(func(a []uint32) bool { +func testBitmapQuick(t *testing.T, n int, min, max uint64) { + quick.Check(func(a []uint64) bool { bm := roaring.NewBitmap() - m := make(map[uint32]struct{}) + m := make(map[uint64]struct{}) // Add values to the bitmap and set. for _, v := range a { @@ -45,7 +100,7 @@ func testBitmapQuick(t *testing.T, n int, min, max uint32) { } // Verify slices are equal. - if got, exp := bm.Slice(), uint32SetSlice(m); !reflect.DeepEqual(got, exp) { + if got, exp := bm.Slice(), uint64SetSlice(m); !reflect.DeepEqual(got, exp) { t.Fatalf("unexpected values:\n\ngot=%+v\n\nexp=%+v\n\n", got, exp) } @@ -62,85 +117,30 @@ func testBitmapQuick(t *testing.T, n int, min, max uint32) { return true }, &quick.Config{ Values: func(values []reflect.Value, rand *rand.Rand) { - values[0] = reflect.ValueOf(GenerateUint32Slice(n, min, max, rand)) + values[0] = reflect.ValueOf(GenerateUint64Slice(n, min, max, rand)) }, }) } -// Ensure an empty bitmap returns false if checking for existence. -func TestBitmap_Contains_Empty(t *testing.T) { - if roaring.NewBitmap().Contains(1000) { - t.Fatal("expected false") - } -} - -// Ensure an empty bitmap does nothing when removing an element. -func TestBitmap_Remove_Empty(t *testing.T) { - roaring.NewBitmap().Remove(1000) -} - -// Ensure a bitmap can return a slice of values. -func TestBitmap_Slice(t *testing.T) { - if a := roaring.NewBitmap(1, 2, 3).Slice(); !reflect.DeepEqual(a, []uint32{1, 2, 3}) { - t.Fatalf("unexpected slice: %+v", a) - } -} - -// Ensure an empty bitmap returns an empty slice of values. -func TestBitmap_Slice_Empty(t *testing.T) { - if a := roaring.NewBitmap().Slice(); len(a) != 0 { - t.Fatalf("unexpected slice: %+v", a) - } -} - -// Ensure a bitmap can return a slice of values within a range. -func TestBitmap_SliceRange(t *testing.T) { - if a := roaring.NewBitmap(0, 1000001, 1000002, 1000003).SliceRange(1, 1000003); !reflect.DeepEqual(a, []uint32{1000001, 1000002}) { - t.Fatalf("unexpected slice: %+v", a) - } -} - -// Ensure a bitmap can loop over a set of values. -func TestBitmap_ForEach(t *testing.T) { - var a []uint32 - roaring.NewBitmap(1, 2, 3).ForEach(func(v uint32) { - a = append(a, v) - }) - if !reflect.DeepEqual(a, []uint32{1, 2, 3}) { - t.Fatalf("unexpected values: %+v", a) - } -} - -// Ensure a bitmap can loop over a set of values in a range. -func TestBitmap_ForEachRange(t *testing.T) { - var a []uint32 - roaring.NewBitmap(1, 2, 3, 4).ForEachRange(2, 4, func(v uint32) { - a = append(a, v) - }) - if !reflect.DeepEqual(a, []uint32{2, 3}) { - t.Fatalf("unexpected values: %+v", a) - } -} - func TestBitmap_Marshal_Quick_Array1(t *testing.T) { testBitmapMarshalQuick(t, 1000, 1000, 2000) } func TestBitmap_Marshal_Quick_Array2(t *testing.T) { testBitmapMarshalQuick(t, 10000, 0, 1000) } func TestBitmap_Marshal_Quick_Bitmap1(t *testing.T) { testBitmapMarshalQuick(t, 10000, 0, 10000) } func TestBitmap_Marshal_Quick_Bitmap2(t *testing.T) { testBitmapMarshalQuick(t, 10000, 10000, 20000) } func TestBitmap_Marshal_Quick_LargeValue(t *testing.T) { - testBitmapMarshalQuick(t, 100, 0, math.MaxUint32) + testBitmapMarshalQuick(t, 100, 0, math.MaxInt64) } // Ensure a bitmap can be marshaled and unmarshaled. -func testBitmapMarshalQuick(t *testing.T, n int, min, max uint32) { +func testBitmapMarshalQuick(t *testing.T, n int, min, max uint64) { if testing.Short() { t.Skip("short") } - quick.Check(func(a0, a1 []uint32) bool { + quick.Check(func(a0, a1 []uint64) bool { // Create bitmap with initial values set. bm := roaring.NewBitmap(a0...) - set := make(map[uint32]struct{}) + set := make(map[uint64]struct{}) for _, v := range a0 { set[v] = struct{}{} } @@ -173,12 +173,12 @@ func testBitmapMarshalQuick(t *testing.T, n int, min, max uint32) { } // Verify the original bitmap has the correct set of values. - if exp, got := uint32SetSlice(set), bm.Slice(); !reflect.DeepEqual(exp, got) { + if exp, got := uint64SetSlice(set), bm.Slice(); !reflect.DeepEqual(exp, got) { t.Fatalf("mismatch: %s\n\nexp=%+v\n\ngot=%+v\n\n", diff(exp, got), exp, got) } // Verify the bitmap loaded with the ops log has the correct set of values. - if exp, got := uint32SetSlice(set), bm2.Slice(); !reflect.DeepEqual(exp, got) { + if exp, got := uint64SetSlice(set), bm2.Slice(); !reflect.DeepEqual(exp, got) { t.Fatalf("mismatch: %s\n\nexp=%+v\n\ngot=%+v\n\n", diff(exp, got), exp, got) } } @@ -186,39 +186,39 @@ func testBitmapMarshalQuick(t *testing.T, n int, min, max uint32) { return true }, &quick.Config{ Values: func(values []reflect.Value, rand *rand.Rand) { - values[0] = reflect.ValueOf(GenerateUint32Slice(n, min, max, rand)) - values[1] = reflect.ValueOf(GenerateUint32Slice(100, min, max, rand)) + values[0] = reflect.ValueOf(GenerateUint64Slice(n, min, max, rand)) + values[1] = reflect.ValueOf(GenerateUint64Slice(100, min, max, rand)) }, }) } -// GenerateUint32Slice generates between [0, n) random uint32 numbers between min and max. -func GenerateUint32Slice(n int, min, max uint32, rand *rand.Rand) []uint32 { - a := make([]uint32, rand.Intn(n)) +// GenerateUint64Slice generates between [0, n) random uint64 numbers between min and max. +func GenerateUint64Slice(n int, min, max uint64, rand *rand.Rand) []uint64 { + a := make([]uint64, rand.Intn(n)) for i := range a { - a[i] = min + uint32(rand.Intn(int(max-min))) + a[i] = min + uint64(rand.Int63n(int64(max-min))) } return a } -// uint32SetSlice returns the values in a uint32 set. -func uint32SetSlice(m map[uint32]struct{}) []uint32 { - a := make([]uint32, 0, len(m)) +// uint64SetSlice returns the values in a uint64 set. +func uint64SetSlice(m map[uint64]struct{}) []uint64 { + a := make([]uint64, 0, len(m)) for v := range m { a = append(a, v) } - sort.Sort(uint32Slice(a)) + sort.Sort(uint64Slice(a)) return a } -// uint32Slice represents a sortable slice of uint32 numbers. -type uint32Slice []uint32 +// uint64Slice represents a sortable slice of uint64 numbers. +type uint64Slice []uint64 -func (p uint32Slice) Swap(i, j int) { p[i], p[j] = p[j], p[i] } -func (p uint32Slice) Len() int { return len(p) } -func (p uint32Slice) Less(i, j int) bool { return p[i] < p[j] } +func (p uint64Slice) Swap(i, j int) { p[i], p[j] = p[j], p[i] } +func (p uint64Slice) Len() int { return len(p) } +func (p uint64Slice) Less(i, j int) bool { return p[i] < p[j] } -func diff(a, b []uint32) string { +func diff(a, b []uint64) string { if len(a) != len(b) { return fmt.Sprintf("len: %d != %d", len(a), len(b)) }