// SecondaryIndex.cpp // CSI 2131 Asst 2b by Naim R. El-Far (naim@discover.uottawa.ca) // CSI 2131 - Winter 2006 - Prof. L.M. Moura // File: SecondaryIndex.cpp // Desc: Class SecondaryIndex definitions #include #include #include #include #include #include #include "SecondaryIndex.h" #include "KeyList.h" using namespace std; SecondaryIndex::SecondaryIndex() { //Default constructor indexFileName = ""; indexFileStream; numberOfRecordsInIndex = 0; pKeyList = NULL; } SecondaryIndex::SecondaryIndex(string secIndexFileName, string keyListFileName) { //Filename constructor indexFileName = secIndexFileName; indexFileStream.open(indexFileName.c_str(), ios::out); indexFileStream.close(); indexFileStream.open(indexFileName.c_str(), ios::in | ios::out | ios::binary); if (!indexFileStream) { cerr << endl << "------------------------------------------------" << endl << "Error! Specified index file could not be opened!" << endl << "------------------------------------------------" << endl << endl; exit(1); } else cout << "Index file " << secIndexFileName << " opened successfully." << endl; pKeyList = new KeyList(keyListFileName); numberOfRecordsInIndex = 0; } SecondaryIndex::~SecondaryIndex(){ //Destructor indexFileStream.close(); delete pKeyList; } void SecondaryIndex::SpaceFormatLastName(string& lastName) { //Given a last name, format it so that it would adhere to the 20-character field length specified stringstream lastNameStream(stringstream::in | stringstream::out); lastNameStream << setw(20) << setiosflags(ios::left) << lastName; getline(lastNameStream, lastName); } void SecondaryIndex::Insert(string lastName, long int listStart) { //Given a last name and a list start value, insert the tuple where the file pointer currently is. Another function has to position the file pointer; this function simply inserts at the current file pointer position //Write last name in text mode indexFileStream << setw(20) << setiosflags(ios::left) << lastName; //Write list start value in binary mode after converting from decimal to binary number indexFileStream.write((char const*)(&listStart), 4); } void SecondaryIndex::ReadByRRN(long int RRN, string& lastName, long int& listStart) { //Given a RRN and a string and long int in which to store the read values, retrieve the last name and list start values and sotre them in the given variables long int entryOffset = indexFileStream.tellg(); indexFileStream.clear(); indexFileStream.seekg(RRN * SEC_INDEX_RECORD_SIZE); char lastNameChar[20]; indexFileStream.read(lastNameChar, 20); indexFileStream.read((char*)(&listStart), 4); lastName = ""; for (int i = 0 ; i < 20 ; i++) lastName = lastName + lastNameChar[i]; indexFileStream.clear(); indexFileStream.seekg(entryOffset); } void SecondaryIndex::WriteByRRN(long int RRN, string lastName, long int listStart) { //Store the given tuple at the given RRN position long int entryOffset = indexFileStream.tellg(); indexFileStream.clear(); indexFileStream.seekp(RRN * SEC_INDEX_RECORD_SIZE); Insert(lastName, listStart); indexFileStream.clear(); indexFileStream.seekg(entryOffset); } void SecondaryIndex::ShiftIndexRecords(long int startingRRN) { long int entryOffset = indexFileStream.tellg(); indexFileStream.clear(); long int currentRRN = numberOfRecordsInIndex - 1; bool done = false; while (!done) { if (startingRRN > currentRRN) done = true; //No shifting necessary if inserting at the end else { string tempStudentNumber = ""; long int tempListStart = 0; ReadByRRN(currentRRN, tempStudentNumber, tempListStart); WriteByRRN(currentRRN + 1, tempStudentNumber, tempListStart); currentRRN--; if (currentRRN == startingRRN - 1) done = true; } } indexFileStream.clear(); indexFileStream.seekg(entryOffset); } void SecondaryIndex::Addition(string lastName, string studentNumber, long int byteOffset) { //Housekeeping indexFileStream.clear(); //Convert to canonical form CapitalizeLastName(lastName); //Look for an entry with the same last name in the index bool foundLastNameInIndex = false; long int foundListStart = -1; long int foundRRN = -2; for (int i = 0 ; i < numberOfRecordsInIndex ; i++) { string retrievedLastName = ""; long int retrievedListStart = -1; ReadByRRN(i, retrievedLastName, retrievedListStart); string formattedLastName = lastName; SpaceFormatLastName(formattedLastName); if (retrievedLastName.compare(formattedLastName) == 0) { foundLastNameInIndex = true; foundListStart = retrievedListStart; foundRRN = i; break; } } //If there is no entry with the same last name if (!foundLastNameInIndex) { long int positionWhereToInsert = -1; //Holds the RRN for the record to be inserted in alphabetical order bool positionWhereToInsertFound = false; for (int i = 0 ; i < numberOfRecordsInIndex && !positionWhereToInsertFound ; i++) { string retrievedLastName = ""; long int retrievedListStart = -1; ReadByRRN(i, retrievedLastName, retrievedListStart); if (retrievedLastName.compare(lastName) == 0) { //If the retrieved last name is identical to the one we are inserting, then something's wrong since we can't have duplicates cerr << endl << "-------------------------------------------" << endl << "Error! Duplicates found in secondary index!" << endl << "-------------------------------------------" << endl << endl; exit(1); } else if (retrievedLastName.compare(lastName) < 0) {} //Otherwise if the retrieved last name is alphabetically less than the given one, then keep proceeding else if (retrievedLastName.compare(lastName) > 0) { //If we're retrieving a record alphabetically greater than the given record, then this is where we want to insert the new record positionWhereToInsertFound = true; positionWhereToInsert = i; break; } } if (!positionWhereToInsertFound) { //At this point, if we've gone through all the records and none of them is greater than the given last name, then we must insert the given last name at the end of the index positionWhereToInsert = numberOfRecordsInIndex; } assert(positionWhereToInsert != -1); //Make sure we have found a valid value where to insert the new data ShiftIndexRecords(positionWhereToInsert); //Make room for the record to be inserted in the secondary index long int newRecordListStart = pKeyList->numberOfKeysInList; //A new record in the secondary index needs a new entry in the key list pKeyList->WriteByRRN(newRecordListStart, studentNumber, -1); //Insert the record at the end of the key list (at the end since it is for a brand new last name) pKeyList->IncrementNumberOfKeys(); //Increment the number of keys in the key list WriteByRRN(positionWhereToInsert, lastName, newRecordListStart); //Insert the record in the correct RRN position numberOfRecordsInIndex++; //Increment the number of records in the secondary index } //Otherwise, if there is an entry with the same last name else { long int newListHead = pKeyList->InsertIntoInvertedList(foundListStart, studentNumber); if (newListHead != foundListStart) { //If we inserted at the beginning of a list thereby changing the list head string retrievedLastName = ""; long int retrievedListStart = -1; ReadByRRN(foundRRN, retrievedLastName, retrievedListStart); WriteByRRN(foundRRN, retrievedLastName, newListHead); } } } void SecondaryIndex::PrintIndex(ostream& str) { str << setw(20) << setiosflags(ios::left) << "Last Name" << '\t' << setw(10) << setiosflags(ios::left) << "List Start" << endl << setw(20) << setiosflags(ios::left) << "---------" << '\t' << setw(10) << setiosflags(ios::left) << "----------" << endl; string lastName = ""; long int listStart = -2; for (int i = 0 ; i < numberOfRecordsInIndex ; i++) { ReadByRRN(i, lastName, listStart); str << setw(20) << setiosflags(ios::left) << lastName << '\t' << setw(10) << setiosflags(ios::left) << listStart << endl; } str << "--END OF SEC INDEX--" << endl << endl; } long int SecondaryIndex::BinarySearch(string givenLastName) { //Convert to canonical form CapitalizeLastName(givenLastName); long int entryPoint = indexFileStream.tellg(); indexFileStream.clear(); long int low = 0; //Lower bound of the binary search long int high = numberOfRecordsInIndex - 1; //Higher bound of the binary search string retrievedLastName = ""; long int retrievedListStart = -1; SpaceFormatLastName(givenLastName); while (low <= high) { long int guess = (high + low) / 2; ReadByRRN(guess, retrievedLastName, retrievedListStart); if (retrievedLastName.compare(givenLastName) == 0) { indexFileStream.clear(); indexFileStream.seekg(entryPoint); return retrievedListStart; //Found } else if (retrievedLastName.compare(givenLastName) > 0) high = guess - 1; else //if (retrievedStudentNumberInt < givenStudentNumberInt) low = guess + 1; } indexFileStream.clear(); indexFileStream.seekg(entryPoint); return -1; //If we reach this point, then no matching record was found } void SecondaryIndex::CapitalizeLastName(string& lastName) { for (unsigned int i = 0 ; i < lastName.size() ; i++) { if (lastName[i] >= 97 && lastName[i] <= 122) //Capitalize only lower case letters lastName[i] = lastName[i] - 32; } }