...
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
# Ternary Search Tree (TST) Implementation Plan
|
||||
|
||||
> STILL BROKEN, CAN'T FIND YET
|
||||
|
||||
## Overview
|
||||
|
||||
A Ternary Search Tree (TST) is a specialized tree structure where each node has three children:
|
||||
|
||||
140
lib/data/tst/edge_case_prefix_test.v
Normal file
140
lib/data/tst/edge_case_prefix_test.v
Normal file
@@ -0,0 +1,140 @@
|
||||
module tst
|
||||
|
||||
import os
|
||||
|
||||
// Define a struct for test cases
|
||||
struct PrefixEdgeCaseTest {
|
||||
prefix string
|
||||
expected_keys []string
|
||||
}
|
||||
|
||||
// Test specific edge cases for prefix search that were problematic
|
||||
fn test_edge_case_prefix_search() {
|
||||
mut tree := new(path: 'testdata/test_edge_prefix.db', reset: true) or {
|
||||
assert false, 'Failed to create TST: ${err}'
|
||||
return
|
||||
}
|
||||
|
||||
// Keys with a common prefix that may cause issues
|
||||
keys := [
|
||||
'test', 'testing', 'tea', 'team', 'technology',
|
||||
'apple', 'application', 'appreciate',
|
||||
'banana', 'bandage', 'band',
|
||||
'car', 'carpet', 'carriage'
|
||||
]
|
||||
|
||||
// Insert all keys
|
||||
for i, key in keys {
|
||||
value := 'value-${i}'.bytes()
|
||||
tree.set(key, value) or {
|
||||
assert false, 'Failed to set key "${key}": ${err}'
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Test cases specifically focusing on the 'te' prefix issue
|
||||
test_cases := [
|
||||
// prefix, expected_keys
|
||||
PrefixEdgeCaseTest{
|
||||
prefix: 'te'
|
||||
expected_keys: ['test', 'testing', 'tea', 'team', 'technology']
|
||||
},
|
||||
PrefixEdgeCaseTest{
|
||||
prefix: 'tes'
|
||||
expected_keys: ['test', 'testing']
|
||||
},
|
||||
PrefixEdgeCaseTest{
|
||||
prefix: 'tea'
|
||||
expected_keys: ['tea', 'team']
|
||||
},
|
||||
PrefixEdgeCaseTest{
|
||||
prefix: 'a'
|
||||
expected_keys: ['apple', 'application', 'appreciate']
|
||||
},
|
||||
PrefixEdgeCaseTest{
|
||||
prefix: 'ba'
|
||||
expected_keys: ['banana', 'bandage', 'band']
|
||||
},
|
||||
PrefixEdgeCaseTest{
|
||||
prefix: 'ban'
|
||||
expected_keys: ['banana', 'band']
|
||||
},
|
||||
PrefixEdgeCaseTest{
|
||||
prefix: 'c'
|
||||
expected_keys: ['car', 'carpet', 'carriage']
|
||||
}
|
||||
]
|
||||
|
||||
for test_case in test_cases {
|
||||
prefix := test_case.prefix
|
||||
expected_keys := test_case.expected_keys
|
||||
|
||||
result := tree.list(prefix) or {
|
||||
assert false, 'Failed to list keys with prefix "${prefix}": ${err}'
|
||||
return
|
||||
}
|
||||
|
||||
// Check count matches
|
||||
assert result.len == expected_keys.len,
|
||||
'For prefix "${prefix}": expected ${expected_keys.len} keys, got ${result.len} (keys: ${result})'
|
||||
|
||||
// Check all expected keys are present
|
||||
for key in expected_keys {
|
||||
assert key in result, 'Key "${key}" missing from results for prefix "${prefix}"'
|
||||
}
|
||||
|
||||
// Verify each result starts with the prefix
|
||||
for key in result {
|
||||
assert key.starts_with(prefix), 'Key "${key}" does not start with prefix "${prefix}"'
|
||||
}
|
||||
}
|
||||
|
||||
println('All edge case prefix tests passed successfully!')
|
||||
}
|
||||
|
||||
// Test prefix search with insert order that might cause issues
|
||||
fn test_tricky_insertion_order() {
|
||||
mut tree := new(path: 'testdata/test_tricky_insert.db', reset: true) or {
|
||||
assert false, 'Failed to create TST: ${err}'
|
||||
return
|
||||
}
|
||||
|
||||
// Insert keys in a specific order that might trigger the issue
|
||||
// Insert 'team' first, then 'test', etc. to ensure tree layout is challenging
|
||||
tricky_keys := [
|
||||
'team', 'test', 'technology', 'tea', // 'te' prefix cases
|
||||
'car', 'carriage', 'carpet' // 'ca' prefix cases
|
||||
]
|
||||
|
||||
// Insert all keys
|
||||
for i, key in tricky_keys {
|
||||
value := 'value-${i}'.bytes()
|
||||
tree.set(key, value) or {
|
||||
assert false, 'Failed to set key "${key}": ${err}'
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Test 'te' prefix
|
||||
te_results := tree.list('te') or {
|
||||
assert false, 'Failed to list keys with prefix "te": ${err}'
|
||||
return
|
||||
}
|
||||
assert te_results.len == 4, 'Expected 4 keys with prefix "te", got ${te_results.len} (keys: ${te_results})'
|
||||
assert 'team' in te_results, 'Expected "team" in results'
|
||||
assert 'test' in te_results, 'Expected "test" in results'
|
||||
assert 'technology' in te_results, 'Expected "technology" in results'
|
||||
assert 'tea' in te_results, 'Expected "tea" in results'
|
||||
|
||||
// Test 'ca' prefix
|
||||
ca_results := tree.list('ca') or {
|
||||
assert false, 'Failed to list keys with prefix "ca": ${err}'
|
||||
return
|
||||
}
|
||||
assert ca_results.len == 3, 'Expected 3 keys with prefix "ca", got ${ca_results.len} (keys: ${ca_results})'
|
||||
assert 'car' in ca_results, 'Expected "car" in results'
|
||||
assert 'carriage' in ca_results, 'Expected "carriage" in results'
|
||||
assert 'carpet' in ca_results, 'Expected "carpet" in results'
|
||||
|
||||
println('All tricky insertion order tests passed successfully!')
|
||||
}
|
||||
14
lib/data/tst/texttools.v
Normal file
14
lib/data/tst/texttools.v
Normal file
@@ -0,0 +1,14 @@
|
||||
module tst
|
||||
|
||||
// namefix normalizes a string for consistent key handling
|
||||
// - removes leading/trailing whitespace
|
||||
// - converts to lowercase
|
||||
// - replaces special characters with standard ones
|
||||
pub fn namefix(s string) string {
|
||||
mut result := s.trim_space().to_lower()
|
||||
|
||||
// Replace any problematic characters or sequences if needed
|
||||
// For this implementation, we'll keep it simple
|
||||
|
||||
return result
|
||||
}
|
||||
@@ -61,6 +61,7 @@ pub fn new(args NewArgs) !TST {
|
||||
root_data := db.get(1)! // Get root node with ID 1
|
||||
root := deserialize_node(root_data)!
|
||||
println('Root node retrieved: character=${root.character}, is_end=${root.is_end_of_string}, left=${root.left_id}, middle=${root.middle_id}, right=${root.right_id}')
|
||||
root_id = 1 // Ensure we're using ID 1 for the root
|
||||
}
|
||||
|
||||
return TST{
|
||||
@@ -71,8 +72,10 @@ pub fn new(args NewArgs) !TST {
|
||||
|
||||
// Sets a key-value pair in the tree
|
||||
pub fn (mut self TST) set(key string, value []u8) ! {
|
||||
println('Setting key: "${key}"')
|
||||
if key.len == 0 {
|
||||
normalized_key := namefix(key)
|
||||
println('Setting key: "${key}" (normalized: "${normalized_key}")')
|
||||
|
||||
if normalized_key.len == 0 {
|
||||
return error('Empty key not allowed')
|
||||
}
|
||||
|
||||
@@ -91,30 +94,79 @@ pub fn (mut self TST) set(key string, value []u8) ! {
|
||||
println('Root node created with ID: ${self.root_id}')
|
||||
}
|
||||
|
||||
self.insert_recursive(self.root_id, key, 0, value)!
|
||||
println('Key "${key}" inserted successfully')
|
||||
// Insert the key-value pair
|
||||
mut last_node_id := self.insert_recursive(self.root_id, normalized_key, 0, value)!
|
||||
println('Key "${normalized_key}" inserted to node ${last_node_id}')
|
||||
|
||||
// Make sure the last node is marked as end of string with the value
|
||||
if last_node_id != 0 {
|
||||
node_data := self.db.get(last_node_id)!
|
||||
mut node := deserialize_node(node_data)!
|
||||
|
||||
// Ensure this node is marked as the end of a string
|
||||
if !node.is_end_of_string {
|
||||
println('Setting node ${last_node_id} as end of string')
|
||||
node.is_end_of_string = true
|
||||
node.value = value
|
||||
self.db.set(id: last_node_id, data: serialize_node(node))!
|
||||
}
|
||||
}
|
||||
|
||||
println('Key "${normalized_key}" inserted successfully')
|
||||
}
|
||||
|
||||
// Recursive helper function for insertion
|
||||
fn (mut self TST) insert_recursive(node_id u32, key string, pos int, value []u8) !u32 {
|
||||
// Check for position out of bounds
|
||||
if pos >= key.len {
|
||||
println('Position ${pos} is out of bounds for key "${key}"')
|
||||
return error('Position out of bounds')
|
||||
}
|
||||
|
||||
// If we've reached the end of the tree, create a new node
|
||||
if node_id == 0 {
|
||||
println('Creating new node for character: ${key[pos]} (${key[pos].ascii_str()}) at position ${pos}')
|
||||
|
||||
// Create a node for this character
|
||||
new_node := Node{
|
||||
character: key[pos]
|
||||
is_end_of_string: pos == key.len - 1
|
||||
value: if pos == key.len - 1 { value } else { []u8{} }
|
||||
value: if pos == key.len - 1 { value.clone() } else { []u8{} }
|
||||
left_id: 0
|
||||
middle_id: 0
|
||||
right_id: 0
|
||||
}
|
||||
new_id := self.db.set(data: serialize_node(new_node))!
|
||||
println('New node created with ID: ${new_id}, character: ${key[pos]} (${key[pos].ascii_str()}), is_end: ${pos == key.len - 1}')
|
||||
|
||||
// If this is the last character in the key, we're done
|
||||
if pos == key.len - 1 {
|
||||
return new_id
|
||||
}
|
||||
|
||||
// Otherwise, create the next node in the sequence and link to it
|
||||
next_id := self.insert_recursive(0, key, pos + 1, value)!
|
||||
|
||||
// Update the middle link
|
||||
node_data := self.db.get(new_id)!
|
||||
mut updated_node := deserialize_node(node_data)!
|
||||
updated_node.middle_id = next_id
|
||||
self.db.set(id: new_id, data: serialize_node(updated_node))!
|
||||
|
||||
return new_id
|
||||
}
|
||||
|
||||
// Get the current node
|
||||
mut node := deserialize_node(self.db.get(node_id)!)!
|
||||
// Get the current node with error handling
|
||||
node_data := self.db.get(node_id) or {
|
||||
println('Failed to get node data for ID ${node_id}')
|
||||
return error('Node retrieval error: ${err}')
|
||||
}
|
||||
|
||||
mut node := deserialize_node(node_data) or {
|
||||
println('Failed to deserialize node with ID ${node_id}')
|
||||
return error('Node deserialization error: ${err}')
|
||||
}
|
||||
|
||||
println('Node ${node_id}: character=${node.character} (${node.character.ascii_str()}), is_end=${node.is_end_of_string}, left=${node.left_id}, middle=${node.middle_id}, right=${node.right_id}')
|
||||
|
||||
// Compare the current character with the node's character
|
||||
@@ -149,8 +201,10 @@ fn (mut self TST) insert_recursive(node_id u32, key string, pos int, value []u8)
|
||||
|
||||
// Gets a value by key from the tree
|
||||
pub fn (mut self TST) get(key string) ![]u8 {
|
||||
println('Getting key: "${key}"')
|
||||
if key.len == 0 {
|
||||
normalized_key := namefix(key)
|
||||
println('Getting key: "${key}" (normalized: "${normalized_key}")')
|
||||
|
||||
if normalized_key.len == 0 {
|
||||
return error('Empty key not allowed')
|
||||
}
|
||||
|
||||
@@ -158,51 +212,92 @@ pub fn (mut self TST) get(key string) ![]u8 {
|
||||
return error('Tree is empty')
|
||||
}
|
||||
|
||||
return self.search_recursive(self.root_id, key, 0)!
|
||||
return self.search_recursive(self.root_id, normalized_key, 0)!
|
||||
}
|
||||
|
||||
// Recursive helper function for search
|
||||
// Very simple recursive search with explicit structure to make the compiler happy
|
||||
fn (mut self TST) search_recursive(node_id u32, key string, pos int) ![]u8 {
|
||||
// Base cases
|
||||
if node_id == 0 {
|
||||
println('Node ID is 0, key not found')
|
||||
return error('Key not found')
|
||||
}
|
||||
|
||||
node := deserialize_node(self.db.get(node_id)!)!
|
||||
println('Searching node ${node_id}: character=${node.character} (${node.character.ascii_str()}), is_end=${node.is_end_of_string}, left=${node.left_id}, middle=${node.middle_id}, right=${node.right_id}, pos=${pos}')
|
||||
|
||||
if pos >= key.len {
|
||||
println('Position ${pos} out of bounds for key "${key}"')
|
||||
return error('Key not found - position out of bounds')
|
||||
}
|
||||
|
||||
// Get the node
|
||||
node_data := self.db.get(node_id) or {
|
||||
println('Failed to get node ${node_id}')
|
||||
return error('Node not found in database')
|
||||
}
|
||||
|
||||
node := deserialize_node(node_data) or {
|
||||
println('Failed to deserialize node ${node_id}')
|
||||
return error('Failed to deserialize node')
|
||||
}
|
||||
|
||||
println('Searching node ${node_id}: char=${node.character}, pos=${pos}, key_char=${key[pos]}')
|
||||
|
||||
mut result := []u8{}
|
||||
|
||||
// Left branch
|
||||
if key[pos] < node.character {
|
||||
println('Going left: ${key[pos]} (${key[pos].ascii_str()}) < ${node.character} (${node.character.ascii_str()})')
|
||||
// Go left
|
||||
return self.search_recursive(node.left_id, key, pos)!
|
||||
} else if key[pos] > node.character {
|
||||
println('Going right: ${key[pos]} (${key[pos].ascii_str()}) > ${node.character} (${node.character.ascii_str()})')
|
||||
// Go right
|
||||
return self.search_recursive(node.right_id, key, pos)!
|
||||
} else {
|
||||
// Equal
|
||||
println('Character matches: ${key[pos]} (${key[pos].ascii_str()}) == ${node.character} (${node.character.ascii_str()})')
|
||||
if pos == key.len - 1 {
|
||||
// We've reached the end of the key
|
||||
if node.is_end_of_string {
|
||||
println('End of key reached and is_end_of_string=true, returning value')
|
||||
return node.value
|
||||
} else {
|
||||
println('End of key reached but is_end_of_string=false, key not found')
|
||||
return error('Key not found')
|
||||
println('Going left')
|
||||
result = self.search_recursive(node.left_id, key, pos) or {
|
||||
return error(err.str())
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// Right branch
|
||||
if key[pos] > node.character {
|
||||
println('Going right')
|
||||
result = self.search_recursive(node.right_id, key, pos) or {
|
||||
return error(err.str())
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// Character matches
|
||||
println('Character match')
|
||||
|
||||
// At end of key
|
||||
if pos == key.len - 1 {
|
||||
if node.is_end_of_string {
|
||||
println('Found key')
|
||||
// Copy the value
|
||||
for i in 0 .. node.value.len {
|
||||
result << node.value[i]
|
||||
}
|
||||
return result
|
||||
} else {
|
||||
// Move to the next character in the key
|
||||
println('Moving to next character: ${key[pos+1]} (${key[pos+1].ascii_str()})')
|
||||
return self.search_recursive(node.middle_id, key, pos + 1)!
|
||||
println('Character match but not end of string')
|
||||
return error('Key not found - not marked as end of string')
|
||||
}
|
||||
}
|
||||
|
||||
// Not at end of key, go to middle
|
||||
if node.middle_id == 0 {
|
||||
println('No middle child')
|
||||
return error('Key not found - no middle child')
|
||||
}
|
||||
|
||||
println('Going to middle child')
|
||||
result = self.search_recursive(node.middle_id, key, pos + 1) or {
|
||||
return error(err.str())
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// Deletes a key from the tree
|
||||
pub fn (mut self TST) delete(key string) ! {
|
||||
println('Deleting key: "${key}"')
|
||||
if key.len == 0 {
|
||||
normalized_key := namefix(key)
|
||||
println('Deleting key: "${key}" (normalized: "${normalized_key}")')
|
||||
|
||||
if normalized_key.len == 0 {
|
||||
return error('Empty key not allowed')
|
||||
}
|
||||
|
||||
@@ -210,8 +305,8 @@ pub fn (mut self TST) delete(key string) ! {
|
||||
return error('Tree is empty')
|
||||
}
|
||||
|
||||
self.delete_recursive(self.root_id, key, 0)!
|
||||
println('Key "${key}" deleted successfully')
|
||||
self.delete_recursive(self.root_id, normalized_key, 0)!
|
||||
println('Key "${normalized_key}" deleted successfully')
|
||||
}
|
||||
|
||||
// Recursive helper function for deletion
|
||||
@@ -220,14 +315,35 @@ fn (mut self TST) delete_recursive(node_id u32, key string, pos int) !bool {
|
||||
println('Node ID is 0, key not found')
|
||||
return error('Key not found')
|
||||
}
|
||||
|
||||
// Check for position out of bounds
|
||||
if pos >= key.len {
|
||||
println('Position ${pos} is out of bounds for key "${key}"')
|
||||
return error('Key not found - position out of bounds')
|
||||
}
|
||||
|
||||
mut node := deserialize_node(self.db.get(node_id)!)!
|
||||
// Get the current node with error handling
|
||||
node_data := self.db.get(node_id) or {
|
||||
println('Failed to get node data for ID ${node_id}')
|
||||
return error('Node retrieval error: ${err}')
|
||||
}
|
||||
|
||||
mut node := deserialize_node(node_data) or {
|
||||
println('Failed to deserialize node with ID ${node_id}')
|
||||
return error('Node deserialization error: ${err}')
|
||||
}
|
||||
|
||||
println('Deleting from node ${node_id}: character=${node.character} (${node.character.ascii_str()}), is_end=${node.is_end_of_string}, left=${node.left_id}, middle=${node.middle_id}, right=${node.right_id}, pos=${pos}')
|
||||
mut deleted := false
|
||||
|
||||
if key[pos] < node.character {
|
||||
println('Going left: ${key[pos]} (${key[pos].ascii_str()}) < ${node.character} (${node.character.ascii_str()})')
|
||||
// Go left
|
||||
if node.left_id == 0 {
|
||||
println('Left child is null, key not found')
|
||||
return error('Key not found')
|
||||
}
|
||||
|
||||
deleted = self.delete_recursive(node.left_id, key, pos)!
|
||||
if deleted && node.left_id != 0 {
|
||||
// Check if the left child has been deleted
|
||||
@@ -244,6 +360,11 @@ fn (mut self TST) delete_recursive(node_id u32, key string, pos int) !bool {
|
||||
} else if key[pos] > node.character {
|
||||
println('Going right: ${key[pos]} (${key[pos].ascii_str()}) > ${node.character} (${node.character.ascii_str()})')
|
||||
// Go right
|
||||
if node.right_id == 0 {
|
||||
println('Right child is null, key not found')
|
||||
return error('Key not found')
|
||||
}
|
||||
|
||||
deleted = self.delete_recursive(node.right_id, key, pos)!
|
||||
if deleted && node.right_id != 0 {
|
||||
// Check if the right child has been deleted
|
||||
@@ -285,6 +406,11 @@ fn (mut self TST) delete_recursive(node_id u32, key string, pos int) !bool {
|
||||
} else {
|
||||
// Move to the next character in the key
|
||||
println('Moving to next character: ${key[pos+1]} (${key[pos+1].ascii_str()})')
|
||||
if node.middle_id == 0 {
|
||||
println('Middle child is null, key not found')
|
||||
return error('Key not found')
|
||||
}
|
||||
|
||||
deleted = self.delete_recursive(node.middle_id, key, pos + 1)!
|
||||
if deleted && node.middle_id != 0 {
|
||||
// Check if the middle child has been deleted
|
||||
@@ -310,170 +436,3 @@ fn (mut self TST) delete_recursive(node_id u32, key string, pos int) !bool {
|
||||
|
||||
return deleted
|
||||
}
|
||||
|
||||
// Lists all keys with a given prefix
|
||||
pub fn (mut self TST) list(prefix string) ![]string {
|
||||
println('Listing keys with prefix: "${prefix}"')
|
||||
mut result := []string{}
|
||||
|
||||
// Handle empty prefix case - will return all keys
|
||||
if prefix.len == 0 {
|
||||
println('Empty prefix, collecting all keys')
|
||||
self.collect_all_keys(self.root_id, '', mut result)!
|
||||
println('Found ${result.len} keys: ${result}')
|
||||
return result
|
||||
}
|
||||
|
||||
// Find the node corresponding to the prefix
|
||||
println('Finding node for prefix: "${prefix}"')
|
||||
|
||||
// Start from the root and traverse to the node corresponding to the last character of the prefix
|
||||
mut node_id := self.root_id
|
||||
mut pos := 0
|
||||
mut current_path := ''
|
||||
|
||||
// Traverse the tree to find the node corresponding to the prefix
|
||||
for pos < prefix.len && node_id != 0 {
|
||||
node := deserialize_node(self.db.get(node_id)!)!
|
||||
println('Examining node ${node_id}: character=${node.character} (${node.character.ascii_str()}), is_end=${node.is_end_of_string}, left=${node.left_id}, middle=${node.middle_id}, right=${node.right_id}, pos=${pos}, current_path="${current_path}"')
|
||||
|
||||
if prefix[pos] < node.character {
|
||||
println('Going left: ${prefix[pos]} (${prefix[pos].ascii_str()}) < ${node.character} (${node.character.ascii_str()})')
|
||||
node_id = node.left_id
|
||||
} else if prefix[pos] > node.character {
|
||||
println('Going right: ${prefix[pos]} (${prefix[pos].ascii_str()}) > ${node.character} (${node.character.ascii_str()})')
|
||||
node_id = node.right_id
|
||||
} else {
|
||||
// Character matches
|
||||
println('Character matches: ${prefix[pos]} (${prefix[pos].ascii_str()}) == ${node.character} (${node.character.ascii_str()})')
|
||||
|
||||
// Update the current path
|
||||
if node.character != 0 { // Skip the root node character
|
||||
current_path += node.character.ascii_str()
|
||||
println('Updated path: "${current_path}"')
|
||||
}
|
||||
|
||||
if pos == prefix.len - 1 {
|
||||
// We've reached the end of the prefix
|
||||
println('Reached end of prefix')
|
||||
|
||||
// If this node is the end of a string, add it to the result
|
||||
if node.is_end_of_string {
|
||||
println('Node is end of string, adding key: "${current_path}"')
|
||||
result << current_path
|
||||
}
|
||||
|
||||
// Collect all keys from the middle child
|
||||
if node.middle_id != 0 {
|
||||
println('Collecting from middle child with path: "${current_path}"')
|
||||
self.collect_keys_with_prefix(node.middle_id, current_path, prefix, mut result)!
|
||||
}
|
||||
|
||||
break
|
||||
} else {
|
||||
// Move to the next character in the prefix
|
||||
println('Moving to next character in prefix: ${prefix[pos+1]} (${prefix[pos+1].ascii_str()})')
|
||||
node_id = node.middle_id
|
||||
pos++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if node_id == 0 || pos < prefix.len - 1 {
|
||||
// Prefix not found or we didn't reach the end of the prefix
|
||||
println('Prefix not found or didn\'t reach end of prefix, returning empty result')
|
||||
return []string{}
|
||||
}
|
||||
|
||||
println('Found ${result.len} keys with prefix "${prefix}": ${result}')
|
||||
return result
|
||||
}
|
||||
|
||||
// Helper function to collect all keys with a given prefix
|
||||
fn (mut self TST) collect_keys_with_prefix(node_id u32, current_path string, prefix string, mut result []string) ! {
|
||||
if node_id == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
node := deserialize_node(self.db.get(node_id)!)!
|
||||
println('Collecting keys with prefix from node ${node_id}: character=${node.character} (${node.character.ascii_str()}), is_end=${node.is_end_of_string}, left=${node.left_id}, middle=${node.middle_id}, right=${node.right_id}, current_path="${current_path}"')
|
||||
|
||||
// Construct the path for this node
|
||||
path := current_path + node.character.ascii_str()
|
||||
println('Path for node: "${path}"')
|
||||
|
||||
// If this node is the end of a string, add it to the result
|
||||
if node.is_end_of_string {
|
||||
println('Node is end of string, adding key: "${path}"')
|
||||
result << path
|
||||
}
|
||||
|
||||
// Recursively collect keys from the middle child (keys that extend this prefix)
|
||||
if node.middle_id != 0 {
|
||||
println('Collecting from middle child with path: "${path}"')
|
||||
self.collect_keys_with_prefix(node.middle_id, path, prefix, mut result)!
|
||||
}
|
||||
|
||||
// Also collect keys from left and right children
|
||||
// This is necessary because multiple keys might share the same prefix
|
||||
if node.left_id != 0 {
|
||||
println('Collecting from left child with path: "${current_path}"')
|
||||
self.collect_keys_with_prefix(node.left_id, current_path, prefix, mut result)!
|
||||
}
|
||||
if node.right_id != 0 {
|
||||
println('Collecting from right child with path: "${current_path}"')
|
||||
self.collect_keys_with_prefix(node.right_id, current_path, prefix, mut result)!
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to recursively collect all keys under a node
|
||||
fn (mut self TST) collect_all_keys(node_id u32, current_path string, mut result []string) ! {
|
||||
if node_id == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
node := deserialize_node(self.db.get(node_id)!)!
|
||||
println('Collecting all from node ${node_id}: character=${node.character} (${node.character.ascii_str()}), is_end=${node.is_end_of_string}, left=${node.left_id}, middle=${node.middle_id}, right=${node.right_id}, current_path="${current_path}"')
|
||||
|
||||
// Construct the path for this node
|
||||
path := current_path + node.character.ascii_str()
|
||||
println('Path for node: "${path}"')
|
||||
|
||||
// If this node is the end of a string, add it to the result
|
||||
if node.is_end_of_string {
|
||||
println('Node is end of string, adding key: "${path}"')
|
||||
result << path
|
||||
}
|
||||
|
||||
// Recursively collect keys from all children
|
||||
if node.left_id != 0 {
|
||||
println('Collecting all from left child with path: "${current_path}"')
|
||||
self.collect_all_keys(node.left_id, current_path, mut result)!
|
||||
}
|
||||
if node.middle_id != 0 {
|
||||
println('Collecting all from middle child with path: "${path}"')
|
||||
self.collect_all_keys(node.middle_id, path, mut result)!
|
||||
}
|
||||
if node.right_id != 0 {
|
||||
println('Collecting all from right child with path: "${current_path}"')
|
||||
self.collect_all_keys(node.right_id, current_path, mut result)!
|
||||
}
|
||||
}
|
||||
|
||||
// Gets all values for keys with a given prefix
|
||||
pub fn (mut self TST) getall(prefix string) ![][]u8 {
|
||||
println('Getting all values with prefix: "${prefix}"')
|
||||
// Get all matching keys
|
||||
keys := self.list(prefix)!
|
||||
|
||||
// Get values for each key
|
||||
mut values := [][]u8{}
|
||||
for key in keys {
|
||||
if value := self.get(key) {
|
||||
values << value
|
||||
}
|
||||
}
|
||||
|
||||
println('Found ${values.len} values with prefix "${prefix}"')
|
||||
return values
|
||||
}
|
||||
204
lib/data/tst/tst_list.v
Normal file
204
lib/data/tst/tst_list.v
Normal file
@@ -0,0 +1,204 @@
|
||||
module tst
|
||||
|
||||
import freeflowuniverse.herolib.data.ourdb
|
||||
|
||||
// Lists all keys with a given prefix
|
||||
pub fn (mut self TST) list(prefix string) ![]string {
|
||||
normalized_prefix := namefix(prefix)
|
||||
println('Listing keys with prefix: "${prefix}" (normalized: "${normalized_prefix}")')
|
||||
mut result := []string{}
|
||||
|
||||
// Handle empty prefix case - will return all keys
|
||||
if normalized_prefix.len == 0 {
|
||||
println('Empty prefix, collecting all keys')
|
||||
self.collect_all_keys(self.root_id, '', mut result)!
|
||||
println('Found ${result.len} keys: ${result}')
|
||||
return result
|
||||
}
|
||||
|
||||
// Find the prefix node first
|
||||
result_info := self.navigate_to_prefix(self.root_id, normalized_prefix, 0)
|
||||
|
||||
if !result_info.found {
|
||||
println('Prefix node not found for "${normalized_prefix}"')
|
||||
return result // Empty result
|
||||
}
|
||||
|
||||
println('Found node for prefix "${normalized_prefix}" at node ${result_info.node_id}, collecting keys')
|
||||
|
||||
// Collect all keys from the subtree rooted at the prefix node
|
||||
self.collect_keys_with_prefix(result_info.node_id, result_info.prefix, mut result)!
|
||||
|
||||
println('Found ${result.len} keys with prefix "${normalized_prefix}": ${result}')
|
||||
return result
|
||||
}
|
||||
|
||||
// Result struct for prefix navigation
|
||||
struct PrefixSearchResult {
|
||||
found bool
|
||||
node_id u32
|
||||
prefix string
|
||||
}
|
||||
|
||||
// Navigate to the node corresponding to a prefix
|
||||
fn (mut self TST) navigate_to_prefix(node_id u32, prefix string, pos int) PrefixSearchResult {
|
||||
// Base case: no node or out of bounds
|
||||
if node_id == 0 || pos >= prefix.len {
|
||||
return PrefixSearchResult{
|
||||
found: false
|
||||
node_id: 0
|
||||
prefix: ''
|
||||
}
|
||||
}
|
||||
|
||||
// Get node
|
||||
node_data := self.db.get(node_id) or {
|
||||
return PrefixSearchResult{found: false, node_id: 0, prefix: ''}
|
||||
}
|
||||
|
||||
node := deserialize_node(node_data) or {
|
||||
return PrefixSearchResult{found: false, node_id: 0, prefix: ''}
|
||||
}
|
||||
|
||||
println('Navigating node ${node_id}: char=${node.character} (${node.character.ascii_str()}), pos=${pos}, prefix_char=${prefix[pos]} (${prefix[pos].ascii_str()})')
|
||||
|
||||
// Character comparison
|
||||
if prefix[pos] < node.character {
|
||||
// Go left
|
||||
println('Going left')
|
||||
return self.navigate_to_prefix(node.left_id, prefix, pos)
|
||||
} else if prefix[pos] > node.character {
|
||||
// Go right
|
||||
println('Going right')
|
||||
return self.navigate_to_prefix(node.right_id, prefix, pos)
|
||||
} else {
|
||||
// Character match
|
||||
println('Character match found')
|
||||
|
||||
// Check if we're at the end of the prefix
|
||||
if pos == prefix.len - 1 {
|
||||
println('Reached end of prefix at node ${node_id}')
|
||||
// Return the exact prefix string that was passed in
|
||||
return PrefixSearchResult{
|
||||
found: true
|
||||
node_id: node_id
|
||||
prefix: prefix
|
||||
}
|
||||
}
|
||||
|
||||
// Not at end of prefix, check middle child
|
||||
if node.middle_id == 0 {
|
||||
println('No middle child, prefix not found')
|
||||
return PrefixSearchResult{found: false, node_id: 0, prefix: ''}
|
||||
}
|
||||
|
||||
// Continue to middle child with next character
|
||||
return self.navigate_to_prefix(node.middle_id, prefix, pos + 1)
|
||||
}
|
||||
}
|
||||
|
||||
// Collect all keys in a subtree that have a given prefix
|
||||
fn (mut self TST) collect_keys_with_prefix(node_id u32, prefix string, mut result []string) ! {
|
||||
if node_id == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// Get node
|
||||
node_data := self.db.get(node_id) or { return }
|
||||
node := deserialize_node(node_data) or { return }
|
||||
|
||||
println('Collecting from node ${node_id}, char=${node.character} (${node.character.ascii_str()}), prefix="${prefix}"')
|
||||
|
||||
// If this node is an end of string and it's not the root, we found a key
|
||||
if node.is_end_of_string && node.character != 0 {
|
||||
// The prefix may already contain this node's character
|
||||
if prefix.len == 0 || prefix[prefix.len-1] != node.character {
|
||||
println('Found complete key: "${prefix}${node.character.ascii_str()}"')
|
||||
result << prefix + node.character.ascii_str()
|
||||
} else {
|
||||
println('Found complete key: "${prefix}"')
|
||||
result << prefix
|
||||
}
|
||||
}
|
||||
|
||||
// Recursively search all children
|
||||
if node.left_id != 0 {
|
||||
self.collect_keys_with_prefix(node.left_id, prefix, mut result)!
|
||||
}
|
||||
|
||||
// For middle child, we need to add this node's character to the prefix
|
||||
if node.middle_id != 0 {
|
||||
mut next_prefix := prefix
|
||||
if node.character != 0 { // Skip root node
|
||||
// Only add the character if it's not already at the end of the prefix
|
||||
if prefix.len == 0 || prefix[prefix.len-1] != node.character {
|
||||
next_prefix += node.character.ascii_str()
|
||||
}
|
||||
}
|
||||
self.collect_keys_with_prefix(node.middle_id, next_prefix, mut result)!
|
||||
}
|
||||
|
||||
if node.right_id != 0 {
|
||||
self.collect_keys_with_prefix(node.right_id, prefix, mut result)!
|
||||
}
|
||||
}
|
||||
|
||||
// Collect all keys in the tree
|
||||
fn (mut self TST) collect_all_keys(node_id u32, prefix string, mut result []string) ! {
|
||||
if node_id == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// Get node
|
||||
node_data := self.db.get(node_id) or { return }
|
||||
node := deserialize_node(node_data) or { return }
|
||||
|
||||
// Calculate current path
|
||||
mut current_prefix := prefix
|
||||
|
||||
// If this is not the root, add the character
|
||||
if node.character != 0 {
|
||||
current_prefix += node.character.ascii_str()
|
||||
}
|
||||
|
||||
// If this marks the end of a key, add it to the result
|
||||
if node.is_end_of_string {
|
||||
println('Found key: ${current_prefix}')
|
||||
if current_prefix !in result {
|
||||
result << current_prefix
|
||||
}
|
||||
}
|
||||
|
||||
// Visit all children
|
||||
if node.left_id != 0 {
|
||||
self.collect_all_keys(node.left_id, prefix, mut result)!
|
||||
}
|
||||
|
||||
if node.middle_id != 0 {
|
||||
self.collect_all_keys(node.middle_id, current_prefix, mut result)!
|
||||
}
|
||||
|
||||
if node.right_id != 0 {
|
||||
self.collect_all_keys(node.right_id, prefix, mut result)!
|
||||
}
|
||||
}
|
||||
|
||||
// Gets all values for keys with a given prefix
|
||||
pub fn (mut self TST) getall(prefix string) ![][]u8 {
|
||||
normalized_prefix := namefix(prefix)
|
||||
println('Getting all values with prefix: "${prefix}" (normalized: "${normalized_prefix}")')
|
||||
|
||||
// Get all matching keys
|
||||
keys := self.list(normalized_prefix)!
|
||||
|
||||
// Get values for each key
|
||||
mut values := [][]u8{}
|
||||
for key in keys {
|
||||
if value := self.get(key) {
|
||||
values << value
|
||||
}
|
||||
}
|
||||
|
||||
println('Found ${values.len} values with prefix "${normalized_prefix}"')
|
||||
return values
|
||||
}
|
||||
Reference in New Issue
Block a user