diff --git a/examples/gno.land/p/demo/avlhelpers/avlhelpers.gno b/examples/gno.land/p/demo/avlhelpers/avlhelpers.gno new file mode 100644 index 00000000000..27842932dd3 --- /dev/null +++ b/examples/gno.land/p/demo/avlhelpers/avlhelpers.gno @@ -0,0 +1,41 @@ +package avlhelpers + +import ( + "gno.land/p/demo/avl" +) + +// Iterate the keys in-order starting from the given prefix. +// It calls the provided callback function for each key-value pair encountered. +// If the callback returns true, the iteration is stopped. +// The prefix and keys are treated as byte strings, ignoring possible multi-byte Unicode runes. +func IterateByteStringKeysByPrefix(tree avl.Tree, prefix string, cb avl.IterCbFn) { + end := "" + n := len(prefix) + // To make the end of the search, increment the final character ASCII by one. + for n > 0 { + if ascii := int(prefix[n-1]); ascii < 0xff { + end = prefix[0:n-1] + string(ascii+1) + break + } + + // The last character is 0xff. Try the previous character. + n-- + } + + tree.Iterate(prefix, end, cb) +} + +// Get a list of keys starting from the given prefix. Limit the +// number of results to maxResults. +// The prefix and keys are treated as byte strings, ignoring possible multi-byte Unicode runes. +func ListByteStringKeysByPrefix(tree avl.Tree, prefix string, maxResults int) []string { + result := []string{} + IterateByteStringKeysByPrefix(tree, prefix, func(key string, value interface{}) bool { + result = append(result, key) + if len(result) >= maxResults { + return true + } + return false + }) + return result +} diff --git a/examples/gno.land/p/demo/avlhelpers/gno.mod b/examples/gno.land/p/demo/avlhelpers/gno.mod new file mode 100644 index 00000000000..559f60975cf --- /dev/null +++ b/examples/gno.land/p/demo/avlhelpers/gno.mod @@ -0,0 +1,3 @@ +module gno.land/p/demo/avlhelpers + +require gno.land/p/demo/avl v0.0.0-latest diff --git a/examples/gno.land/p/demo/avlhelpers/z_0_filetest.gno b/examples/gno.land/p/demo/avlhelpers/z_0_filetest.gno new file mode 100644 index 00000000000..1c7873e297a --- /dev/null +++ b/examples/gno.land/p/demo/avlhelpers/z_0_filetest.gno @@ -0,0 +1,91 @@ +// PKGPATH: gno.land/r/test +package test + +import ( + "encoding/hex" + + "gno.land/p/demo/avl" + "gno.land/p/demo/avlhelpers" + "gno.land/p/demo/ufmt" +) + +func main() { + tree := avl.Tree{} + + { + // Empty tree. + matches := avlhelpers.ListByteStringKeysByPrefix(tree, "", 10) + println(ufmt.Sprintf("# matches: %d", len(matches))) + } + + tree.Set("alice", "") + tree.Set("andy", "") + tree.Set("bob", "") + + { + // Match only alice. + matches := avlhelpers.ListByteStringKeysByPrefix(tree, "al", 10) + println(ufmt.Sprintf("# matches: %d", len(matches))) + println("match: " + matches[0]) + } + + { + // Match alice and andy. + matches := avlhelpers.ListByteStringKeysByPrefix(tree, "a", 10) + println(ufmt.Sprintf("# matches: %d", len(matches))) + println("match: " + matches[0]) + println("match: " + matches[1]) + } + + { + // Match alice and andy limited to 1. + matches := avlhelpers.ListByteStringKeysByPrefix(tree, "a", 1) + println(ufmt.Sprintf("# matches: %d", len(matches))) + println("match: " + matches[0]) + } + + tree = avl.Tree{} + tree.Set("a\xff", "") + tree.Set("a\xff\xff", "") + tree.Set("b", "") + tree.Set("\xff\xff\x00", "") + + { + // Match only "a\xff\xff". + matches := avlhelpers.ListByteStringKeysByPrefix(tree, "a\xff\xff", 10) + println(ufmt.Sprintf("# matches: %d", len(matches))) + println(ufmt.Sprintf("match: %s", hex.EncodeToString([]byte(matches[0])))) + } + + { + // Match "a\xff" and "a\xff\xff". + matches := avlhelpers.ListByteStringKeysByPrefix(tree, "a\xff", 10) + println(ufmt.Sprintf("# matches: %d", len(matches))) + println(ufmt.Sprintf("match: %s", hex.EncodeToString([]byte(matches[0])))) + println(ufmt.Sprintf("match: %s", hex.EncodeToString([]byte(matches[1])))) + } + + { + // Edge case: Match only "\xff\xff\x00". + matches := avlhelpers.ListByteStringKeysByPrefix(tree, "\xff\xff", 10) + println(ufmt.Sprintf("# matches: %d", len(matches))) + println(ufmt.Sprintf("match: %s", hex.EncodeToString([]byte(matches[0])))) + } +} + +// Output: +// # matches: 0 +// # matches: 1 +// match: alice +// # matches: 2 +// match: alice +// match: andy +// # matches: 1 +// match: alice +// # matches: 1 +// match: 61ffff +// # matches: 2 +// match: 61ff +// match: 61ffff +// # matches: 1 +// match: ffff00 diff --git a/examples/gno.land/r/demo/users/gno.mod b/examples/gno.land/r/demo/users/gno.mod index a2ee2ea86ba..61b11c09b80 100644 --- a/examples/gno.land/r/demo/users/gno.mod +++ b/examples/gno.land/r/demo/users/gno.mod @@ -2,5 +2,6 @@ module gno.land/r/demo/users require ( gno.land/p/demo/avl v0.0.0-latest + gno.land/p/demo/avlhelpers v0.0.0-latest gno.land/p/demo/users v0.0.0-latest ) diff --git a/examples/gno.land/r/demo/users/users.gno b/examples/gno.land/r/demo/users/users.gno index 9b8e93b579b..4a0b9c1caf7 100644 --- a/examples/gno.land/r/demo/users/users.gno +++ b/examples/gno.land/r/demo/users/users.gno @@ -7,6 +7,7 @@ import ( "strings" "gno.land/p/demo/avl" + "gno.land/p/demo/avlhelpers" "gno.land/p/demo/users" ) @@ -255,6 +256,12 @@ func GetUserByAddressOrName(input users.AddressOrName) *users.User { return GetUserByAddress(std.Address(input)) } +// Get a list of user names starting from the given prefix. Limit the +// number of results to maxResults. (This can be used for a name search tool.) +func ListUsersByPrefix(prefix string, maxResults int) []string { + return avlhelpers.ListByteStringKeysByPrefix(name2User, prefix, maxResults) +} + func Resolve(input users.AddressOrName) std.Address { name, isName := input.GetName() if !isName { diff --git a/examples/gno.land/r/demo/users/z_12_filetest.gno b/examples/gno.land/r/demo/users/z_12_filetest.gno new file mode 100644 index 00000000000..0fb7d27bd34 --- /dev/null +++ b/examples/gno.land/r/demo/users/z_12_filetest.gno @@ -0,0 +1,49 @@ +package main + +// SEND: 200000000ugnot + +import ( + "strconv" + + "gno.land/r/demo/users" +) + +func main() { + users.Register("", "alicia", "my profile") + + { + // Normal usage + names := users.ListUsersByPrefix("a", 1) + println("# names: " + strconv.Itoa(len(names))) + println("name: " + names[0]) + } + + { + // Empty prefix: match all + names := users.ListUsersByPrefix("", 1) + println("# names: " + strconv.Itoa(len(names))) + println("name: " + names[0]) + } + + { + // The prefix is before "alicia" + names := users.ListUsersByPrefix("alich", 1) + println("# names: " + strconv.Itoa(len(names))) + } + + { + // The prefix is after the last name + names := users.ListUsersByPrefix("y", 10) + println("# names: " + strconv.Itoa(len(names))) + } + + // More tests are in p/demo/avlhelpers +} + +// Output: +// # names: 1 +// name: alicia +// # names: 1 +// name: alicia +// # names: 0 +// # names: 0