Search vector of objects by object data member attribute - c++

I'm writing a Jukebox simulator and I'm trying to search a vector of Album objects by album title and return the index for use in other functions. The function is to be used for a number of different things such as deleting an album, printing an album etc.
I have gotten it to work in a previous application when the function was in the same Class as the data member to search for. I can however for some reason not get it to work using getters. No matter what I input as search key the idx returns 3 although the vector only contains indexes 0, 1 and 2 (only 3 albums right now).
The lambda function seem to be able to access data by using the getAlbum()-getter but somehow the comparison doesn't work.
My approach might be entirely wrong and I'd be grateful for any pointers in the right direction, or suggestions on how to accomplish the desired result using some other technique.
int Jukebox::findAlbumIdx(string key)
{
// Get search predicate
auto it = find_if(albvec.begin(), albvec.end(), [key](Album const &a)
{
return (a.getAlbum() == key);
});
int idx = it - albvec.begin();
return idx;
}
void Jukebox::delAlbum()
{
cin.get();
string key;
cout << "Input title of album to delete: ";
getline(cin, key);
int idx = findAlbumIdx(key);
if(idx > albvec.size() - 1)
cout << "No hits." << endl;
else
albvec.erase(albvec.begin() + idx);
}
getAlbum is just a simple inline getter as such:
string getAlbum() const {return album_title;}
Following Jonathan Wakely's suggestion to add std::cout << a.getAlbum() << " == " << key << std::endl; in the lambda the output is this:
Input title of album to delete: Abbey Road
== Abbey Road
== Abbey Road
== Abbey RoadLonely Hearts Club Band
No hits.
Obviously the getter isn't actually getting much to use for comparison. Not sure why it only gets the last entry and on the right hand side of the comparison.
If I add this to any of the functions above it gets and displays the Album titles correctly. The vector seems to be fine just before calling findAlbumIdx(key); and also inside the findAlbumIdx function.
for(unsigned int i = 0; i < albvec.size(); ++i)
cout << albvec[i].getAlbum() << endl;

