Skip to content

Commit

Permalink
Merge branch 'feat/312' of github.com:omarsy/gno into feat/312
Browse files Browse the repository at this point in the history
  • Loading branch information
thehowl committed Jan 10, 2025
2 parents 9a3f5a9 + a48f8ae commit ef75f16
Show file tree
Hide file tree
Showing 16 changed files with 825 additions and 48 deletions.
100 changes: 100 additions & 0 deletions examples/gno.land/p/moul/addrset/addrset.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// Package addrset provides a specialized set data structure for managing unique Gno addresses.
//
// It is built on top of an AVL tree for efficient operations and maintains addresses in sorted order.
// This package is particularly useful when you need to:
// - Track a collection of unique addresses (e.g., for whitelists, participants, etc.)
// - Efficiently check address membership
// - Support pagination when displaying addresses
//
// Example usage:
//
// import (
// "std"
// "gno.land/p/moul/addrset"
// )
//
// func MyHandler() {
// // Create a new address set
// var set addrset.Set
//
// // Add some addresses
// addr1 := std.Address("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5")
// addr2 := std.Address("g1sss5g0rkqr88k4u648yd5d3l9t4d8vvqwszqth")
//
// set.Add(addr1) // returns true (newly added)
// set.Add(addr2) // returns true (newly added)
// set.Add(addr1) // returns false (already exists)
//
// // Check membership
// if set.Has(addr1) {
// // addr1 is in the set
// }
//
// // Get size
// size := set.Size() // returns 2
//
// // Iterate with pagination (10 items per page, starting at offset 0)
// set.IterateByOffset(0, 10, func(addr std.Address) bool {
// // Process addr
// return false // continue iteration
// })
//
// // Remove an address
// set.Remove(addr1) // returns true (was present)
// set.Remove(addr1) // returns false (not present)
// }
package addrset

import (
"std"

"gno.land/p/demo/avl"
)

type Set struct {
tree avl.Tree
}

// Add inserts an address into the set.
// Returns true if the address was newly added, false if it already existed.
func (s *Set) Add(addr std.Address) bool {
return !s.tree.Set(string(addr), nil)
}

// Remove deletes an address from the set.
// Returns true if the address was found and removed, false if it didn't exist.
func (s *Set) Remove(addr std.Address) bool {
_, removed := s.tree.Remove(string(addr))
return removed
}

// Has checks if an address exists in the set.
func (s *Set) Has(addr std.Address) bool {
return s.tree.Has(string(addr))
}

// Size returns the number of addresses in the set.
func (s *Set) Size() int {
return s.tree.Size()
}

// IterateByOffset walks through addresses starting at the given offset.
// The callback should return true to stop iteration.
func (s *Set) IterateByOffset(offset int, count int, cb func(addr std.Address) bool) {
s.tree.IterateByOffset(offset, count, func(key string, _ interface{}) bool {
return cb(std.Address(key))
})
}

// ReverseIterateByOffset walks through addresses in reverse order starting at the given offset.
// The callback should return true to stop iteration.
func (s *Set) ReverseIterateByOffset(offset int, count int, cb func(addr std.Address) bool) {
s.tree.ReverseIterateByOffset(offset, count, func(key string, _ interface{}) bool {
return cb(std.Address(key))
})
}

// Tree returns the underlying AVL tree for advanced usage.
func (s *Set) Tree() avl.ITree {
return &s.tree
}
174 changes: 174 additions & 0 deletions examples/gno.land/p/moul/addrset/addrset_test.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
package addrset

import (
"std"
"testing"

"gno.land/p/demo/uassert"
)

func TestSet(t *testing.T) {
addr1 := std.Address("addr1")
addr2 := std.Address("addr2")
addr3 := std.Address("addr3")

tests := []struct {
name string
actions func(s *Set)
size int
has map[std.Address]bool
addrs []std.Address // for iteration checks
}{
{
name: "empty set",
actions: func(s *Set) {},
size: 0,
has: map[std.Address]bool{addr1: false},
},
{
name: "single address",
actions: func(s *Set) {
s.Add(addr1)
},
size: 1,
has: map[std.Address]bool{
addr1: true,
addr2: false,
},
addrs: []std.Address{addr1},
},
{
name: "multiple addresses",
actions: func(s *Set) {
s.Add(addr1)
s.Add(addr2)
s.Add(addr3)
},
size: 3,
has: map[std.Address]bool{
addr1: true,
addr2: true,
addr3: true,
},
addrs: []std.Address{addr1, addr2, addr3},
},
{
name: "remove address",
actions: func(s *Set) {
s.Add(addr1)
s.Add(addr2)
s.Remove(addr1)
},
size: 1,
has: map[std.Address]bool{
addr1: false,
addr2: true,
},
addrs: []std.Address{addr2},
},
{
name: "duplicate adds",
actions: func(s *Set) {
uassert.True(t, s.Add(addr1)) // first add returns true
uassert.False(t, s.Add(addr1)) // second add returns false
uassert.True(t, s.Remove(addr1)) // remove existing returns true
uassert.False(t, s.Remove(addr1)) // remove non-existing returns false
},
size: 0,
has: map[std.Address]bool{
addr1: false,
},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var set Set

// Execute test actions
tt.actions(&set)

// Check size
uassert.Equal(t, tt.size, set.Size())

// Check existence
for addr, expected := range tt.has {
uassert.Equal(t, expected, set.Has(addr))
}

// Check iteration if addresses are specified
if tt.addrs != nil {
collected := []std.Address{}
set.IterateByOffset(0, 10, func(addr std.Address) bool {
collected = append(collected, addr)
return false
})

// Check length
uassert.Equal(t, len(tt.addrs), len(collected))

// Check each address
for i, addr := range tt.addrs {
uassert.Equal(t, addr, collected[i])
}
}
})
}
}

func TestSetIterationLimits(t *testing.T) {
tests := []struct {
name string
addrs []std.Address
offset int
limit int
expected int
}{
{
name: "zero offset full list",
addrs: []std.Address{"a1", "a2", "a3"},
offset: 0,
limit: 10,
expected: 3,
},
{
name: "offset with limit",
addrs: []std.Address{"a1", "a2", "a3", "a4"},
offset: 1,
limit: 2,
expected: 2,
},
{
name: "offset beyond size",
addrs: []std.Address{"a1", "a2"},
offset: 3,
limit: 1,
expected: 0,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var set Set
for _, addr := range tt.addrs {
set.Add(addr)
}

// Test forward iteration
count := 0
set.IterateByOffset(tt.offset, tt.limit, func(addr std.Address) bool {
count++
return false
})
uassert.Equal(t, tt.expected, count)

// Test reverse iteration
count = 0
set.ReverseIterateByOffset(tt.offset, tt.limit, func(addr std.Address) bool {
count++
return false
})
uassert.Equal(t, tt.expected, count)
})
}
}
1 change: 1 addition & 0 deletions examples/gno.land/p/moul/addrset/gno.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module gno.land/p/moul/addrset
Loading

0 comments on commit ef75f16

Please sign in to comment.