Skip to content

Commit 9ce8d32

Browse files
committed
add visualizer.md for live btree state
Signed-off-by: Arshdeep54 <balarsh535@gmail.com>
1 parent 91794f5 commit 9ce8d32

File tree

6 files changed

+199
-27
lines changed

6 files changed

+199
-27
lines changed

src/btree/README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
# Btree Implementation
22

3+
# Usage
4+
5+
- Run `cargo run`
6+
- Start filling up the btree with `BTREE insert 4 heykey4` and so on..
7+
- Search using `BTREE search 4`
8+
- Delete using `BTREE delete 4`
9+
10+
### See the live visualization of the Btree in `/tests/visualizer.md` (Use CTRL+SHIFT+V for rendering markdown)
11+
312
# Resouces
413

514
- https://www.dataquest.io/blog/b-tree-data-structure/

src/btree/mod.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ use std::io::{self, Result};
66
mod metadata;
77
mod node;
88
mod paging;
9-
pub const DEGREE: i32 = 3;
9+
pub mod utils;
10+
11+
pub const DEGREE: i32 = 2;
1012
pub const MIN_ITEMS: i32 = DEGREE - 1;
1113
pub const MAX_ITEMS: i32 = DEGREE * 2;
1214

src/btree/node.rs

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use std::{fmt, io::Result};
22

33
use super::{
4-
Item, MAX_ITEMS, MIN_ITEMS,
4+
Item, MAX_ITEMS,
55
paging::{Page, PageID, Pager},
66
};
77