The original playlist file that is read into the vector to be searched had dos newlines, after converting it using dos2unix (since I'm running Linux) the search, and I presume a lot of other things, is working correctly.
I suppose trimming newline characters while reading the file into the vector would be the more correct approach.

Related

Build different number of vectors/maps during runtime, to insert into BST - C++

The problem: So earlier the requirement for this C++ program was to just deal with one file's input (Each line represents the average of 10 minutes of the weather detail, about 50k lines). The end user wanted to be able to find out the average of the weather attributes for: a) A specified month and year, b)Average for each month of a specified year, c) Total for each month of a specified year, and d) average of each month of a specified year outputted to a .csv file.
Example: (First 4 lines of input csv)
WAST,DP,Dta,Dts,EV,QFE,QFF,QNH,RF,RH,S,SR,ST1,ST2,ST3,ST4,Sx,T
1/01/2010 9:00,8,151,23,0.1,1014.6,1018.1,1018.2,0,43.3,7,813,23.6,27,26.9,25.4,10,20.98
1/01/2010 9:10,8.7,161,28,0.1,1014.6,1018.1,1018.2,0,44.4,6,845,23.7,26.9,26.9,25.4,10,21.37
1/01/2010 9:20,8.9,176,21,0.2,1014.6,1018.1,1018.2,0,43.4,6,877,23.8,26.9,26.9,25.4,9,21.96
Solution: As not all the data from each line was required, upon reading in each line, they're parsed, segregated and built into an object instance of 'Weather', which consists of:
Date m_dateObj;
Time m_timeObj;
float m_windSpeed;
float m_solarRadiation;
float m_airTemperature;
A vector of Weather object was made to host this information.
Now, the problem has expanded to multiple files (150K-500K lines of data). Reading in multiple files is fine, all the data is retrieved and converted to Weather object with no problems, I'm just having trouble with the design(more specifically the syntax aspect of it, I know what I want to do). Additionally, there is a new option introduced where the user will enter dd/mm/yy and instances of highest solarRadiation for that day will be outputted(This requires me to have access to each specific object of weather and I cant just store aggregates).
BST and Maps are mandatory, so what I thought was: Data is read in line by line, for each line - Convert into Weather obj, store into a vector specifically for that month+year, so for every month of every year there will be a different vector eg; jan2007, feb2007, jan2008 etc. and each of these vectors are stored in a map:
map<pair<int, int>, vector<Weather> > monthMap;
So it looks like
<pair<3,2007>, march2007Vec>
and stores these maps into the BST (which I would need to randomize since its sorted data, to avoid making my BST a linked list, tips on how to do it? I found snippets for self-balancing trees that I might implement). This should work as the key for all maps are unique, thus making all BST nodes unique.
So it would look like this -
User runs program
Program opens files (there is a txt file with file names in it)
For each file
Open file
For each line
Convert into weather Object
Check month+year,
if map for combination exists,
add to that vector (eg march2007)
else
create new vector store in new map
Close file
add all maps to BST
BST will self sort
Provide user with menu to choose from
The actual computation of what the user needs is pretty simple, I just need help figuring out how to actually make it so there are n numbers of maps and vectors (n = number of maps = number of vectors, I think), as I don't know how many months/years there will be.
Heres a snippet of my code to get a better understanding of what I'm trying to do:
int main()
{
vector<Weather> monthVec;
map<pair<int, int>, vector<Weather> > monthMap;
map<pair<int, int>, vector<Weather> >::iterator itr;
int count = 0;
bool found = false;
Weather weatherObj;
ifstream weatherFileList;
weatherFileList.open("data/met_index.txt");
if(weatherFileList.is_open())
{
cout << "Success";
while (!weatherFileList.eof())
{
string data;
string fileName;
getline(weatherFileList, fileName);
cout << fileName << endl;
fileName = "data/" + fileName;
cout << fileName << endl;
ifstream weatherFile;
weatherFile.open(fileName.c_str());
getline(weatherFile, data);
while (!weatherFile.eof())
{
getline(weatherFile, data);
if (!data.empty())
{
weatherObj = ConvertData(data);
//cout << count << " " << weatherObj.GetTime().ToString() << endl;
//monthVec.push_back(weatherObj);
// for (itr = monthMap.begin(); itr != monthMap.end(); ++itr)
// {
//
// }
int month = weatherObj.GetDate().GetMonth();
int year = weatherObj.GetDate().GetYear();
itr = monthMap.find(make_pair(month,year));
if(itr != monthMap.end())
{
monthVec = itr->second;
monthVec.push_back(weatherObj);
}
else
{
}
count++;
}
//cout << data << endl;
}
weatherFile.close();
}
listOptions();
}
else
{
cout << "Not open";
}
cout << count << endl;
cout << monthVec.size() << "/" << monthVec.capacity();
return 0;
}
Apologies for the untidy code, so I was thinking about how to make it so for every new combination there's a new vector placed in a new map, but because of my inexperience, I don't know how to syntax it or even search it well.
TLDR: Need to map unknown number of combinations of ,VectorOfObject>
Would one make a switch case and have 12 vectors, one for each month hardcoded and just store all February (2007 2008 2009 etc) details in it, that would mean a lot of unnecessary processing.
How would one create different vectors without actually giving them a unique name for reference in the code, (<3,2007>,March2007)
How would one retrieve the contents of the vector(Of which we don't know the name, sure we know the key is 03 2007 aka march 2007, but wouldn't we need an explicit name to open the vector? march2007.find()), which is inside a map.
Thanks for the read, and potential help!
Please do Direct Message me if you'd like to see the problem in more detail, I would be grateful!

Why are my variables giving a different value when they are printed outside of the class I set them in?

I am making fairly new to C++ and I am using it to make a text based game for a school project. However during the first section of the game the player answers questions by entering the number shown beside the answer they choose. However when I tested the variables the input going to using std::cout they return different values depending on where they are outputted. If I outputted them in the class I am using to set them (Introduction) the they return the correct value such as 1 or 3 etc. However when I output them in any file other than Introduction.cpp, the value displayed is -858993460 for all of the values. I get the same result from Main.cpp when I call them in my main function and if I call them from another function in a different class to Introduction.
This is an example of some of the code used to get input from the user:
void Introduction::CharacterCreation()
{
Universal universal;
std::fstream creation("Introduction_CharacterCreation.txt");
universal.line = 5;
for (int i = 0; i < universal.line; i++)
{
if (i > 0)
{
std::getline(creation, universal.displayText);
std::cout << universal.displayText << std::endl;
}
if (i == 4)
{
std::cout << std::endl;
std::cin >> universal.gender;
while (universal.gender <= 0 || universal.gender >= 3)
{
std::cout << "Please make a valid choice" << std::endl;
std::cin >> universal.gender;
}
}
}
// Code cut out here
}
The gender variable is an int declared in the Universal class, and the user is prompted to enter 1 for male or 2 for female by text pulled from a separate file. If the input is not 1 or 2 then a while loop forces the player to keep re-answering the question until they enter 1 or 2. The line variable is also an int however that is used for the for loops to ensure the right lines are read by the program.
To output the gender variable this is the code I use:
std::cout << gender << std::endl;
There is no universal. as it is being called within the Universal class itself.
This has confused me massively and I can't get my head around what is causing the problem. Any help or explanation would be great, thanks in advance.
Short answer: you're declaring a Universal object in your CharacterCreation() method. When this function exits since the scope of the universal variable was local so the entire object is destroyed.
Whatever you are outputting is just uninitialized garbage. It could really be any number depending on what system is compiling / running the program. To test this right after you input the gender, while still inside the function, try running
std::cout << universal.gender << std::endl;
This should output just fine.
There are a lot of ways you can go about fixing this. Since you didn't post where you call this method or your Universal class I can't say for sure. But I can speculate one such solution which is to declare the Universal object outside the method and then pass it in as a parameter:
Universal universal = Universal();
Introduction::CharacterCreation(universal);
std::cout << universal.gender << std::endl;
And just declare your function header to accept a Universal object:
void Introduction::CharacterCreation(Universal & universal)
{
//code here
}

How do you remove a object from a list in C++?

This is where I'm having the problem. I need to remove a object from the array once the creature dies.
for each(BaseObject monster in MonsterList)
{
if(MonsterSelect == monster.GetName())
{
Damage = Player.GetATK() - monster.GetDEF();
monster.SetHP(monster.GetHP() - Damage);
cout << monster.GetName() << " has taken " << Damage << "." << endl;
if(monster.GetAliveFlag() == false)
{
cout << monster.GetName() << " has died." << endl;
MonsterList.remove(monster);//This is where the object needs to be removed.
int sdf = 234;
}
}
}
You should use an ordinary for loop with iterators and apply method erase for the list.
Something like this
for ( auto it = MonsterList.begin(); it != MonsterList.end(); )
{
//...
if( MonsterSelect == it->GetName())
{
//,,,
if( it->GetAliveFlag() == false)
{
it = MonsterList.erase( it );
//...
}
else
{
++it;
}
}
else
{
++it;
}
}
You can reformat the loop that it would be more readable.
I suppose that each time when the current monster satisfies some condition you have to remove only this one monster in the list.
As for method remove then it removes all elements of the list that are equal to the given monster.
I'm going to interpret this as asking how you'd accomplish this basic task in decently written C++.
First, I'd get rid of the for loop. Second, I'd move most of the logic into one class or the other. Right now, these look like quasi-classes to me--i.e., all the real logic is in separate code operating on a couple of what are basically dumb data objects, disguised as classes by using accessors to get at the raw data.
So, if you were doing this in actual C++, you might end up with a map (or multimap) to let you find monsters by name. So, you'd have something like this:
auto monster = monsters.find(monsterSelect);
if (monster == monsters.end())
// Not found
if (monster.kill(player.attack())) {
cout << monsterSelect << " has died.\n";
monsters.erase(monster);
}
For the moment, I'm assuming there's really only a single monster by any one name. If this might really be an attack against multiple monsters, you have a couple of choices.
The preferred one (at least IMO) is to decouple the "battle the monster" part from the "remove dead monster(s) from the list" part. I'd probably use a Boost filter-iterator to iterate across all the monsters by a given name, then the remove/erase idiom to remove all the dead monsters from the list. (Note, however, that the remove/erase idiom is specific to sequential collections, and not suitable for associative containers).

C++ After Function Call and Function Completion, Game Crashes Entirely

I've been having an issue with a game I've been making in my C++ game programming class for school. For some reason, after calling a function which I'm using to manage the inventory based stuff, the function seems to complete and work (I think this because I put in cout commands at the end of it and they printed correctly, also the function runs twice in a row, and they both run), my entire game crashes and doesn't reach the next line. I tried commenting out all the code in the function and it still crashed. I commented out the function calls and it worked, but I still can't tell what is wrong with it. I'll put the code for the function and the section were I make the calls:
string inventoryFunction(int h, string ab)
{
if(h == 1)
inventory.push_back(ab);
else
if(h == 2)
{
for(int i=0; i < inventory.size(); i++)
{
if(inventory[i] == ab)
inventory[i].erase();
}
}
else
if(h == 3)
{
cout << inventory[0];
for(int i=1; i < inventory.size(); i++)
cout << ", " << inventory[i];
}
}
The function call:
if(answer.find("village") != string::npos)
{
cout << endl;
cout << "While looking around your village,\nyou found a stone sword and a cracked wooden shield!" << endl;
inventoryFunction(1, "stone sword");
inventoryFunction(1, "cracked wooden shield");
cout << "Would you like to set off on your adventure now?" << endl;
cin >> answer2;
capitalizeLower(answer2);
Not sure there's anything there likely to cause a crash, my advice would be to single-step your code in the debugger to see where it's falling over. It's quite possible the bug is somewhere totally different and it's just being exacerbated by the function calls modifying the vector.
That's the nature of bugs unfortunately, you can never really tell where they're actually coming from without looking closely :-)
However, there are a couple of issues with the code that I'd like to point out.
First, with regard to:
inventory[i].erase();
That doesn't do what you think it does. inventory[i] is the string inside your vector so it's simply erasing the string contents.
If you want to remove the string from the vector, you need something like:
inventory.erase (inventory.begin() + i);
Second, I'd tend to have three separate functions for addToInventory, removeFromInventory and listInventory.
It seems a little ... unintuitive ... to have to remember the magic values for h to achieve what you want to do, and there's no real commonality in the three use cases other than access to the inventory vector (and that's not really reason enough to combine them into the same member function).
On top of that, your function appears to be returning a string but you have no actual return statements and, in fact, none of the three use cases of your function require anything to be passed back.
The signature is better off as:
void inventoryFunction(int h, string ab)
In terms of the second and third points above, I'd probably start with something like:
void addToInventory (string item) {
inventory.push_back(ab);
}
void removeFromInventory (string item) {
for (int i = 0; i < inventory.size(); i++) {
if (inventory[i] == ab) {
inventory.erase (inventory.begin() + i);
break;
}
}
void listInventory () {
cout << inventory[0];
for (int i = 1; i < inventory.size(); i++)
cout << ", " << inventory[i];
}
You may also want to look into using iterators exclusively for the second and third functions rather than manually iterating over the collection with i.
It'll save you some code and be more "C++ic", a C++ version of the "Pythonic" concept, a meme that I hope will catch on and make me famous :-)
So by changing the inventoryFunction to a void function like #Retired Ninja said, the crash has stopped occurring and now the program is working great.
Also, #paxdiablo pointed out that I was using the inventory[i].erase() thing incorrectly, so thanks a bunch to him, because now I won't have to come back on here later to try to fix that :D
string inventoryFunction(int h, string ab)
should return a string but does not have any return statements. Of course it works, after you change it to a void function, which correctly does not return anything. Interesting is, that you are able co compile this code without an error - normally a compiler would show you this problem.

Rearrange list the same way as another one

I bumped into a page where there were a lot of categories and next to each one the number of items in each category, wrapped in parenthesis. Something really common. It looked like this:
Category 1 (2496)
Category 2 (34534)
Category 3 (1039)
Category 4 (9)
...
So I was curious and I wanted to see which categories had more items and such, and since all categories were all together in the page I could just select them all and copy them in a text file, making things really easy.
I made a little program that reads all the numbers, store them in a list and sort them. In order to know what category the number it belonged to I would just Ctrl + F the number in the browser.
But I thought it would be nice to have the name of the category next to the number in my text file, and I managed to parse them in another file. However, they are not ordered, obviously.
This is what I could do so far:
bool is_number(const string& s) {
return !s.empty() && find_if(s.begin(), s.end(), [](char c) { return !isdigit(c); }) == s.end();
}
int main() {
ifstream file;
ofstream file_os, file_t_os;
string word, text; // word is the item count and text the category name
list<int> words_list; // list of item counts
list<string> text_list; // list of category names
file.open("a.txt");
file_os.open("a_os.txt");
file_t_os.open("a_t_os.txt");
while (file >> word) {
if (word.front() == '(' && word.back() == ')') { // check if it's being read something wrapped in parenthesis
string old_word = word;
word.erase(word.begin());
word.erase(word.end()-1);
if (is_number(word)) { // check if it's a number (item count)
words_list.push_back(atoi(word.c_str()));
text.pop_back(); // get rid of an extra space in the category name
text_list.push_back(text);
text.clear();
} else { // it's part of the category name
text.append(old_word);
text.append(" ");
}
} else {
text.append(word);
text.append(" ");
}
}
words_list.sort();
for (list<string>::iterator it = text_list.begin(); it != text_list.end(); ++it) {
file_t_os << *it << endl;
}
for (list<int>::iterator it = words_list.begin(); it != words_list.end(); ++it) {
file_os << fixed << *it << endl;
}
cout << text_list.size() << endl << words_list.size() << endl; // I'm getting the same count
}
Now I forget about having the name next to the number, because something more interesting occured to me. I thought it would be interesting to find a way to rearrange the strings in the text_list which contain the names of the categories in the exact same way the list with the item count was sorted.
Let me explain with an example, lets say we have the following categories:
A (5)
B (3)
C (10)
D (6)
The way I'm doing it I will have a list<int> containing this: {10, 6, 5, 3} and a list<string> containing this: {A, B, C, D}.
What I'm saying is I want to find a way I can keep track of the way the elements were rearranged in the first list and apply that very pattern to the second list. What would be the rearrange pattern? It would be: the first item (5) goes to the third position, the second one (3) to the fourth one, the third one (10) to the first one, and so on.... Then this pattern should be applied to the other list, so that it would end up like this: {C, D, A, B}.
The thing would be to keep track of the Pattern and apply it to the list below.
Is there any way I can do this? Any particular function that could help me? Any way to track all the swaps and switches the sort algorithm does so it can be applied to a different list with the same size? What about a different sorting algorithm?
I know this might be highly inefficient and a bad idea, but it seemed like a little challenge.
I also know I could just pair both string and int, category and item count, in some sort of container like pair or map or make a container class of my own and sort the items based on the item count (I guess map would be the best choice, what do you think?), but this is not what I am asking.
The best way to do this would be to create a list that contains both sets of information you want to sort and feed in a custom sorting function.
For instance:
struct Record {
string name;
int count;
};
list<Record> myList;
sort(myList, [](Record a, Record b){
return a.count < b.count;
});
In the general case, it's always better to manage one list of a complex datatype, than to try to separately manage two or more lists of simple datatypes, especially when they're mutable.
Some more improve way:
First some notes:
It's recommended to storage category name and items together, for clarity, easy of read code, etc...
It's better use std::vector instead of std::list (see Bjarne Stroustrup opinion)
The code load the file with the format specified in your question, storage in the vector the info pair.
Use std::sort function to sort only by items number (the categories with the same items would be in any order, if you would like to sort for category name the categories with the same items change the lambda body to return std::tie(left.items, left.name) > std::tie(right.items, right.name);.
Added a version with info split, in one collection items and index (to correlate items with name) info, and in the other names info.
Code:
#include <iostream>
#include <fstream>
#include <algorithm>
#include <vector>
bool is_number(const std::string& s) {
return !s.empty() &&
find_if(s.begin(), s.end(), [](char c) { return !isdigit(c); }) ==
s.end();
}
struct category_info {
std::string name;
int items;
};
struct category_items_info {
int items;
size_t index;
};
int main() {
std::ifstream file("H:\\save.txt");
std::vector<category_info> categories;
std::vector<category_items_info> categories_items;
std::vector<std::string> categories_names;
std::string word;
std::string text;
while (file >> word) {
if (word.front() == '(' && word.back() == ')') {
std::string inner_word = word.substr(1, word.size() - 2);
if (is_number(inner_word)) {
std::string name = text.substr(0, text.size() - 1);
int items = atoi(inner_word.c_str());
categories.push_back(category_info{name, items});
categories_names.push_back(name);
categories_items.push_back(
category_items_info{items, categories_items.size()});
text.clear();
} else { // it's part of the category name
text.append(word);
text.append(" ");
}
} else {
text.append(word);
text.append(" ");
}
}
std::sort(categories.begin(), categories.end(),
[](const category_info& left, const category_info& right) {
return left.items > right.items;
});
std::sort(
categories_items.begin(), categories_items.end(),
[](const category_items_info& left, const category_items_info& right) {
return left.items > right.items;
});
std::cout << "Using the same storage." << std::endl;
for (auto c : categories) {
std::cout << c.name << " (" << c.items << ")" << std::endl;
}
std::cout << std::endl;
std::cout << "Using separated storage." << std::endl;
for (auto c : categories_items) {
std::cout << categories_names[c.index] << " (" << c.items << ")"
<< std::endl;
}
}
Output obtained:
Using the same storage.
Category 2 (34534)
Category 1 (2496)
Category 3 (1039)
Category 4 (9)
Using separated storage.
Category 2 (34534)
Category 1 (2496)
Category 3 (1039)
Category 4 (9)
Lists do not support random access iterators, so this is going to be a problem, since a list can't be permuted based on a vector (or array) of indices without doing a lot of list traversal back and forth to emulate random access iteration. NetVipeC's solution was to use vectors instead of lists to get around this problem. If using vectors, then you could generate a vector (or array) of indices to the vector to be sorted, then sort the vector indices using a custom compare operator. You could then copy the vectors according to the vector of sorted indices. It's also possible to reorder a vector in place according to the indices, but that algorithm also sorts the vector of indices, so you're stuck making a copy of the sorted indices (to sort the second vector), or copying each vector in sorted index order.
If you really want to use lists, you could implement your own std::list::sort, that would perform the same operations on both lists. The Microsoft version of std::list::sort uses an array of lists where the number of nodes in array[i] = 2^i, and it merges nodes one at a time into the array, then when all nodes are processed, it merges the lists in the array to produce a sorted list. You'd need two arrays, one for each list to be sorted. I can post example C code for this type of list sort if you want.