mirror of
https://github.com/hanzoai/graph.git
synced 2026-10-30 18:35:37 +00:00
Derived from Dgraph (github.com/dgraph-io/dgraph) at commit 64804bd2, Apache-2.0. See NOTICE. WHAT THIS IS. The graph engine as a Go library: posting lists, the DQL parser, the type and tokenizer system, the index and schema layers, and the wire types. 39,170 lines of the upstream's 166,337. THE STORE is github.com/luxfi/zapdb, a Badger fork that kept the package name, the subpackages (options, pb, y) and the API — Open, OpenManaged, DB, Options — so the 74 import sites are aliased rather than rewritten. THE CODEC is ZAP. zapdb serializes with ZAP, not protobuf: its pb package builds the ZAP types by default and the protobuf ones only under `-tags grpc`, a path that does not compile (table/builder.go calls pb.Marshal, which that tag removes). One call site in posting needed converting; every other type here is Hanzo's own protobuf and is untouched. NO TRANSPORT. An embedded engine dials nothing, so nothing here imports google.golang.org/grpc. Removed: 20 transport functions in x, the generated service stubs for Raft, Zero and Worker (1,657 lines), and the JWT helper — identity belongs to IAM. The core called none of them. NOT KEPT: worker, conn, raftwal, the cluster commands, the GraphQL layer and the test harnesses. `query`, the DQL execution engine, went with them because it depends on worker; parsing DQL is available here, executing it is not. The history of the upstream project lives at github.com/dgraph-io/dgraph. This repository begins at the derivation because three quarters of that history describes code this fork does not carry.
308 lines
8.3 KiB
Go
308 lines
8.3 KiB
Go
/*
|
|
* SPDX-FileCopyrightText: © 2017-2026 Istari Digital, Inc.
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
package algo
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"errors"
|
|
"fmt"
|
|
"hash"
|
|
"hash/fnv"
|
|
"io"
|
|
"math"
|
|
)
|
|
|
|
// This code is copied from https://www.github.com/tylertreat/BoomFilters/refs/heads/master/countmin.go
|
|
// CountMinSketch implements a Count-Min Sketch as described by Cormode and
|
|
// Muthukrishnan in An Improved Data Stream Summary: The Count-Min Sketch and
|
|
// its Applications:
|
|
//
|
|
// http://dimacs.rutgers.edu/~graham/pubs/papers/cm-full.pdf
|
|
//
|
|
// A Count-Min Sketch (CMS) is a probabilistic data structure which
|
|
// approximates the frequency of events in a data stream. Unlike a hash map, a
|
|
// CMS uses sub-linear space at the expense of a configurable error factor.
|
|
// Similar to Counting Bloom filters, items are hashed to a series of buckets,
|
|
// which increment a counter. The frequency of an item is estimated by taking
|
|
// the minimum of each of the item's respective counter values.
|
|
//
|
|
// Count-Min Sketches are useful for counting the frequency of events in
|
|
// massive data sets or unbounded streams online. In these situations, storing
|
|
// the entire data set or allocating counters for every event in memory is
|
|
// impractical. It may be possible for offline processing, but real-time
|
|
// processing requires fast, space-efficient solutions like the CMS. For
|
|
// approximating set cardinality, refer to the HyperLogLog.
|
|
type CountMinSketch struct {
|
|
matrix [][]uint64 // count matrix
|
|
width uint // matrix width
|
|
depth uint // matrix depth
|
|
count uint64 // number of items added
|
|
epsilon float64 // relative-accuracy factor
|
|
delta float64 // relative-accuracy probability
|
|
hash hash.Hash64 // hash function (kernel for all depth functions)
|
|
}
|
|
|
|
// hashKernel returns the upper and lower base hash values from which the k
|
|
// hashes are derived.
|
|
func hashKernel(data []byte, hash hash.Hash64) (uint32, uint32) {
|
|
hash.Write(data)
|
|
sum := hash.Sum64()
|
|
hash.Reset()
|
|
upper := uint32(sum & 0xffffffff)
|
|
lower := uint32((sum >> 32) & 0xffffffff)
|
|
return upper, lower
|
|
}
|
|
|
|
// NewCountMinSketch creates a new Count-Min Sketch whose relative accuracy is
|
|
// within a factor of epsilon with probability delta. Both of these parameters
|
|
// affect the space and time complexity.
|
|
func NewCountMinSketch(epsilon, delta float64) *CountMinSketch {
|
|
var (
|
|
width = uint(math.Ceil(math.E / epsilon))
|
|
depth = uint(math.Ceil(math.Log(1 / delta)))
|
|
matrix = make([][]uint64, depth)
|
|
)
|
|
|
|
for i := uint(0); i < depth; i++ {
|
|
matrix[i] = make([]uint64, width)
|
|
}
|
|
|
|
return &CountMinSketch{
|
|
matrix: matrix,
|
|
width: width,
|
|
depth: depth,
|
|
epsilon: epsilon,
|
|
delta: delta,
|
|
hash: fnv.New64(),
|
|
}
|
|
}
|
|
|
|
// Epsilon returns the relative-accuracy factor, epsilon.
|
|
func (c *CountMinSketch) Epsilon() float64 {
|
|
return c.epsilon
|
|
}
|
|
|
|
// Delta returns the relative-accuracy probability, delta.
|
|
func (c *CountMinSketch) Delta() float64 {
|
|
return c.delta
|
|
}
|
|
|
|
// TotalCount returns the number of items added to the sketch.
|
|
func (c *CountMinSketch) TotalCount() uint64 {
|
|
return c.count
|
|
}
|
|
|
|
// AddInt will add the data to the set n times. Returns the CountMinSketch to allow for
|
|
// chaining.
|
|
func (c *CountMinSketch) AddInt(data []byte, n uint64) *CountMinSketch {
|
|
lower, upper := hashKernel(data, c.hash)
|
|
existingValue := uint64(math.MaxUint64)
|
|
|
|
// Increment count in each row.
|
|
for i := uint(0); i < c.depth; i++ {
|
|
index := (uint(lower) + uint(upper)*i) % c.width
|
|
val := c.matrix[i][index]
|
|
if val < existingValue {
|
|
existingValue = val
|
|
}
|
|
if c.matrix[i][index] < n {
|
|
c.matrix[i][index] = n
|
|
}
|
|
}
|
|
|
|
if n > existingValue {
|
|
c.count += n - existingValue
|
|
}
|
|
return c
|
|
}
|
|
|
|
// Add will add the data to the set. Returns the CountMinSketch to allow for
|
|
// chaining.
|
|
func (c *CountMinSketch) Add(data []byte) *CountMinSketch {
|
|
lower, upper := hashKernel(data, c.hash)
|
|
|
|
// Increment count in each row.
|
|
for i := uint(0); i < c.depth; i++ {
|
|
c.matrix[i][(uint(lower)+uint(upper)*i)%c.width]++
|
|
}
|
|
|
|
c.count++
|
|
return c
|
|
}
|
|
|
|
// Count returns the approximate count for the specified item, correct within
|
|
// epsilon * total count with a probability of delta.
|
|
func (c *CountMinSketch) Count(data []byte) uint64 {
|
|
var (
|
|
lower, upper = hashKernel(data, c.hash)
|
|
count = uint64(math.MaxUint64)
|
|
)
|
|
|
|
for i := uint(0); i < c.depth; i++ {
|
|
count = uint64(math.Min(float64(count),
|
|
float64(c.matrix[i][(uint(lower)+uint(upper)*i)%c.width])))
|
|
}
|
|
|
|
return count
|
|
}
|
|
|
|
// Merge combines this CountMinSketch with another. Returns an error if the
|
|
// matrix width and depth are not equal.
|
|
func (c *CountMinSketch) Merge(other *CountMinSketch) error {
|
|
if c.depth != other.depth {
|
|
return errors.New("matrix depth must match")
|
|
}
|
|
|
|
if c.width != other.width {
|
|
return errors.New("matrix width must match")
|
|
}
|
|
|
|
for i := uint(0); i < c.depth; i++ {
|
|
for j := uint(0); j < c.width; j++ {
|
|
c.matrix[i][j] += other.matrix[i][j]
|
|
}
|
|
}
|
|
|
|
c.count += other.count
|
|
return nil
|
|
}
|
|
|
|
// Reset restores the CountMinSketch to its original state. It returns itself
|
|
// to allow for chaining.
|
|
func (c *CountMinSketch) Reset() *CountMinSketch {
|
|
for i := 0; i < len(c.matrix); i++ {
|
|
for j := 0; j < len(c.matrix[i]); j++ {
|
|
c.matrix[i][j] = 0
|
|
}
|
|
}
|
|
|
|
c.count = 0
|
|
return c
|
|
}
|
|
|
|
// SetHash sets the hashing function used.
|
|
func (c *CountMinSketch) SetHash(h hash.Hash64) {
|
|
c.hash = h
|
|
}
|
|
|
|
// WriteDataTo writes a binary representation of the CMS data to
|
|
// an io stream. It returns the number of bytes written and error
|
|
func (c *CountMinSketch) WriteDataTo(stream io.Writer) (int, error) {
|
|
buf := new(bytes.Buffer)
|
|
// serialize epsilon and delta as cms configuration check
|
|
err := binary.Write(buf, binary.LittleEndian, c.epsilon)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
err = binary.Write(buf, binary.LittleEndian, c.delta)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
err = binary.Write(buf, binary.LittleEndian, c.count)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
// encode matrix
|
|
for i := range c.matrix {
|
|
err = binary.Write(buf, binary.LittleEndian, c.matrix[i])
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
}
|
|
|
|
return stream.Write(buf.Bytes())
|
|
}
|
|
|
|
// ReadDataFrom reads a binary representation of the CMS data written
|
|
// by WriteDataTo() from io stream. It returns the number of bytes read
|
|
// and error
|
|
// If serialized CMS configuration is different it returns error with expected params
|
|
func (c *CountMinSketch) ReadDataFrom(stream io.Reader) (int, error) {
|
|
var (
|
|
count uint64
|
|
epsilon, delta float64
|
|
)
|
|
|
|
err := binary.Read(stream, binary.LittleEndian, &epsilon)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
err = binary.Read(stream, binary.LittleEndian, &delta)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
// check if serialized and target cms configurations are same
|
|
if c.epsilon != epsilon || c.delta != delta {
|
|
return 0, fmt.Errorf("expected cms values for epsilon %f and delta %f", epsilon, delta)
|
|
}
|
|
|
|
err = binary.Read(stream, binary.LittleEndian, &count)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
for i := uint(0); i < c.depth; i++ {
|
|
err = binary.Read(stream, binary.LittleEndian, c.matrix[i])
|
|
}
|
|
// count size of matrix and count
|
|
size := int(c.depth*c.width)*binary.Size(uint64(0)) + binary.Size(count) + 2*binary.Size(float64(0))
|
|
|
|
c.count = count
|
|
|
|
return size, err
|
|
}
|
|
|
|
// TestAndRemove attemps to remove n counts of data from the CMS. If
|
|
// n is greater than the data count, TestAndRemove is a no-op and
|
|
// returns false. Else, return true and decrement count by n.
|
|
func (c *CountMinSketch) TestAndRemove(data []byte, n uint64) bool {
|
|
h, count := c.traverseDepth(data)
|
|
|
|
if n > count {
|
|
return false
|
|
}
|
|
|
|
for i := uint(0); i < c.depth; i++ {
|
|
*h[i] -= n
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// TestAndRemoveAll counts data frequency, performs TestAndRemove(data, count),
|
|
// and returns true if count is positive. If count is 0, TestAndRemoveAll is a
|
|
// no-op and returns false.
|
|
func (c *CountMinSketch) TestAndRemoveAll(data []byte) bool {
|
|
h, count := c.traverseDepth(data)
|
|
|
|
if count == 0 {
|
|
return false
|
|
}
|
|
|
|
for i := uint(0); i < c.depth; i++ {
|
|
*h[i] -= count
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func (c *CountMinSketch) traverseDepth(data []byte) ([]*uint64, uint64) {
|
|
var (
|
|
lower, upper = hashKernel(data, c.hash)
|
|
count = uint64(math.MaxUint64)
|
|
h = make([]*uint64, c.depth)
|
|
)
|
|
|
|
for i := uint(0); i < c.depth; i++ {
|
|
h[i] = &c.matrix[i][(uint(lower)+uint(upper)*i)%c.width]
|
|
count = uint64(math.Min(float64(count), float64(*h[i])))
|
|
}
|
|
|
|
return h, count
|
|
}
|