-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This adds a FixedTree type, which operates on just int values, making it trivial to manage mapping parent-child relationships in a sorted slice. The FixedTree nodes each have a fixed number of children. This is intended to be used to map out parent-child relationships in a tree for distributing block data over a public network, hence the "g-net-dag" (Gordian, Network, Directed Acyclic Graph) name.
- Loading branch information
1 parent
5ac09f1
commit 4eefb14
Showing
3 changed files
with
179 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
// Package gnetdag (Gordian NETwork Directed Acyclic Graph) | ||
// contains types for determining directional flow of network traffic. | ||
// | ||
// Types in this package are focused on int values, | ||
// so that they remain decoupled from any concrete implementations | ||
// of validators, network addresses, and so on. | ||
// Callers may simply use the int values as indices into slices | ||
// of the actual type needing the directed graph. | ||
// | ||
// This package currently contains the [FixedTree] type, | ||
// which effectively maps indices in a slice such that | ||
// every non-root node contains a fixed number of children. | ||
// This package will be expanded with more types as deemed necessary. | ||
package gnetdag |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
package gnetdag | ||
|
||
// FixedTree represents a tree where each non-leaf node has a fixed number of children. | ||
// With BranchFactor=3, the entries are arranged in layers like: | ||
// | ||
// 0 (L0) | ||
// 1 2 3 (L1) | ||
// 4 5 6 7 8 9 10 11 12 (L2) | ||
// | ||
// The entryIdx parameter used in methods on FixedTree | ||
// are intended to be used as indices into an existing ordered slice | ||
// that is to be treated as a tree. | ||
// | ||
// Methods on FixedTree use unchecked math, | ||
// so invalid values, such as negative entry indices or branch factors, | ||
// or branch factor so large that bf^2 overflows an int, | ||
// result in undefined behavior. | ||
type FixedTree struct { | ||
// The width of the layer at index 1. | ||
BranchFactor int | ||
} | ||
|
||
// Parent returns the "parent" index of the given entry index. | ||
// It returns -1 for entryIdx = 0. | ||
func (t FixedTree) Parent(entryIdx int) int { | ||
if entryIdx == 0 { | ||
return -1 | ||
} | ||
|
||
// Special case for first layer, to avoid some off by one math. | ||
if entryIdx <= t.BranchFactor { | ||
return 0 | ||
} | ||
|
||
curLayer := t.Layer(entryIdx) | ||
|
||
// Calculate how many entries are present before the parent layer. | ||
parentLayer := curLayer - 1 | ||
ancestorEntries := 1 | ||
ancestorWidth := 1 | ||
for range parentLayer - 1 { | ||
ancestorWidth *= t.BranchFactor | ||
ancestorEntries += ancestorWidth | ||
} | ||
|
||
// Our current row has t.BranchFactor times more entries than the parent row, | ||
// so map our offset in the current row, into the offset of the parent row. | ||
parentLayerWidth := ancestorWidth * t.BranchFactor | ||
parentOffset := (entryIdx - parentLayerWidth - ancestorEntries) / t.BranchFactor | ||
return ancestorEntries + parentOffset | ||
} | ||
|
||
// FirstChild returns the entry index of the first child of the given entry index. | ||
// Every parent contains t.BranchFactor children, | ||
// but the FixedTree type does not track number of entries, | ||
// so it is the caller's responsibility to confirm that there are | ||
// at least t.BranchFactor children available. | ||
func (t FixedTree) FirstChild(entryIdx int) int { | ||
if entryIdx == 0 { | ||
return 1 | ||
} | ||
|
||
curLayerWidth := t.BranchFactor | ||
entriesBeforeCurLayer := 1 | ||
|
||
for { | ||
if entryIdx <= entriesBeforeCurLayer+curLayerWidth { | ||
// Offset of the given entry index, within its respective layer. | ||
entryLayerOffset := entryIdx - entriesBeforeCurLayer | ||
|
||
// Then find the start of the next layer, | ||
// and move forward t.BranchFactor times according to the current layer offset. | ||
return entriesBeforeCurLayer + curLayerWidth + (entryLayerOffset * t.BranchFactor) | ||
} | ||
|
||
entriesBeforeCurLayer += curLayerWidth | ||
curLayerWidth *= t.BranchFactor | ||
} | ||
} | ||
|
||
// Layer returns the layer that would contain the given entry index. | ||
func (t FixedTree) Layer(entryIdx int) int { | ||
if entryIdx == 0 { | ||
return 0 | ||
} | ||
|
||
layer := 1 | ||
layerWidth := t.BranchFactor | ||
entriesSoFar := 1 + t.BranchFactor | ||
|
||
for { | ||
if entryIdx < entriesSoFar { | ||
return layer | ||
} | ||
|
||
layer++ | ||
layerWidth *= t.BranchFactor | ||
entriesSoFar += layerWidth | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
package gnetdag_test | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/gordian-engine/gordian/gnetdag" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
// Most of these tests use a branch factor of 3, | ||
// resulting in layers like: | ||
// 0 (L0) | ||
// 1 2 3 (L1) | ||
// 4 5 6 7 8 9 10 11 12 (L2) | ||
// 13 14 15 16... (L3) | ||
|
||
func TestFixedTree_Layer(t *testing.T) { | ||
t.Parallel() | ||
|
||
tree := gnetdag.FixedTree{BranchFactor: 3} | ||
require.Equal(t, 0, tree.Layer(0)) | ||
require.Equal(t, 1, tree.Layer(1)) | ||
require.Equal(t, 2, tree.Layer(4)) | ||
|
||
tree.BranchFactor = 5 | ||
require.Equal(t, 0, tree.Layer(0)) | ||
require.Equal(t, 1, tree.Layer(4)) | ||
} | ||
|
||
func TestFixedTree_Parent(t *testing.T) { | ||
t.Parallel() | ||
|
||
tree := gnetdag.FixedTree{BranchFactor: 3} | ||
require.Equal(t, -1, tree.Parent(0)) | ||
|
||
require.Equal(t, 0, tree.Parent(1)) | ||
require.Equal(t, 0, tree.Parent(2)) | ||
require.Equal(t, 0, tree.Parent(3)) | ||
|
||
require.Equal(t, 1, tree.Parent(4)) | ||
require.Equal(t, 1, tree.Parent(5)) | ||
require.Equal(t, 1, tree.Parent(6)) | ||
require.Equal(t, 2, tree.Parent(7)) | ||
require.Equal(t, 2, tree.Parent(8)) | ||
require.Equal(t, 2, tree.Parent(9)) | ||
require.Equal(t, 3, tree.Parent(10)) | ||
require.Equal(t, 3, tree.Parent(11)) | ||
require.Equal(t, 3, tree.Parent(12)) | ||
|
||
require.Equal(t, 4, tree.Parent(13)) | ||
} | ||
|
||
func TestFixedTree_FirstChild(t *testing.T) { | ||
t.Parallel() | ||
|
||
tree := gnetdag.FixedTree{BranchFactor: 3} | ||
|
||
require.Equal(t, 1, tree.FirstChild(0)) | ||
|
||
require.Equal(t, 4, tree.FirstChild(1)) | ||
require.Equal(t, 7, tree.FirstChild(2)) | ||
require.Equal(t, 10, tree.FirstChild(3)) | ||
|
||
require.Equal(t, 13, tree.FirstChild(4)) | ||
} |