I'm working on a project where I'm building an AVL tree using a file to store it. When I write to the file the data saves correctly but when I read the location for the data it gives me back the data for that was previously stored in the file.
Here's my code so far
#include "stdafx.h"
#include "AVL.h"
#include <string>
#include <iostream>
#include <fstream>
// When tree is initialized open the input and output files
AVL::AVL(std::string treeFilePath)
{
inputTreeFile.open(treeFilePath, std::ios::binary);
if (inputTreeFile.fail())
std::cout << "failed to open input file" << std::endl;
outputTreeFile.open(treeFilePath, std::ios::binary | std::ios::trunc);
if (outputTreeFile.fail())
std::cout << "failed to open output file" << std::endl;
}
// close the tree files when destructing the tree
AVL::~AVL()
{
inputTreeFile.close();
outputTreeFile.close();
}
// writes the node to a given location of the output file
// based on the index of that node
void AVL::writeToDisk(Node node)
{
outputTreeFile.seekp(node.index * sizeof(Node));
char * buffer = (char *)&node;
outputTreeFile.write(buffer, sizeof(Node));
outputTreeFile.flush();
if (outputTreeFile.fail())
std::cout << "failed to write to output file" << std::endl;
}
// reads a node from the file given an index of the file in order to generate an offset
AVL::Node AVL::readFromDisk(int index)
{
Node node;
if (index == -1)
return node;
inputTreeFile.seekg(index * sizeof(Node));
inputTreeFile.read((char *)&node, sizeof(Node));
if (inputTreeFile.fail())
std::cout << "failed to read from input file" << std::endl;
return node;
}
// inserts a node into the tree and then checks balance factors to decide if a rotation is needed
// to maintain the balance of the tree
void AVL::insert(char input[30])
{
// If the root is null we only need to do a dumb insert and nothing else
if (root == -1)
{
root = 0;
node1.balanceFactor = 0;
node1.count = 1;
node1.index = 0;
node1.leftChild = -1;
node1.rightChild = -1;
strcpy_s(node1.value, input);
uniqueInserts++;
writeToDisk(node1);
return;
}
int p; // our current location on the tree
int q; // lags behind current node
int a; // the last parent of current node that had a balance factor of +- 1
int f; // lags behind recent nonzero
p = a = root;
q = f = -1;
node1 = readFromDisk(root);
// while the current node is not at the child of a leaf or a duplicate value keep traversing through the tree
// keeping track of the most recent nonzero balance factor node
while (p != -1)
{
if (strcmp(input, node1.value) == 0)
{
node1.count++;
writeToDisk(node1);
return;
}
if (node1.balanceFactor != 0)
{
a = p;
f = q;
}
q = p;
p = (strcmp(input, node1.value) < 0) ? node1.leftChild : node1.rightChild;
node1 = readFromDisk(p);
}
// Now the previous node is a leaf and the current node is the child of that leaf so we need
// to create a new node to insert
// node to insert is y
node1.balanceFactor = 0;
node1.count = 1;
int y = node1.index = uniqueInserts;
node1.leftChild = -1;
node1.rightChild = -1;
strcpy_s(node1.value, input);
uniqueInserts++;
writeToDisk(node1);
int b; // this is the child of the most recent nonzero balance factor in the direction of the potential rotation
int displacement; // this is used to correct balance factors later
// we need to know if the new node we are inserting is the left or the right child of the previous node so we
// can have the correct child pointer point to the new node
node2 = readFromDisk(q);
if (strcmp(input, node2.value) < 0)
node2.leftChild = y;
else
node2.rightChild = y;
writeToDisk(node2);
// if the value of the node we just inserted is less than that of the most recent nonzero balance factor node
// then we went left so the pivot needs to be the left child else its the right child
// the displacement is set based on the direction taken
node1 = readFromDisk(a);
if (strcmp(input, node1.value) > 0)
{
p = node1.rightChild;
b = p;
displacement = -1;
}
else
{
p = node1.leftChild;
b = p;
displacement = 1;
}
// then we traverse down from the most recent nonzero balance factor node to the node we inserted setting balance factors
// on the way down
while (p != y)
{
node1 = readFromDisk(p);
if (strcmp(input, node1.value) > 0)
{
node1.balanceFactor = -1;
p = node1.rightChild;
}
else
{
node1.balanceFactor = 1;
p = node1.leftChild;
}
writeToDisk(node1);
}
node1 = readFromDisk(a);
// if the tree was completely balanced recentNonZero will be at the root of the tree and our insert will
// have pushed the tree slightly out of balance
if (0 == node1.balanceFactor)
{
node1.balanceFactor = displacement;
writeToDisk(node1);
return;
}
// if the most reent nonzero balance factor is opposite the displacement then we put the tree back in balance
if (node1.balanceFactor == -displacement)
{
node1.balanceFactor = 0;
writeToDisk(node1);
return;
}
node2 = readFromDisk(b);
// At this point we have thrown the tree out of balance by inserting
// The displacement tells us the first direction of the rotation and the most recent non-zero balance factor node (b)
// tells us the second direction
if (displacement == 1)
{
if (node2.balanceFactor == 1) //LL
{
// set a's left child to b's right and b's right to a
// then fix balance factors
node1.leftChild = node2.rightChild;
node2.rightChild = a;
node1.balanceFactor = node2.balanceFactor = 0;
}
else //LR
{
// create a new node c that is b's right child
node3 = readFromDisk(node2.rightChild);
// for this rotation the order of a, b, and c are b < c < a
// so c gets pulled up to the middle and sets its children to b (left) and a (right)
// this cuts off c's children though so prior to this c's left needs to be attached as b's right
// and c's right is attached as a's left
// then attach c's children to a and b
node1.leftChild = node3.rightChild;
node2.rightChild = node3.leftChild;
// then set c's children to b and a
node3.leftChild = b;
node3.rightChild = a;
// then we set a and b's balance factors to 0 and correct one of them depending on what
// c's balance factor
node1.balanceFactor = node2.balanceFactor = 0;
if (node3.balanceFactor == 1)
node1.balanceFactor = -1;
else if (node3.balanceFactor == -1)
node2.balanceFactor = 1;
// then c's balance factor is fixed and set to 0
node3.balanceFactor = 0;
writeToDisk(node3);
b = node3.index; // this is for reattaching the subtree to the proper parent
}
}
else // again the next parts are symmetric so almost all the operations are just flipped
{
if (node2.balanceFactor == -1) //RR
{
node1.rightChild = node2.leftChild;
node2.leftChild = a;
node1.balanceFactor = node2.balanceFactor = 0;
}
else //RL
{
node3 = readFromDisk(node2.leftChild);
node1.rightChild = node3.leftChild;
node2.leftChild = node3.rightChild;
node3.rightChild = b;
node3.leftChild = a;
node1.balanceFactor = node2.balanceFactor = 0;
if (node3.balanceFactor == 1)
node2.balanceFactor = -1;
else if (node3.balanceFactor == -1)
node1.balanceFactor = 1;
node3.balanceFactor = 0;
writeToDisk(node3);
b = node3.index;
}
}
writeToDisk(node1);
writeToDisk(node2);
// if the parent of the recent non zero balance factor node was null then there were no nodes with a nonzero balance
// or the only one was the root. in either case the recent non zero was the root so whatever is in position b needs to
// be set as the root
if (f == -1)
{
root = b;
return;
}
node3 = readFromDisk(f);
// if the left child of the recent nonzero BF parent node is the recent nonzero node we need to attach the new
// root of the subtree (b) in a's place
if (node3.leftChild == a)
node3.leftChild = b;
else // otherwise its the right child that needs reattached
node3.rightChild = b;
writeToDisk(node3);
}
heres my main
#include "stdafx.h"
#include "AVL.h"
#include <fstream>
#include <iostream>
#include <chrono>
using namespace std;
int main()
{
string inputFilePath = "C:\\Users\\DMCar\\Desktop\\a1s1.txt";
// set of delimiters for reading in the file
char delimiters[11] = { 9 , 10 , 13 , 32 , '.' , ',' , '!' , ';' , ':' , '(' , ')' };
AVL avl("C:\\Users\\DMCar\\Desktop\\test.txt");
std::ifstream inputStream;
inputStream.open(inputFilePath, std::ios::binary); // binary flag is set to read the file one byte at a time
// if we couldn't open the file, let the user know and return
if (inputStream.fail())
{
std::cout << "Could not open file" << std::endl;
return 0;
}
bool isDelimiter = false;
char nextChar;
inputStream.get(nextChar);
char input[30]{ 0 };
int index = 0;
// keep getting bytes until we have reached the end of the file
while (!inputStream.eof())
{
// loop through the delimiters to check if the character read is one of them
for each (char delimiter in delimiters)
{
// if the character is a delimiter we check to see if the word is empty
// if the word is not empty it is sent to the trees insert function
if (nextChar == delimiter)
{
if (input[0])
{
cout << input << endl;
avl.insert(input);
}
memset(input, 0, sizeof(input));
index = -1;
isDelimiter = true;
}
}
// if the character was not a delimiter we need to append it to next word
if (!isDelimiter)
input[index] = nextChar;
index++;
isDelimiter = false; // reset is delimiter
if (inputStream.eof())
{
int i = 1;
i++;
}
inputStream.get(nextChar); // try to read the next character
}
if (input[0] != 0)
{
avl.insert(input);
}
return 0;
}
I am inserting from act 1 scene 1 of the collective works of shakespeare but the issue i have happens when inserting the first 'I' at this section
writeToDisk(node2);
// if the value of the node we just inserted is less than that of the most recent nonzero balance factor node
// then we went left so the pivot needs to be the left child else its the right child
// the displacement is set based on the direction taken
node1 = readFromDisk(a);
at this point node2 contains 'ACT' and its right child is 1. this gets written to position 0. since a = 0 here node1 will be read from position 0. therefore what is read back should be the same as node2 but node1 does not have the correct right child. the right child it returns is -1 when it should be 1. I have stepped through the code and found that all of the correct data is written to the file but when the read is called it behaves like the last write never happened.
here is the structure of Node
struct Node {
int leftChild = -1;
int rightChild = -1;
char value[30];
unsigned int count = 0;
int balanceFactor = 0;
int index = -1;
};
does anyone know what might be happening? all guesses welcome I've been looking at this for several hours now and cant figure it out. Also, how can I fix this?
That's because ifstream only read the file at the first time and keep content in buffer for later read.
Since you will operate the file in both read and write mode, you may just only one file handler.
Step 1: We combine read and write stream handler to one, at AVL.h:
//std::ifstream inputTreeFile;
//std::ofstream outputTreeFile;
std::fstream treeFile;
Step 2: Change AVL constructor:
AVL::AVL(std::string treeFilePath)
{
//inputTreeFile.open(treeFilePath, std::ios::binary | std::ios::);
//if (inputTreeFile.fail())
// std::cout << "failed to open input file" << std::endl;
//outputTreeFile.open(treeFilePath, std::ios::binary | std::ios::trunc);
//if (outputTreeFile.fail())
// std::cout << "failed to open output file" << std::endl;
treeFile.open(treeFilePath, std::ios::binary | std::ios::trunc | std::ios::in | std::ios::out);
if (treeFile.fail())
std::cout << "failed to open file" << std::endl;
}
Step 3: Replace your inputTreeFile and outputTreeFile to this treeFile in read and write method:
// writes the node to a given location of the output file
// based on the index of that node
void AVL::writeToDisk(Node node)
{
treeFile.seekp(node.index * sizeof(Node));
char * buffer = (char *)&node;
treeFile.write(buffer, sizeof(Node));
treeFile.flush();
if (treeFile.fail())
std::cout << "failed to write to output file" << std::endl;
}
// reads a node from the file given an index of the file in order to generate an offset
AVL::Node AVL::readFromDisk(int index)
{
Node node;
if (index == -1)
return node;
treeFile.seekg(index * sizeof(Node));
treeFile.read((char *)&node, sizeof(Node));
char * p = (char *)&node;
if (treeFile.fail())
std::cout << "failed to read from input file" << std::endl;
return node;
}
Now you shall see the expect result.