@@ -75,6 +75,8 @@ impl Node {
7575
if pos > self.num_items || pos < 0 {
7676
return;
7777
}
78+
79+
// Find the correct position to maintain sorted order
7880
let mut insert_pos = pos as usize;
7981
while insert_pos < self.items.len() && self.items[insert_pos].key < item.key {
8082
insert_pos += 1;
@@ -96,11 +98,11 @@ impl Node {
9698
let new_id = pager.allocate_page()?;
9799
let mut new_node = Node::new(new_id);
98100

99-
let mid = MIN_ITEMS;
101+
let mid = self.num_items / 2;
100102
let mid_item = self.items[mid as usize].clone();
101103

102-
new_node.items = self.items[mid as usize..].to_vec();
103-
new_node.num_items = self.num_items - mid;
104+
new_node.items = self.items[mid as usize + 1..].to_vec();
105+
new_node.num_items = self.num_items - mid - 1;
104106

105107
if !self.is_leaf() {
106108
new_node.children = self.children[mid as usize + 1..].to_vec();
@@ -130,20 +132,31 @@ impl Node {
130132
return;
131133
}
132134

133-
if self.children[pos as usize].num_items >= MAX_ITEMS {
134-
let (mid_item, new_node) = self.children[pos as usize].split(pager).unwrap();
135-
self.insert_item_at(pos, mid_item);
136-
self.insert_child_at(pos + 1, new_node);
135+
let child_pos = if pos >= self.num_items {
136+
(self.num_items) as usize
137+
} else {
138+
pos as usize
139+
};
140+
141+
if self.children[child_pos].num_items >= MAX_ITEMS {
142+
let (mid_item, new_node) = self.children[child_pos].split(pager).unwrap();
143+
let mid_key = mid_item.key;
144+
self.insert_item_at(child_pos as i32, mid_item);
145+
self.insert_child_at(child_pos as i32 + 1, new_node);
137146

138-
let condition = item.key - self.items[pos as usize].key;
139-
if condition < 0 {
140-
} else if condition > 0 {
141-
pos += 1;
147+
if item.key > mid_key {
148+
pos = child_pos as i32 + 1;
142149
} else {
143-
return;
150+
pos = child_pos as i32;
144151
}
145152
}
146-
self.children[pos as usize].insert(item, pager);
153+
154+
let child_pos = if pos >= self.num_items {
155+
(self.num_items) as usize
156+
} else {
157+
pos as usize
158+
};
159+
self.children[child_pos].insert(item, pager);
147160
}
148161
}
149162

src/btree/tests/visualizer.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# B-tree Visualization
2+
3+
Current state of the B-tree:
4+
5+
```mermaid
6+
graph TD
7+
n8["Node 8<br>51:he555"]
8+
n3["Node 3<br>4:he555 | 6:h555"]
9+
n1["Node 1<br>1:he555 | 2:he555 | 3:he555"]
10+
n2["Node 2<br>5:he555"]
11+
n9["Node 9<br>7:h555 | 8:he555 | 9:h555"]
12+
n7["Node 7<br>5224:he555 | 5855:he555 | 58555:he555"]
13+
n4["Node 4<br>58:he555 | 528:he555"]
14+
n5["Node 5<br>5555:he555"]
15+
n6["Node 6<br>9888:h555 | 55555:he555"]
16+
n10["Node 10<br>58558:he555 | 58588:he555"]
17+
n8 --> n3
18+
n3 --> n1
19+
n3 --> n2
20+
n3 --> n9
21+
n8 --> n7
22+
n7 --> n4
23+
n7 --> n5
24+
n7 --> n6
25+
n7 --> n10
26+
```

src/btree/utils.rs

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
use std::fs::{self, write};
2+
use std::io;
3+
use std::path::Path;
4+
5+
use super::{Btree, Node};
6+
7+
pub struct Visualizer {
8+
output_path: String,
9+
}
10+
11+
impl Visualizer {
12+
pub fn new(path: &str) -> Self {
13+
if let Some(parent) = Path::new(path).parent() {
14+
if !parent.exists() {
15+
fs::create_dir_all(parent).expect("Failed to create visualization directory");
16+
}
17+
}
18+
19+
Visualizer {
20+
output_path: path.to_string(),
21+
}
22+
}
23+
24+
pub fn update(&self, btree: &Btree) -> io::Result<()> {
25+
let mermaid = self.generate_mermaid(btree);
26+
let content = format!(
27+
"# B-tree Visualization\n\n\
28+
Current state of the B-tree:\n\n\
29+
```mermaid\n\
30+
graph TD\n\
31+
{mermaid}\n\
32+
```\n"
33+
);
34+
write(&self.output_path, content)
35+
}
36+
37+
fn generate_mermaid(&self, btree: &Btree) -> String {
38+
match &btree.root {
39+
None => String::from("empty[Empty Tree]"),
40+
Some(root) => {
41+
let mut nodes = Vec::new();
42+
let mut edges = Vec::new();
43+
self.generate_node_diagram(&mut nodes, &mut edges, root, None);
44+
format!("{}\n{}", nodes.join("\n"), edges.join("\n"))
45+
}
46+
}
47+
}
48+
49+
fn generate_node_diagram(
50+
&self,
51+
nodes: &mut Vec<String>,
52+
edges: &mut Vec<String>,
53+
node: &Node,
54+
parent_id: Option<u32>,
55+
) {
56+
let items: Vec<String> = node
57+
.items
58+
.iter()
59+
.map(|item| format!("{}:{}", item.key, self.truncate_value(&item.val, 5)))
60+
.collect();
61+
62+
nodes.push(format!(
63+
" n{}[\"Node {}<br>{}\"]",
64+
node.id,
65+
node.id,
66+
items.join(" | ")
67+
));
68+
69+
if let Some(parent) = parent_id {
70+
edges.push(format!(" n{} --> n{}", parent, node.id));
71+
}
72+
73+
for child in &node.children {
74+
self.generate_node_diagram(nodes, edges, child, Some(node.id));
75+
}
76+
}
77+
78+
fn truncate_value(&self, value: &str, max_len: usize) -> String {
79+
if value.len() <= max_len {
80+
value.to_string()
81+
} else {
82+
format!("{}...", &value[..max_len])
83+
}
84+
}
85+
}

src/parsing/mod.rs

Lines changed: 49 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
use crate::{IndexSession, btree::Item};
1+
use crate::{
2+
IndexSession,
3+
btree::{Item, utils::Visualizer},
4+
};
5+
use std::path::PathBuf;
26

37
struct Command<KeyType, ValType> {
48
index_type: String,
@@ -12,30 +16,57 @@ where
1216
KeyType: std::str::FromStr,
1317
ValType: std::str::FromStr,
1418
{
15-
fn new(command: &str) -> Self {
16-
let tokens: Vec<&str> = command.split(" ").collect();
19+
fn new(command: &str) -> Option<Self> {
20+
let tokens: Vec<&str> = command.split_whitespace().collect();
21+
if tokens.is_empty() {
22+
return None;
23+
}
24+
25+
let index_type = tokens[0].to_string();
26+
if tokens.len() < 2 {
27+
return None;
28+
}
29+
30+
let index_function = tokens[1].to_string();
1731
let key = if tokens.len() > 2 {
18-
Some(tokens[2].parse::<KeyType>().ok().unwrap())
32+
tokens[2].parse::<KeyType>().ok()
1933
} else {
2034
None
2135
};
2236
let value = if tokens.len() > 3 {
23-
Some(tokens[3].parse::<ValType>().ok().unwrap())
37+
tokens[3].parse::<ValType>().ok()
2438
} else {
2539
None
2640
};
27-
Command {
28-
index_type: tokens[0].to_string(),
29-
index_function: tokens[1].to_string(),
41+
42+
Some(Command {
43+
index_type,
44+
index_function,
3045
key,
3146
value,
32-
}
47+
})
3348
}
3449
}
50+
3551
pub fn parse_command(index_session: &mut IndexSession, command: &str) {
3652
let trimmed_command = command.trim();
53+
if trimmed_command.is_empty() {
54+
return;
55+
}
3756
println!("Command: {trimmed_command}");
38-
let cmd: Command<i32, String> = Command::new(command);
57+
58+
let mut viz_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
59+
viz_path.push("src");
60+
viz_path.push("btree");
61+
viz_path.push("tests");
62+
viz_path.push("visualizer.md");
63+
let visualizer = Visualizer::new(viz_path.to_str().unwrap());
64+
65+
let cmd = match Command::<i32, String>::new(trimmed_command) {
66+
Some(cmd) => cmd,
67+
None => return,
68+
};
69+
3970
if cmd.index_type.as_str() == "BTREE" || cmd.index_type.as_str() == "btree" {
4071
match cmd.index_function.as_str() {
4172
"INSERT" | "insert" => {
@@ -58,7 +89,10 @@ pub fn parse_command(index_session: &mut IndexSession, command: &str) {
5889
};
5990

6091
index_session.btree.insert(Item { key, val });
61-
println!("{:?}", index_session.btree);
92+
93+
if let Err(e) = visualizer.update(&index_session.btree) {
94+
eprintln!("Failed to update visualization: {e}");
95+
}
6296
}
6397
"SEARCH" | "search" => {
6498
let key = match cmd.key {
@@ -85,7 +119,10 @@ pub fn parse_command(index_session: &mut IndexSession, command: &str) {
85119
match index_session.btree.delete(key) {
86120
Ok(_) => {
87121
println!("Successfully deleted key {key}");
88-
println!("Tree after deletion: {:?}", index_session.btree);
122+
123+
if let Err(e) = visualizer.update(&index_session.btree) {
124+
eprintln!("Failed to update visualization: {e}");
125+
}
89126
}
90127
Err(e) => println!("Failed to delete key {key}: {e}"),
91128
}

0 commit comments

Comments
 (0)