1
0
mirror of https://github.com/sjwhitworth/golearn.git synced 2025-05-01 22:18:10 +08:00

base: Added support for grouping and storing BinaryAttributes

* Pond was renamed to FixedAttributeGroup.
* AttributeGroup interface.
* BinaryAttributeGroup introduced.
This commit is contained in:
Richard Townsend 2014-08-05 11:53:32 +01:00
parent d4ac151271
commit 8196db1230
6 changed files with 453 additions and 202 deletions

62
base/bag.go Normal file
View File

@ -0,0 +1,62 @@
package base
import (
"fmt"
)
// BinaryAttributeGroups contain only BinaryAttributes
// Compact each Attribute to a bit for better storage
type BinaryAttributeGroup struct {
FixedAttributeGroup
}
func (b *BinaryAttributeGroup) RowSize() int {
return (len(b.attributes) + 7) / 8
}
// String gets a human-readable view of this group
func (b *BinaryAttributeGroup) String() string {
if len(b.alloc) > 1 {
return fmt.Sprintf("BinaryAttributeGroup(%d attributes\n thread: %d\n size: %d\n)", len(b.attributes), b.threadNo, b.size)
}
return fmt.Sprintf("BinaryAttributeGroup(%d attributes\n thread: %d\n size: %d\n)", len(b.attributes), b.threadNo, b.size)
}
func (b *BinaryAttributeGroup) getByteOffset(col, row int) int {
return row*b.RowSize() + col/8
}
func (b *BinaryAttributeGroup) set(col, row int, val []byte) {
// Resolve the block
curBlock, blockOffset := b.resolveBlock(col, row)
// If the value is 1, OR it
if val[0] > 0 {
b.alloc[curBlock][blockOffset] |= (1 << (uint(col) % 8))
} else {
// Otherwise, AND its complement
b.alloc[curBlock][blockOffset] &= ^(1 << (uint(col) % 8))
}
row++
if row > b.maxRow {
b.maxRow = row
}
}
func (b *BinaryAttributeGroup) resolveBlock(col, row int) (int, int) {
byteOffset := row*b.RowSize() + (col / 3)
rowSize := b.RowSize()
return b.FixedAttributeGroup.resolveBlockFromByteOffset(byteOffset, rowSize)
}
func (b *BinaryAttributeGroup) get(col, row int) []byte {
curBlock, blockOffset := b.resolveBlock(col, row)
if b.alloc[curBlock][blockOffset]&(1<<(uint(col%8))) > 0 {
return []byte{1}
} else {
return []byte{0}
}
}

136
base/bag_test.go Normal file
View File

@ -0,0 +1,136 @@
package base
import (
"fmt"
. "github.com/smartystreets/goconvey/convey"
"math/rand"
"testing"
)
func TestBAGSimple(t *testing.T) {
Convey("Given certain bit data", t, func() {
// Generate said bits
bVals := [][]byte{
[]byte{1, 0, 0},
[]byte{0, 1, 0},
[]byte{0, 0, 1},
}
// Create a new DenseInstances
inst := NewDenseInstances()
for i := 0; i < 3; i++ {
inst.AddAttribute(NewBinaryAttribute(fmt.Sprintf("%d", i)))
}
// Get and re-order the attributes
attrSpecsUnordered := ResolveAllAttributes(inst)
attrSpecs := make([]AttributeSpec, 3)
for _, a := range attrSpecsUnordered {
name := a.GetAttribute().GetName()
if name == "0" {
attrSpecs[0] = a
} else if name == "1" {
attrSpecs[1] = a
} else if name == "2" {
attrSpecs[2] = a
} else {
panic(name)
}
}
inst.Extend(3)
for row, b := range bVals {
for col, c := range b {
inst.Set(attrSpecs[col], row, []byte{c})
}
}
Convey("All the row values should be the right length...", func() {
inst.MapOverRows(attrSpecs, func(row [][]byte, i int) (bool, error) {
for i := range attrSpecs {
So(len(row[i]), ShouldEqual, 1)
}
return true, nil
})
})
Convey("All the values should be the same...", func() {
inst.MapOverRows(attrSpecs, func(row [][]byte, i int) (bool, error) {
for j := range attrSpecs {
So(row[j][0], ShouldEqual, bVals[i][j])
}
return true, nil
})
})
})
}
func TestBAG(t *testing.T) {
Convey("Given randomly generated bit data", t, func() {
// Generate said bits
bVals := make([][]byte, 0)
for i := 0; i < 50; i++ {
b := make([]byte, 3)
for j := 0; j < 3; j++ {
if rand.NormFloat64() >= 0 {
b[j] = byte(1)
} else {
b[j] = byte(0)
}
}
bVals = append(bVals, b)
}
// Create a new DenseInstances
inst := NewDenseInstances()
for i := 0; i < 3; i++ {
inst.AddAttribute(NewBinaryAttribute(fmt.Sprintf("%d", i)))
}
// Get and re-order the attributes
attrSpecsUnordered := ResolveAllAttributes(inst)
attrSpecs := make([]AttributeSpec, 3)
for _, a := range attrSpecsUnordered {
name := a.GetAttribute().GetName()
if name == "0" {
attrSpecs[0] = a
} else if name == "1" {
attrSpecs[1] = a
} else if name == "2" {
attrSpecs[2] = a
} else {
panic(name)
}
}
inst.Extend(50)
for row, b := range bVals {
for col, c := range b {
inst.Set(attrSpecs[col], row, []byte{c})
}
}
Convey("All the row values should be the right length...", func() {
inst.MapOverRows(attrSpecs, func(row [][]byte, i int) (bool, error) {
for i := range attrSpecs {
So(len(row[i]), ShouldEqual, 1)
}
return true, nil
})
})
Convey("All the values should be the same...", func() {
inst.MapOverRows(attrSpecs, func(row [][]byte, i int) (bool, error) {
for j := range attrSpecs {
So(row[j][0], ShouldEqual, bVals[i][j])
}
return true, nil
})
})
})
}

