From e225d620262a67cf33680c4a1534afa383c7e70c Mon Sep 17 00:00:00 2001 From: Jeff Thompson Date: Wed, 6 Mar 2024 17:41:44 +0100 Subject: [PATCH 1/8] feat: Add p/demo/avlhelpers. See the PR. Signed-off-by: Jeff Thompson --- .../gno.land/p/demo/avlhelpers/avlhelpers.gno | 32 +++++++ examples/gno.land/p/demo/avlhelpers/gno.mod | 3 + .../p/demo/avlhelpers/z_0_filetest.gno | 94 +++++++++++++++++++ 3 files changed, 129 insertions(+) create mode 100644 examples/gno.land/p/demo/avlhelpers/avlhelpers.gno create mode 100644 examples/gno.land/p/demo/avlhelpers/gno.mod create mode 100644 examples/gno.land/p/demo/avlhelpers/z_0_filetest.gno 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..fbc097b0f94 --- /dev/null +++ b/examples/gno.land/p/demo/avlhelpers/avlhelpers.gno @@ -0,0 +1,32 @@ +package avlhelpers + +import ( + "gno.land/p/demo/avl" +) + +// Get a list of keys starting from the given prefix. Limit the +// number of results to maxResults. +func ListKeysByPrefix(tree avl.Tree, prefix string, maxResults int) []string { + 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-- + } + + result := []string{} + tree.Iterate(prefix, end, 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..c25a3da5b64 --- /dev/null +++ b/examples/gno.land/p/demo/avlhelpers/z_0_filetest.gno @@ -0,0 +1,94 @@ +// 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 init() { +} + +func main() { + tree := avl.Tree{} + + { + // Empty tree. + matches := avlhelpers.ListKeysByPrefix(tree, "", 10) + println(ufmt.Sprintf("# matches: %d", len(matches))) + } + + tree.Set("alice", "") + tree.Set("andy", "") + tree.Set("bob", "") + + { + // Match only alice. + matches := avlhelpers.ListKeysByPrefix(tree, "al", 10) + println(ufmt.Sprintf("# matches: %d", len(matches))) + println("match: " + matches[0]) + } + + { + // Match alice and andy. + matches := avlhelpers.ListKeysByPrefix(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.ListKeysByPrefix(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.ListKeysByPrefix(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.ListKeysByPrefix(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.ListKeysByPrefix(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 From 19d9215128894026e68348c0c4dde8bb24aad794 Mon Sep 17 00:00:00 2001 From: Jeff Thompson Date: Wed, 6 Mar 2024 17:43:54 +0100 Subject: [PATCH 2/8] feat: In r/demo/users, add ListUsersByPrefix. See the PR. Signed-off-by: Jeff Thompson --- examples/gno.land/r/demo/users/gno.mod | 1 + examples/gno.land/r/demo/users/users.gno | 7 +++ .../gno.land/r/demo/users/z_11_filetest.gno | 49 +++++++++++++++++++ 3 files changed, 57 insertions(+) create mode 100644 examples/gno.land/r/demo/users/z_11_filetest.gno 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 4fe486a71d0..a52cb9fdf25 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" ) @@ -235,6 +236,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.ListKeysByPrefix(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_11_filetest.gno b/examples/gno.land/r/demo/users/z_11_filetest.gno new file mode 100644 index 00000000000..a219fee2e23 --- /dev/null +++ b/examples/gno.land/r/demo/users/z_11_filetest.gno @@ -0,0 +1,49 @@ +package main + +// SEND: 2000000000ugnot + +import ( + "strconv" + + "gno.land/r/demo/users" +) + +func main() { + users.Register("", "gnouser", "my profile") + + { + // Normal usage + names := users.ListUsersByPrefix("g", 10) + println("# names: " + strconv.Itoa(len(names))) + println("name: " + names[0]) + } + + { + // Empty prefix: match all + names := users.ListUsersByPrefix("", 10) + println("# names: " + strconv.Itoa(len(names))) + println("name: " + names[0]) + } + + { + // The prefix is before "gnouser" + names := users.ListUsersByPrefix("gnouseq", 10) + println("# names: " + strconv.Itoa(len(names))) + } + + { + // The prefix is after "gnouser" + names := users.ListUsersByPrefix("go", 10) + println("# names: " + strconv.Itoa(len(names))) + } + + // More tests are in p/demo/avlhelpers +} + +// Output: +// # names: 1 +// name: gnouser +// # names: 1 +// name: gnouser +// # names: 0 +// # names: 0 From df5488978e66c2279a7e563d4dc2ddc94f7b66bc Mon Sep 17 00:00:00 2001 From: Jeff Thompson Date: Wed, 12 Jun 2024 17:56:22 +0200 Subject: [PATCH 3/8] chore: avlhelpers: In z_0_filetest.gno, remove unneeded init() Signed-off-by: Jeff Thompson --- examples/gno.land/p/demo/avlhelpers/z_0_filetest.gno | 3 --- 1 file changed, 3 deletions(-) diff --git a/examples/gno.land/p/demo/avlhelpers/z_0_filetest.gno b/examples/gno.land/p/demo/avlhelpers/z_0_filetest.gno index c25a3da5b64..72b3ea6fc7e 100644 --- a/examples/gno.land/p/demo/avlhelpers/z_0_filetest.gno +++ b/examples/gno.land/p/demo/avlhelpers/z_0_filetest.gno @@ -9,9 +9,6 @@ import ( "gno.land/p/demo/ufmt" ) -func init() { -} - func main() { tree := avl.Tree{} From bfa0dafbdb53193a2c67b4691dc3533cf3fcfb79 Mon Sep 17 00:00:00 2001 From: Jeff Thompson Date: Mon, 19 Aug 2024 09:11:47 +0200 Subject: [PATCH 4/8] chore: Rename z_11_filetest.gno to z_12_filetest.gno Signed-off-by: Jeff Thompson --- .../r/demo/users/{z_11_filetest.gno => z_12_filetest.gno} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename examples/gno.land/r/demo/users/{z_11_filetest.gno => z_12_filetest.gno} (100%) diff --git a/examples/gno.land/r/demo/users/z_11_filetest.gno b/examples/gno.land/r/demo/users/z_12_filetest.gno similarity index 100% rename from examples/gno.land/r/demo/users/z_11_filetest.gno rename to examples/gno.land/r/demo/users/z_12_filetest.gno From 760ab85b8c5f4560b557b382a9ca7b08d504f534 Mon Sep 17 00:00:00 2001 From: Jeff Thompson Date: Mon, 19 Aug 2024 09:20:17 +0200 Subject: [PATCH 5/8] chore: In p/demo/avlhelpers, rename ListKeysByPrefix to ListByteStringKeysByPrefix Signed-off-by: Jeff Thompson --- examples/gno.land/p/demo/avlhelpers/avlhelpers.gno | 3 ++- .../gno.land/p/demo/avlhelpers/z_0_filetest.gno | 14 +++++++------- examples/gno.land/r/demo/users/users.gno | 2 +- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/examples/gno.land/p/demo/avlhelpers/avlhelpers.gno b/examples/gno.land/p/demo/avlhelpers/avlhelpers.gno index fbc097b0f94..3c3e3c02b94 100644 --- a/examples/gno.land/p/demo/avlhelpers/avlhelpers.gno +++ b/examples/gno.land/p/demo/avlhelpers/avlhelpers.gno @@ -6,7 +6,8 @@ import ( // Get a list of keys starting from the given prefix. Limit the // number of results to maxResults. -func ListKeysByPrefix(tree avl.Tree, prefix string, maxResults int) []string { +// 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 { end := "" n := len(prefix) // To make the end of the search, increment the final character ASCII by one. diff --git a/examples/gno.land/p/demo/avlhelpers/z_0_filetest.gno b/examples/gno.land/p/demo/avlhelpers/z_0_filetest.gno index 72b3ea6fc7e..1c7873e297a 100644 --- a/examples/gno.land/p/demo/avlhelpers/z_0_filetest.gno +++ b/examples/gno.land/p/demo/avlhelpers/z_0_filetest.gno @@ -14,7 +14,7 @@ func main() { { // Empty tree. - matches := avlhelpers.ListKeysByPrefix(tree, "", 10) + matches := avlhelpers.ListByteStringKeysByPrefix(tree, "", 10) println(ufmt.Sprintf("# matches: %d", len(matches))) } @@ -24,14 +24,14 @@ func main() { { // Match only alice. - matches := avlhelpers.ListKeysByPrefix(tree, "al", 10) + matches := avlhelpers.ListByteStringKeysByPrefix(tree, "al", 10) println(ufmt.Sprintf("# matches: %d", len(matches))) println("match: " + matches[0]) } { // Match alice and andy. - matches := avlhelpers.ListKeysByPrefix(tree, "a", 10) + matches := avlhelpers.ListByteStringKeysByPrefix(tree, "a", 10) println(ufmt.Sprintf("# matches: %d", len(matches))) println("match: " + matches[0]) println("match: " + matches[1]) @@ -39,7 +39,7 @@ func main() { { // Match alice and andy limited to 1. - matches := avlhelpers.ListKeysByPrefix(tree, "a", 1) + matches := avlhelpers.ListByteStringKeysByPrefix(tree, "a", 1) println(ufmt.Sprintf("# matches: %d", len(matches))) println("match: " + matches[0]) } @@ -52,14 +52,14 @@ func main() { { // Match only "a\xff\xff". - matches := avlhelpers.ListKeysByPrefix(tree, "a\xff\xff", 10) + 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.ListKeysByPrefix(tree, "a\xff", 10) + 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])))) @@ -67,7 +67,7 @@ func main() { { // Edge case: Match only "\xff\xff\x00". - matches := avlhelpers.ListKeysByPrefix(tree, "\xff\xff", 10) + matches := avlhelpers.ListByteStringKeysByPrefix(tree, "\xff\xff", 10) println(ufmt.Sprintf("# matches: %d", len(matches))) println(ufmt.Sprintf("match: %s", hex.EncodeToString([]byte(matches[0])))) } diff --git a/examples/gno.land/r/demo/users/users.gno b/examples/gno.land/r/demo/users/users.gno index fb677cadb17..4a0b9c1caf7 100644 --- a/examples/gno.land/r/demo/users/users.gno +++ b/examples/gno.land/r/demo/users/users.gno @@ -259,7 +259,7 @@ func GetUserByAddressOrName(input users.AddressOrName) *users.User { // 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.ListKeysByPrefix(name2User, prefix, maxResults) + return avlhelpers.ListByteStringKeysByPrefix(name2User, prefix, maxResults) } func Resolve(input users.AddressOrName) std.Address { From 91998e1521e16ede3b845eb5e5a1ca5ed654dd10 Mon Sep 17 00:00:00 2001 From: Jeff Thompson Date: Mon, 19 Aug 2024 09:30:01 +0200 Subject: [PATCH 6/8] fix: z_12_filetest.gno: SEND must not be > 200000000ugnot Signed-off-by: Jeff Thompson --- examples/gno.land/r/demo/users/z_12_filetest.gno | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/gno.land/r/demo/users/z_12_filetest.gno b/examples/gno.land/r/demo/users/z_12_filetest.gno index a219fee2e23..d33b4ad2be3 100644 --- a/examples/gno.land/r/demo/users/z_12_filetest.gno +++ b/examples/gno.land/r/demo/users/z_12_filetest.gno @@ -1,6 +1,6 @@ package main -// SEND: 2000000000ugnot +// SEND: 200000000ugnot import ( "strconv" From 5da91f6bb2d11acc8fd2c2c427fefd736bc8724d Mon Sep 17 00:00:00 2001 From: Jeff Thompson Date: Mon, 19 Aug 2024 09:42:38 +0200 Subject: [PATCH 7/8] fix: z_12_filetest.gno: Change test name to alicia to not conflict with preloaded namespaces Signed-off-by: Jeff Thompson --- .../gno.land/r/demo/users/z_12_filetest.gno | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/examples/gno.land/r/demo/users/z_12_filetest.gno b/examples/gno.land/r/demo/users/z_12_filetest.gno index d33b4ad2be3..0fb7d27bd34 100644 --- a/examples/gno.land/r/demo/users/z_12_filetest.gno +++ b/examples/gno.land/r/demo/users/z_12_filetest.gno @@ -9,31 +9,31 @@ import ( ) func main() { - users.Register("", "gnouser", "my profile") + users.Register("", "alicia", "my profile") { // Normal usage - names := users.ListUsersByPrefix("g", 10) + names := users.ListUsersByPrefix("a", 1) println("# names: " + strconv.Itoa(len(names))) println("name: " + names[0]) } { // Empty prefix: match all - names := users.ListUsersByPrefix("", 10) + names := users.ListUsersByPrefix("", 1) println("# names: " + strconv.Itoa(len(names))) println("name: " + names[0]) } { - // The prefix is before "gnouser" - names := users.ListUsersByPrefix("gnouseq", 10) + // The prefix is before "alicia" + names := users.ListUsersByPrefix("alich", 1) println("# names: " + strconv.Itoa(len(names))) } { - // The prefix is after "gnouser" - names := users.ListUsersByPrefix("go", 10) + // The prefix is after the last name + names := users.ListUsersByPrefix("y", 10) println("# names: " + strconv.Itoa(len(names))) } @@ -42,8 +42,8 @@ func main() { // Output: // # names: 1 -// name: gnouser +// name: alicia // # names: 1 -// name: gnouser +// name: alicia // # names: 0 // # names: 0 From 7f8f389ae779f68de2068e214d60557a42c6d255 Mon Sep 17 00:00:00 2001 From: Jeff Thompson Date: Mon, 19 Aug 2024 17:30:55 +0200 Subject: [PATCH 8/8] chore: avlhelpers: Factor out IterateByteStringKeysByPrefix, use it in ListByteStringKeysByPrefix Signed-off-by: Jeff Thompson --- .../gno.land/p/demo/avlhelpers/avlhelpers.gno | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/examples/gno.land/p/demo/avlhelpers/avlhelpers.gno b/examples/gno.land/p/demo/avlhelpers/avlhelpers.gno index 3c3e3c02b94..27842932dd3 100644 --- a/examples/gno.land/p/demo/avlhelpers/avlhelpers.gno +++ b/examples/gno.land/p/demo/avlhelpers/avlhelpers.gno @@ -4,10 +4,11 @@ import ( "gno.land/p/demo/avl" ) -// Get a list of keys starting from the given prefix. Limit the -// number of results to maxResults. +// 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 ListByteStringKeysByPrefix(tree avl.Tree, prefix string, maxResults int) []string { +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. @@ -21,8 +22,15 @@ func ListByteStringKeysByPrefix(tree avl.Tree, prefix string, maxResults int) [] 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{} - tree.Iterate(prefix, end, func(key string, value interface{}) bool { + IterateByteStringKeysByPrefix(tree, prefix, func(key string, value interface{}) bool { result = append(result, key) if len(result) >= maxResults { return true