From b93db3d08b42f42df13c41381aa13d8f57ac4f55 Mon Sep 17 00:00:00 2001 From: Ondrej Prazak Date: Mon, 21 Jun 2021 15:07:30 +0200 Subject: [PATCH 1/2] Fixes #4 - Add immutable remove operation --- trie/remove.go | 18 +++++++++++++ trie/remove_test.go | 62 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+) create mode 100644 trie/remove_test.go diff --git a/trie/remove.go b/trie/remove.go index b7d5463..52986de 100644 --- a/trie/remove.go +++ b/trie/remove.go @@ -26,3 +26,21 @@ func (trie *Trie) RemoveAtDepth(depth int, q key.Key) (reachedDepth int, removed } } } + +func Remove(trie *Trie, q key.Key) (*Trie) { + return RemoveAtDepth(0, trie, q) +} + +func RemoveAtDepth(depth int, trie *Trie, q key.Key) (*Trie) { + switch { + case trie.IsEmptyLeaf() || trie.IsNonEmptyLeaf(): + return &Trie{} + default: + dir := q.BitAt(depth) + copy := &Trie{} + copy.Branch[dir] = RemoveAtDepth(depth + 1, trie.Branch[dir], q) + copy.Branch[1 - dir] = trie.Branch[1 - dir] + copy.shrink() + return copy + } +} diff --git a/trie/remove_test.go b/trie/remove_test.go new file mode 100644 index 0000000..24597ba --- /dev/null +++ b/trie/remove_test.go @@ -0,0 +1,62 @@ +package trie + +import ( + "testing" + "math/rand" +) + +func TestImmutableRemoveIsImmutable(t *testing.T) { + for _, keySet := range testAddSamples { + trie := FromKeys(keySet.Keys) + for _, key := range keySet.Keys { + updated := Remove(trie, key) + if Equal(trie, updated) { + t.Fatalf("immuatble remove should not mutate trie, original: %v, updated: %v", trie, updated) + } + trie = updated + } + } +} + +func TestMutableAndImmutableRemoveSame(t *testing.T) { + for _, keySet := range append(testAddSamples, randomTestAddSamples(100)...) { + mut := FromKeys(keySet.Keys) + immut := FromKeys(keySet.Keys) + + for _, key := range keySet.Keys { + mut.Remove(key) + immut = Remove(immut, key) + if d := mut.CheckInvariant(); d != nil { + t.Fatalf("mutable trie invariant discrepancy: %v", d) + } + if d := immut.CheckInvariant(); d != nil { + t.Fatalf("immutable trie invariant discrepancy: %v", d) + } + if !Equal(mut, immut) { + t.Errorf("mutable trie %v differs from immutable trie %v", mut, immut) + } + } + } +} + +func TestRemoveIsOrderIndependent(t *testing.T) { + for _, keySet := range append(testAddSamples, randomTestAddSamples(100)...) { + mut := FromKeys(keySet.Keys) + immut := FromKeys(keySet.Keys) + + for j := 0; j < 100; j++ { + perm := rand.Perm(len(keySet.Keys)) + for _, idx := range perm { + mut.Remove(keySet.Keys[idx]) + immut = Remove(immut, keySet.Keys[idx]) + + if d := immut.CheckInvariant(); d != nil { + t.Fatalf("trie invariant discrepancy: %v", d) + } + if !Equal(mut, immut) { + t.Errorf("trie %v differs from trie %v", mut, immut) + } + } + } + } +} From 9b71b93a728650706022c33d43271a3d671803ca Mon Sep 17 00:00:00 2001 From: Ondrej Prazak Date: Sun, 4 Jul 2021 12:05:23 +0200 Subject: [PATCH 2/2] Refs #4 - Remove original trie when no key removed --- trie/remove.go | 18 +++++++++++++----- trie/remove_test.go | 12 +++++++++++- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/trie/remove.go b/trie/remove.go index 52986de..6290b21 100644 --- a/trie/remove.go +++ b/trie/remove.go @@ -27,19 +27,27 @@ func (trie *Trie) RemoveAtDepth(depth int, q key.Key) (reachedDepth int, removed } } -func Remove(trie *Trie, q key.Key) (*Trie) { +func Remove(trie *Trie, q key.Key) *Trie { return RemoveAtDepth(0, trie, q) } -func RemoveAtDepth(depth int, trie *Trie, q key.Key) (*Trie) { +func RemoveAtDepth(depth int, trie *Trie, q key.Key) *Trie { switch { - case trie.IsEmptyLeaf() || trie.IsNonEmptyLeaf(): + case trie.IsEmptyLeaf(): + return trie + case trie.IsNonEmptyLeaf() && !key.Equal(trie.Key, q): + return trie + case trie.IsNonEmptyLeaf() && key.Equal(trie.Key, q): return &Trie{} default: dir := q.BitAt(depth) + afterDelete := RemoveAtDepth(depth+1, trie.Branch[dir], q) + if afterDelete == trie.Branch[dir] { + return trie + } copy := &Trie{} - copy.Branch[dir] = RemoveAtDepth(depth + 1, trie.Branch[dir], q) - copy.Branch[1 - dir] = trie.Branch[1 - dir] + copy.Branch[dir] = afterDelete + copy.Branch[1-dir] = trie.Branch[1-dir] copy.shrink() return copy } diff --git a/trie/remove_test.go b/trie/remove_test.go index 24597ba..d322c78 100644 --- a/trie/remove_test.go +++ b/trie/remove_test.go @@ -1,8 +1,9 @@ package trie import ( - "testing" + "github.com/libp2p/go-libp2p-xor/key" "math/rand" + "testing" ) func TestImmutableRemoveIsImmutable(t *testing.T) { @@ -60,3 +61,12 @@ func TestRemoveIsOrderIndependent(t *testing.T) { } } } + +func TestRemoveReturnsOriginalWhenNoKeyRemoved(t *testing.T) { + trie := FromKeys(testAddSamples[0].Keys) + + result := Remove(trie, key.ByteKey(2)) + if trie != result { + t.Fatalf("Remove should return original trie when no key was removed") + } +}