View File

@ -12,8 +12,8 @@ import (
// in a large grid.
type DenseInstances struct {
storage *edf.EdfFile
pondMap map[string]int
ponds []*Pond
agMap map[string]int
ags []AttributeGroup
lock sync.Mutex
fixed bool
classAttrs map[AttributeSpec]bool
@ -31,7 +31,7 @@ func NewDenseInstances() *DenseInstances {
return &DenseInstances{
storage,
make(map[string]int),
make([]*Pond, 0),
make([]AttributeGroup, 0),
sync.Mutex{},
false,
make(map[AttributeSpec]bool),
@ -41,12 +41,15 @@ func NewDenseInstances() *DenseInstances {
}
//
// Pond functions
// AttributeGroup functions
//
// createPond adds a new Pond to this set of Instances
// createAttributeGroup adds a new AttributeGroup to this set of Instances
// IMPORTANT: do not call unless you've acquired the lock
func (inst *DenseInstances) createPond(name string, size int) {
func (inst *DenseInstances) createAttributeGroup(name string, size int) {
var agAdd AttributeGroup
if inst.fixed {
panic("Can't add additional Attributes")
}
@ -65,7 +68,7 @@ func (inst *DenseInstances) createPond(name string, size int) {
}
}
if ok {
panic("Can't create pond: pond thread already exists")
panic("Can't create AttributeGroup: thread already exists")
}
// Write the pool's thread into the file
@ -75,28 +78,38 @@ func (inst *DenseInstances) createPond(name string, size int) {
panic(fmt.Sprintf("Can't write thread: %s", err))
}
// Create the pond information
pond := new(Pond)
pond.threadNo = thread.GetId()
pond.parent = inst
pond.attributes = make([]Attribute, 0)
pond.size = size
pond.alloc = make([][]byte, 0)
// Store within instances
inst.pondMap[name] = len(inst.ponds)
inst.ponds = append(inst.ponds, pond)
// Create the AttributeGroup information
if size != 0 {
ag := new(FixedAttributeGroup)
ag.threadNo = thread.GetId()
ag.parent = inst
ag.attributes = make([]Attribute, 0)
ag.size = size
ag.alloc = make([][]byte, 0)
agAdd = ag
} else {
ag := new(BinaryAttributeGroup)
ag.threadNo = thread.GetId()
ag.parent = inst
ag.attributes = make([]Attribute, 0)
ag.size = size
ag.alloc = make([][]byte, 0)
agAdd = ag
}
inst.agMap[name] = len(inst.ags)
inst.ags = append(inst.ags, agAdd)
}
// CreatePond adds a new Pond to this set of instances
// with a given name. If the size is 0, a bit-pond is added
// if the size of not 0, then the size of each pond attribute
// CreateAttributeGroup adds a new AttributeGroup to this set of instances
// with a given name. If the size is 0, a bit-ag is added
// if the size of not 0, then the size of each ag attribute
// is set to that number of bytes.
func (inst *DenseInstances) CreatePond(name string, size int) (err error) {
func (inst *DenseInstances) CreateAttributeGroup(name string, size int) (err error) {
defer func() {
if r := recover(); r != nil {
var ok bool
if err, ok = r.(error); !ok {
err = fmt.Errorf("CreatePond: %v (not created)", r)
err = fmt.Errorf("CreateAttributeGroup: %v (not created)", r)
}
}
}()
@ -104,21 +117,21 @@ func (inst *DenseInstances) CreatePond(name string, size int) (err error) {
inst.lock.Lock()
defer inst.lock.Unlock()
inst.createPond(name, size)
inst.createAttributeGroup(name, size)
return nil
}
// GetPond returns a reference to a Pond of a given name /
func (inst *DenseInstances) GetPond(name string) (*Pond, error) {
// GetAttributeGroup returns a reference to a AttributeGroup of a given name /
func (inst *DenseInstances) GetAttributeGroup(name string) (AttributeGroup, error) {
inst.lock.Lock()
defer inst.lock.Unlock()
// Check if the pond exists
if id, ok := inst.pondMap[name]; !ok {
return nil, fmt.Errorf("Pond '%s' doesn't exist", name)
// Check if the ag exists
if id, ok := inst.agMap[name]; !ok {
return nil, fmt.Errorf("AttributeGroup '%s' doesn't exist", name)
} else {
// Return the pond
return inst.ponds[id], nil
// Return the ag
return inst.ags[id], nil
}
}
@ -127,7 +140,7 @@ func (inst *DenseInstances) GetPond(name string) (*Pond, error) {
//
// AddAttribute adds an Attribute to this set of DenseInstances
// Creates a default Pond for it if a suitable one doesn't exist.
// Creates a default AttributeGroup for it if a suitable one doesn't exist.
// Returns an AttributeSpec for subsequent Set() calls.
//
// IMPORTANT: will panic if storage has been allocated via Extend.
@ -139,48 +152,54 @@ func (inst *DenseInstances) AddAttribute(a Attribute) AttributeSpec {
panic("Can't add additional Attributes")
}
// Generate a default Pond name
pond := "FLOAT"
// Generate a default AttributeGroup name
ag := "FLOAT"
if _, ok := a.(*CategoricalAttribute); ok {
pond = "CAT"
ag = "CAT"
} else if _, ok := a.(*FloatAttribute); ok {
pond = "FLOAT"
ag = "FLOAT"
} else if _, ok := a.(*BinaryAttribute); ok {
ag = "BIN"
} else {
panic("Unrecognised Attribute type")
}
// Create the pond if it doesn't exist
if _, ok := inst.pondMap[pond]; !ok {
inst.createPond(pond, 8)
// Create the ag if it doesn't exist
if _, ok := inst.agMap[ag]; !ok {
if ag != "BIN" {
inst.createAttributeGroup(ag, 8)
} else {
inst.createAttributeGroup(ag, 0)
}
}
id := inst.pondMap[pond]
p := inst.ponds[id]
p.attributes = append(p.attributes, a)
id := inst.agMap[ag]
p := inst.ags[id]
p.AddAttribute(a)
inst.attributes = append(inst.attributes, a)
return AttributeSpec{id, len(p.attributes) - 1, a}
return AttributeSpec{id, len(p.Attributes()) - 1, a}
}
// AddAttributeToPond adds an Attribute to a given pond
func (inst *DenseInstances) AddAttributeToPond(newAttribute Attribute, pond string) (AttributeSpec, error) {
// AddAttributeToAttributeGroup adds an Attribute to a given ag
func (inst *DenseInstances) AddAttributeToAttributeGroup(newAttribute Attribute, ag string) (AttributeSpec, error) {
inst.lock.Lock()
defer inst.lock.Unlock()
// Check if the pond exists
if _, ok := inst.pondMap[pond]; !ok {
return AttributeSpec{-1, 0, nil}, fmt.Errorf("Pond '%s' doesn't exist. Call CreatePond() first", pond)
// Check if the ag exists
if _, ok := inst.agMap[ag]; !ok {
return AttributeSpec{-1, 0, nil}, fmt.Errorf("Pond '%s' doesn't exist. Call CreatePond() first", ag)
}
id := inst.pondMap[pond]
p := inst.ponds[id]
for i, a := range p.attributes {
id := inst.agMap[ag]
p := inst.ags[id]
for i, a := range p.Attributes() {
if !a.Compatable(newAttribute) {
return AttributeSpec{-1, 0, nil}, fmt.Errorf("Attribute %s is not compatable with %s in pond '%s' (position %d)", newAttribute, a, pond, i)
return AttributeSpec{-1, 0, nil}, fmt.Errorf("Attribute %s is not compatable with %s in pond '%s' (position %d)", newAttribute, a, ag, i)
}
}
p.attributes = append(p.attributes, newAttribute)
p.AddAttribute(newAttribute)
inst.attributes = append(inst.attributes, newAttribute)
return AttributeSpec{id, len(p.attributes) - 1, newAttribute}, nil
return AttributeSpec{id, len(p.Attributes()) - 1, newAttribute}, nil
}
// GetAttribute returns an Attribute equal to the argument.
@ -192,8 +211,8 @@ func (inst *DenseInstances) GetAttribute(get Attribute) (AttributeSpec, error) {
inst.lock.Lock()
defer inst.lock.Unlock()
for i, p := range inst.ponds {
for j, a := range p.attributes {
for i, p := range inst.ags {
for j, a := range p.Attributes() {
if a.Equals(get) {
return AttributeSpec{i, j, a}, nil
}
@ -209,8 +228,8 @@ func (inst *DenseInstances) AllAttributes() []Attribute {
defer inst.lock.Unlock()
ret := make([]Attribute, 0)
for _, p := range inst.ponds {
for _, a := range p.attributes {
for _, p := range inst.ags {
for _, a := range p.Attributes() {
ret = append(ret, a)
}
}
@ -280,10 +299,9 @@ func (inst *DenseInstances) Extend(rows int) error {
// Get the size of each page
pageSize := inst.storage.GetPageSize()
for pondName := range inst.ponds {
p := inst.ponds[pondName]
for _, p := range inst.ags {
// Compute pond row storage requirements
// Compute ag row storage requirements
rowSize := p.RowSize()
// How many rows can we store per page?
@ -293,14 +311,14 @@ func (inst *DenseInstances) Extend(rows int) error {
pagesNeeded := uint32(math.Ceil(float64(rows) / rowsPerPage))
// Allocate those pages
r, err := inst.storage.AllocPages(pagesNeeded, p.threadNo)
r, err := inst.storage.AllocPages(pagesNeeded, p.getThreadNo())
if err != nil {
panic(fmt.Sprintf("Allocation error: %s (rowSize %d, pageSize %d, rowsPerPage %.2f, tried to allocate %d page(s) and extend by %d row(s))", err, rowSize, pageSize, rowsPerPage, pagesNeeded, rows))
}
// Resolve and assign those pages
byteBlock := inst.storage.ResolveRange(r)
for _, block := range byteBlock {
p.alloc = append(p.alloc, block)
p.addStorage(block)
}
}
inst.fixed = true
@ -319,45 +337,31 @@ func (inst *DenseInstances) Extend(rows int) error {
//
// IMPORTANT: Will panic() if the val is not the right length
func (inst *DenseInstances) Set(a AttributeSpec, row int, val []byte) {
inst.ponds[a.pond].set(a.position, row, val)
inst.ags[a.pond].set(a.position, row, val)
}
// Get gets a particular Attribute (given as an AttributeSpec) on a particular
// row.
// AttributeSpecs can be obtained using GetAttribute() or AddAttribute()
func (inst *DenseInstances) Get(a AttributeSpec, row int) []byte {
return inst.ponds[a.pond].get(a.position, row)
return inst.ags[a.pond].get(a.position, row)
}
// RowString returns a string representation of a given row.
func (inst *DenseInstances) RowString(row int) string {
var buffer bytes.Buffer
first := true
for name := range inst.ponds {
for _, p := range inst.ags {
if first {
first = false
} else {
buffer.WriteString(" ")
}
p := inst.ponds[name]
p.appendToRowBuf(row, &buffer)
}
return buffer.String()
}
//
// Row handling functions
//
func (inst *DenseInstances) allocateRowVector(asv []AttributeSpec) [][]byte {
ret := make([][]byte, len(asv))
for i, as := range asv {
p := inst.ponds[as.pond]
ret[i] = make([]byte, p.size)
}
return ret
}
// MapOverRows passes each row map into a function.
// First argument is a list of AttributeSpec in the order
// they're needed in for the function. The second is the function
@ -366,7 +370,7 @@ func (inst *DenseInstances) MapOverRows(asv []AttributeSpec, mapFunc func([][]by
rowBuf := make([][]byte, len(asv))
for i := 0; i < inst.maxRow; i++ {
for j, as := range asv {
p := inst.ponds[as.pond]
p := inst.ags[as.pond]
rowBuf[j] = p.get(as.position, i)
}
ok, err := mapFunc(rowBuf, i)

135
base/fixed.go Normal file
View File

@ -0,0 +1,135 @@
package base
import (
"bytes"
"fmt"
)
// FixedAttributeGroups contain a particular number of rows of
// a particular number of Attributes, all of a given type.
type FixedAttributeGroup struct {
threadNo uint32
parent DataGrid
attributes []Attribute
size int
alloc [][]byte
maxRow int
}
func (f *FixedAttributeGroup) String() string {
if len(f.alloc) > 1 {
return fmt.Sprintf("FixedAttributeGroup(%d attributes\n thread: %d\n size: %d\n)", len(f.attributes), f.threadNo, f.size)
}
return fmt.Sprintf("FixedAttributeGroup(%d attributes\n thread: %d\n size: %d\n %d \n)", len(f.attributes), f.threadNo, f.size, f.alloc[0][0:60])
}
// RowSize returns the size of each row in bytes
func (f *FixedAttributeGroup) RowSize() int {
return len(f.attributes) * f.size
}
// Attributes returns a slice of Attributes in this FixedAttributeGroup
func (f *FixedAttributeGroup) Attributes() []Attribute {
return f.attributes
}
// AddAttribute adds an attribute to this FixedAttributeGroup
func (f *FixedAttributeGroup) AddAttribute(a Attribute) error {
f.attributes = append(f.attributes, a)
return nil
}
// getThreadNo returns the ThreadNo assigned to this FixedAttributeGroup
func (f *FixedAttributeGroup) getThreadNo() uint32 {
return f.threadNo
}
// addStorage appends the given storage reference to this FixedAttributeGroup
func (f *FixedAttributeGroup) addStorage(a []byte) {
f.alloc = append(f.alloc, a)
}
// Storage returns a slice of FixedAttributeGroupStorageRefs which can
// be used to access the memory in this pond.
func (f *FixedAttributeGroup) Storage() []AttributeGroupStorageRef {
ret := make([]AttributeGroupStorageRef, len(f.alloc))
rowSize := f.RowSize()
for i, b := range f.alloc {
ret[i] = AttributeGroupStorageRef{b, len(b) / rowSize}
}
return ret
}
func (f *FixedAttributeGroup) resolveBlock(col int, row int) (int, int) {
if len(f.alloc) == 0 {
panic("No blocks to resolve")
}
// Find where in the pond the byte is
byteOffset := row*f.RowSize() + col*f.size
return f.resolveBlockFromByteOffset(byteOffset, f.RowSize())
}
func (f *FixedAttributeGroup) resolveBlockFromByteOffset(byteOffset, rowSize int) (int, int) {
curOffset := 0
curBlock := 0
blockOffset := 0
for {
if curBlock >= len(f.alloc) {
panic("Don't have enough blocks to fulfill")
}
// Rows are not allowed to span blocks
blockAdd := len(f.alloc[curBlock])
blockAdd -= blockAdd % rowSize
// Case 1: we need to skip this allocation
if curOffset+blockAdd < byteOffset {
curOffset += blockAdd
curBlock++
} else {
blockOffset = byteOffset - curOffset
break
}
}
return curBlock, blockOffset
}
func (f *FixedAttributeGroup) set(col int, row int, val []byte) {
// Double-check the length
if len(val) != f.size {
panic(fmt.Sprintf("Tried to call set() with %d bytes, should be %d", len(val), f.size))
}
// Find where in the pond the byte is
curBlock, blockOffset := f.resolveBlock(col, row)
// Copy the value in
copied := copy(f.alloc[curBlock][blockOffset:], val)
if copied != f.size {
panic(fmt.Sprintf("set() terminated by only copying %d bytes into the current block (should be %d). Check EDF allocation", copied, f.size))
}
row++
if row > f.maxRow {
f.maxRow = row
}
}
func (f *FixedAttributeGroup) get(col int, row int) []byte {
curBlock, blockOffset := f.resolveBlock(col, row)
return f.alloc[curBlock][blockOffset : blockOffset+f.size]
}
func (f *FixedAttributeGroup) appendToRowBuf(row int, buffer *bytes.Buffer) {
for i, a := range f.attributes {
postfix := " "
if i == len(f.attributes)-1 {
postfix = ""
}
buffer.WriteString(fmt.Sprintf("%s%s", a.GetStringFromSysVal(f.get(i, row)), postfix))
}
}

36
base/group.go Normal file
View File

@ -0,0 +1,36 @@
package base
import (
"bytes"
)
// AttributeGroups store related sequences of system values
// in memory for the DenseInstances structure.
type AttributeGroup interface {
// Returns an EDF thread number
getThreadNo() uint32
addStorage(a []byte)
// Used for printing
appendToRowBuf(row int, buffer *bytes.Buffer)
// Adds a new Attribute
AddAttribute(Attribute) error
// Returns all Attributes
Attributes() []Attribute
// Gets the byte slice at a given column, row offset
get(int, int) []byte
// Stores the byte slice at a given column, row offset
set(int, int, []byte)
// Gets the size of each row in bytes (rounded up)
RowSize() int
// Gets references to underlying memory
Storage() []AttributeGroupStorageRef
// Returns a human-readable summary
String() string
}
// AttributeGroupStorageRef is a reference to a particular set
// of allocated rows within a FixedAttributeGroup
type AttributeGroupStorageRef struct {
Storage []byte
Rows int
}

View File

@ -1,122 +0,0 @@
package base
import (
"bytes"
"fmt"
)
// Ponds contain a particular number of rows of
// a particular number of Attributes, all of a given type.
type Pond struct {
threadNo uint32
parent DataGrid
attributes []Attribute
size int
alloc [][]byte
maxRow int
}
func (p *Pond) String() string {
if len(p.alloc) > 1 {
return fmt.Sprintf("Pond(%d attributes\n thread: %d\n size: %d\n)", len(p.attributes), p.threadNo, p.size)
}
return fmt.Sprintf("Pond(%d attributes\n thread: %d\n size: %d\n %d \n)", len(p.attributes), p.threadNo, p.size, p.alloc[0][0:60])
}
// PondStorageRef is a reference to a particular set
// of allocated rows within a Pond
type PondStorageRef struct {
Storage []byte
Rows int
}
// RowSize returns the size of each row in bytes
func (p *Pond) RowSize() int {
return len(p.attributes) * p.size
}
// Attributes returns a slice of Attributes in this Pond
func (p *Pond) Attributes() []Attribute {
return p.attributes
}
// Storage returns a slice of PondStorageRefs which can
// be used to access the memory in this pond.
func (p *Pond) Storage() []PondStorageRef {
ret := make([]PondStorageRef, len(p.alloc))
rowSize := p.RowSize()
for i, b := range p.alloc {
ret[i] = PondStorageRef{b, len(b) / rowSize}
}
return ret
}
func (p *Pond) resolveBlock(col int, row int) (int, int) {
if len(p.alloc) == 0 {
panic("No blocks to resolve")
}
// Find where in the pond the byte is
byteOffset := row*p.RowSize() + col*p.size
curOffset := 0
curBlock := 0
blockOffset := 0
for {
if curBlock >= len(p.alloc) {
panic("Don't have enough blocks to fulfill")
}
// Rows are not allowed to span blocks
blockAdd := len(p.alloc[curBlock])
blockAdd -= blockAdd % p.RowSize()
// Case 1: we need to skip this allocation
if curOffset+blockAdd < byteOffset {
curOffset += blockAdd
curBlock++
} else {
blockOffset = byteOffset - curOffset
break
}
}
return curBlock, blockOffset
}
func (p *Pond) set(col int, row int, val []byte) {
// Double-check the length
if len(val) != p.size {
panic(fmt.Sprintf("Tried to call set() with %d bytes, should be %d", len(val), p.size))
}
// Find where in the pond the byte is
curBlock, blockOffset := p.resolveBlock(col, row)
// Copy the value in
copied := copy(p.alloc[curBlock][blockOffset:], val)
if copied != p.size {
panic(fmt.Sprintf("set() terminated by only copying %d bytes into the current block (should be %d). Check EDF allocation", copied, p.size))
}
row++
if row > p.maxRow {
p.maxRow = row
}
}
func (p *Pond) get(col int, row int) []byte {
curBlock, blockOffset := p.resolveBlock(col, row)
return p.alloc[curBlock][blockOffset : blockOffset+p.size]
}
func (p *Pond) appendToRowBuf(row int, buffer *bytes.Buffer) {
for i, a := range p.attributes {
postfix := " "
if i == len(p.attributes)-1 {
postfix = ""
}
buffer.WriteString(fmt.Sprintf("%s%s", a.GetStringFromSysVal(p.get(i, row)), postfix))
}
}