C++ Access member of struct/class using map - c++

I need to search a database made up of a linked list of pointers to a struct called Person. Inside Person there is a bunch of data - first name, last name, social security, etc. It's all fictional and inconsequential. My problem is that I need to do a search based on user input, which determines what part of the struct is being compared for the search. Since all of the data is stored as members of the struct Person, I think the best way to do this (as in not writing 8 search functions) is by mapping, but my grasp of mapping is so poor as to be nearly non-existent. Here is the relevant code:
List * find(List * database, //mapping stuff, string name)
{
//run search
return database;
}
void search(List * database)
{
string field, searchtype, userinput;
cout << "To search for a person, enter information in this format: 'field equal
value' or 'field begins value'. Type 'clear' to
return to original database. Type 'exit' to leave the program\n";
while(field != "exit")
{
cin >> field >> searchtype >> userinput;
if(userinput == "firstname") //this is just for example, I would have to write one of these out for each parameter.
{
List * smallerdb = find(database, map(//mapping stuff?), string userinput);
}
}
}
This is for school, so please don't suggest I just use another library, as I can't. Thank you!

Each data-type will have to be compared differently, so without explicitly knowing the data-types you are out of luck. Strings are compared alphabetically, numbers are compared by value, and i'm not sure if you have any other custom data-types.
So you don't really have much of an option here besides going through each of the case-by-case scenarios. E.g.
if type == 'name' // compare each Person.name as string
else if type == 'socialSecurity' // compare Person.socialSecurity as int
else if ...
Here is a suggestion for making this code a bit more robust.
Instead of handling the search logic in each of the cases, you can simply return a function pointer than tells you how to compare two Persons. The actual function may be comparing by name, age, social security, or whatever, but your search function won't have to care about that once it has the function pointer that tells it how to compare two Person objects.

Related

Getting a "no viable overloaded operator[] error when trying to return an object from a vector

I am working on making a small text-based game, and am currently stuck trying to build a function that passes a string, which is the name of a weapon, and returns that weapon object from a vector. Here are the relevant functions:
//define weapons like this
Weapon* flimsyDagger = Weapon* (3, "Flimsy Dagger", 17, 4.0);
//store weapons in this vector, which stores all of the stats of each weapon
void Weapon::fillWeaponVector() {
allWeapons.push_back(flimsyDagger);
}
//use this function to return the weapon by passing its name as it was defined (e.g. 'flimsyDagger') and return all stats
Weapon* Weapon::getWeaponStats(std::string weaponName) {
return allWeapons[weaponName];
}
The error is occurring at the first square brace in the line return allWeapons[weaponName];. I have looked everywhere for a solution and haven't found anything that quite fits my situation. Any suggestions?
You claim that you're using a vector, but nothing in the code you've posted indicates that allWeapons is in fact a vector.
If it is, you can't index a vector with a text string - indices for vectors must be integers. As #Eljay suggests, using a std::map may be more appropriate. This allows you to look up the objects you store using a non-numeric key.

Add multiple parameters to IN clause in SQLAPI c++

I am using SQLAPI to connect to a SQL-Server database from a C++ code. I need to execute a simple select statement with a IN clause, where the string values in the clause is received as a vector of strings. We want to use parameterised queries so did something like this:
std::string getSQLQuery(std::vector<std::string> ids){
std::stringstream sql;
sql << "SELECT ID, Name, DOB FROM Employees WHERE ID IN (";
unsigned int counter = 0;
for each (auto id in ids)
{
sql << ":" << counter + 1;
if (++counter < ids.size())
{
sql << ",";
}
}
sql << ")";
return sql.str();
}
And then,
int param_counter = 0;
for each (auto id in ids) {
command.Param(++param_counter).setAsString() = id.c_str();
}
Can anyone please suggest a better way of doing it?
Well, I don't want to undercut your basic question, which I take to be "What's a good way to do this?", but you have some basic syntax problems with your C++. Inferring your intention from the buggy code above, I gather that the goal is to create a command synthesizer with a common SQL select query from an arbitrary set of input id's. Cool. I don't think there's any up-side to first creating a synthesized template command with insertion points and then using their parameter replacement scheme. Might as well do the synthesis all at once. A compile-time-correct version would look something like this (with a few tweaks to make it more reusable) -
std::string getSQLQuery(
const std::string& columns
, const std::string& table
, const std::vector<std::string>& ids
){
// Check for empty strings/arrays here - leads to illegal SQL,
// so error out or except on empty ids, columns, or "table".
std::stringstream sql("SELECT ", std::ios_base::out | std::ios_base::ate);
sql << columns << " FROM " << table << " WHERE ID IN ( ";
// Trailing commas are not allowed in SQL, which makes synthesis a little trickier.
// We checked for empty inputs earlier, so we have at least one ID.
auto iter = ids.begin();
sql << *iter++; // add the first (and possibly only) ID
for (; iter != ids.end(); ++iter) { // add the rest (if any) with commas
sql << ", " << *iter;
}
sql << " )"; // <- should this be " );"? Or does SQLAPI++ deal with that?
return sql.str(); // There's a shrink-to-fit method you may want to use here.
}
Now you can just do something like -
std::vector<std::string> id_array{ "1", "50", "aardvark" };
SACommand basic_command(connection, getSQLQuery("ID, Name, DOB", "Employees", id_array));
basic_command.Execute();
This skips the second substitution phase entirely. The SQLAPI++ parameter substitution is intended for queries with a much more rigid template, but you're doing something more dynamic. You can imagine extending this further with input arrays of columns as well, to avoid syntax errors in the table list (like we do in the id list). Also, since id's are often numerical, you could make the id array std::vector<std::uint64_t> or whatever fits your specific application. In fact, you can handle both cases with the same body of code by making the signature -
template<typename T> std::string getSQLQuery(
const std::string& columns
, const std::string& table
, const std::vector<T>& ids
){
... // rest of the implementation is the same
}
I'm a new contributor, but a long-time user, so just a word about questions. When you ask a question like, "Can anyone please suggest a better way of doing it?", the answer is always "Yes.". There are a lot of smart people out there and an infinity of solutions to every high-level problem. In the future, you want to state the problem you're trying to solve (not hard to figure out in this case), and if you show a solution that was tried and failed, you should give specifics about how it failed. In the case of the code you put forth, the most obvious reason it failed is that it is syntactically wrong - the compiler would not accept it. "for each" is from some other languages. In C++, it's something like "for (auto id : ids)". But if you were just trying to show some kind of pseudo-code, it suggests that you don't really know if your existing approach works, because it hasn't been tried. And even in that case, you should say what you don't like about the presented solution (like the unnecessary second step of using the SQLAPI++ substitution scheme), and ask specifically if someone can think of a way to remove that. I'm a talker, and I would have given the same response, but for future reference, try to avoid a question that comes down to, "The following code is broken. Someone fix it for me." Just FWIW.

How to search by member accessor value with std::find_if()?

I am learning C++ at the moment and have an example program implemented with an array of objects data store. To make some other operations easier, I have changed the store to a vector. With this change I am now not sure of the best way to search the store to find an object based on a member accessor value.
Initially I used a simple loop:
vector<Composer> composers; // where Composer has a member function get_last_name() that returns a string
Composer& Database::get_composer(string last_name)
{
for (Composer& c : composers)
if (c.get_last_name().compare(last_name))
return c;
throw std::out_of_range("Composer not found");
}
This works just fine of course, but to experiment I wanted to see if there were vector specific functions that could also do the job. So far I have settled on trying to use find_if() (if there is a better function, please suggest).
However, I am not sure exactly the correct way to use find_if(). Based on code seen in online research I have replaced the above with the following:
vector<Composer> composers; // where Composer has a member function get_last_name() that returns a string
Composer& Database::get_composer(string last_name)
{
auto found = find_if(composers.begin(), composers.end(),
[last_name](Composer& c) -> bool {c.get_last_name().compare(last_name);});
if (found == composers.end())
throw out_of_range("Composer not found");
else
return *found;
}
This does not work. It does find a result, but it is the incorrect one. If an argument that matches, say the third composer's last name the function always returns the first item from the vector (if I pass an argument that doesn't match any last name the function correctly throws an exception)... what am I doing wrong?
You are on the right track, your lambda needs return statement. Also in such case you do not have to specify it's return type explicitly, it can be deduced:
find_if(composers.begin(), composers.end(),
[last_name](const Composer& c) { return c.get_last_name() == last_name);});
you original code should not compile or at least emit warning(s), you should pay attention to them.
Note: it is not clear how your original code worked if you tested it, it should be:
if (c.get_last_name().compare(last_name) == 0 )
or simply:
if (c.get_last_name() == last_name )
as std::string::compare() returns int -1 0 or 1, so your code searches for string that does not match variable last_name
With range-v3, you may use projection:
auto it = ranges::find(composers, last_name, &composers::get_last_name);

C++: Sort list alphabetically, display only first and last items

Update: Final versions, with <set> and without <set>.
I've been working on a problem:
Design a program that asks the user for a series of names (in no
particular order). After the final person’s name has been entered, the
program should display the name that is first alphabetically and the
name that is last alphabetically.
For example, if the user enters the names Kristin, Joel, Adam, Beth,
Zeb, and Chris, the program would display Adam and Zeb.
Here's the code I have so far:
#include <iostream>
#include <set>
#include <algorithm>
using namespace std;
void displayOutput(const string& item)
{
cout << item << endl;
}
int main()
{
set<string> sortNames;
string name;
do {
cout << "Enter a name (\"end\" to finish):\t";
cin >> name;
sortNames.insert(name);
} while ( name != "end" );
for_each(sortNames.begin(), sortNames.end(), &displayOutput);
return 0;
}
My code so far works fine, in that it displays all inputted strings alphabetically. (On a side note, I'm not sure how to get around having "end" not display in the output itself.) The problem is, the program is only supposed to display the (alphabetically) first and last names from the list. I've been thinking about it, and I think that I might have to approach the problem from a different angle, but I'm not sure where to start. Any help would be greatly appreciated.
for_each(sortNames.begin(), sortNames.end(), &displayOutput); goes through the entire set, and for each item, calls its displayOutput member function.
You apparently only want to call the displayOutput on the first item, sortNames.begin() and the last, sortNames.rbegin().
Sorry, but since this is apparently homework, I'm not going to give a more explicit answer than that.
As for avoiding end being shown (and being part of the collection), you have a basic problem with logic. Right now, you read a name, add it to the collection, then check if it's end, and if so quit adding more names. What you probably want to do is read a name, check if it's end, and only add it to the collection if it's not (then break out of the loop if it is).
I'd probably rewrite the code something like this:
std::string getname() {
string name;
cout << "Enter a name (\"end\" to finish):\t";
cin >> name;
return name;
}
int main() {
std::set<std::string> sortNames;
std::string name;
while ((name=getname()) != "end")
sortNames.insert(name);
// ...
}
I'd also note that there's really no need to store all the names in a set. You could just store two strings, one that's that's the first alphabetically among those seen so far, and one that's the last. When you read each string, check whether it's less than the first (and if so, save it as the first). Otherwise, check if it's greater than the last (and if so, save it as the last).

Trying to keep age/name pairs matched after sorting

I'm writing a program where the user inputs names and then ages. The program then sorts the list alphabetically and outputs the pairs. However, I'm not sure how to keep the ages matched up with the names after sorting them alphabetically. All I've got so far is...
Edit: Changed the code to this -
#include "std_lib_facilities.h"
struct People{
string name;
int age;
};
int main()
{
vector<People>nameage;
cout << "Enter name then age until done. Press enter, 0, enter to continue.:\n";
People name;
People age;
while(name != "0"){
cin >> name;
nameage.push_back(name);
cin >> age;
nameage.push_back(age);}
vector<People>::iterator i = (nameage.end()-1);
nameage.erase(i);
}
I get compiler errors for the != operator and the cin operators. Not sure what to do.
Rather than two vectors (one for names, and one for ages), have a vector of a new type that contains both:
struct Person
{
string name;
double age;
};
vector<Person> people;
edit for comments:
Keep in mind what you're now pushing onto the vector. You must push something of type Person. You can do this in a couple of ways:
Push back a default constructed person and then set the name and age fields:
people.push_back(Person());
people.back().name = name;
people.back().age = age;
Give Person a constructor that takes a name and an age, and push a Person with some values:
struct Person
{
Person(const string& name_, double age_) : name(name_), age(age_) {}
string name;
double age;
};
people.push_back(Person(name, age));
Create a Person, give it some values, and push that into the vector:
Person person;
person.name = name;
person.age = age;
people.push_back(person);
Or more simply:
Person person = { name, age };
people.push_back(person);
(thanks avakar)
In addition to the solution posted by jeje and luke, you can also insert the pairs into a map (or multimap, in case duplicate names are allowed).
assert(names.size() == ages.size());
map<string, double> people;
for (size_t i = 0; i < names.size(); ++i)
people[names[i]] = ages[i];
// The sequence [people.begin(), people.end()) is now sorted
Note that using vector<person> will be faster if you fill it up only once in advance. map will be faster if you decide to add/remove people dynamically.
You should consider putting names and ages together in structured record.
Then sort the records.
J.
You could have a vector of structs/classes, where each one has both a name and an age. When sorting, use a custom comparator that only looks at the name field.
Alternately, build an additional vector of integers [0,names.size()-1]. Sort that, with a custom comparator that instead of comparing a < b compares names[a] < names[b]. After sorting, the integer vector will give you the permutation that you can apply to both the names and ages vectors.
You either need to swap elements in both vectors at the same time (the FORTRAN way), or store a vector of structs or pairs. The later approach is more idiomatic for c-like languages.
You should use the pair<> utility template. Reference here.
G'day,
Given how you're trying to model this, my gut feeling is that you haven't approached the problem from an OO perspective. Try using a class instead of a struct.
struct's are soooo K&R! (-:
Think of a Person as an object and they have attributes that are tightly coupled, e.g. Name and Age. Maybe even address, email, Twitter, weight, height, etc.
Then add to your objects the functions that are meaningful, e.g. comparing ages, weights, etc. Writing a < operator for email addresses or Twitter id's is a bit bizarre though.
OOA is just looking at what attributes your "objects" have in real life and that gives you a good starting point for designing your objects.
To get a better idea of OOA have a look at the excellent book "Object Oriented Systems Analysis: Modeling the World in Data" by Sally Shlaer and Stephen Mellor (sanitised Amazon link). Don't faint at the Amazon price though $83.33 indeed! At least it's $0.01 second hand... (-:
HTH
cheers,