Trying to keep age/name pairs matched after sorting - c++

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,

Related

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).

C++ Access member of struct/class using map

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.

Separate chaining with priority queue (using std::map)

I just start learning hash table and while trying with std::map I come up with this question: when using separate chaining method to solve collision, can I use std:: priority_queue instead of just list?
For example there is a big group of people and I have the information of their first names and ages, and what I want to get is sorted lists of people with same first name e.g. 'David' base on their ages.
So to do this I first use their first name as the key to put these people into the map, and then people with same name that cause the collision should be solved with std::priority_queue base on age.
Is this the right way to solve this problem?
And I just realize that I don't really know the mystery behind std::map, is it using separate chaining or linear probing to solve collision? I couldn't find the answer for that.
Simple code I have for the question I described that might help clarify it a bit:
class people {
public:
people(string inName, int inAge):firstName(inName), age(inAge){};
private:
string firstName;
int age;
}
int main(int argc, char ** argv) {
string name;
int age;
name = "David";
age = 25;
people aPerson(name, age);
//This is just an example, there are usually more than two attributes to deal with.
std::map <string, people> peopleList;
peopleList[name] = aPerson;
//now how do I implement the priority queue for collision first names?
}
Thanks in advance!
EDIT: since I need O(1) search, I should use unordered map instead of map.
Right now you have a mapping between a name and a single people object. You need to change your mapping to be a map between a name and a std::priority_queue, with a custom comparator for the priority queue:
auto comparator = [](const people& p1, const people& p2) -> bool
{ return (p1.age < p2.age); }
std::map<std::string,
std::priority_queue<people, std::vector<people>, comparator>> peopleList;
// ...
peopleList[name].push(aPerson);

Vector of Pointers to Same Class Type Design Feasibility

If I wanted a class that had a vector of pointers to other classes of the same type that could allow a cyclical cycle, how dangerous is it? For example, say I have a text file that looks like this:
city=Detroit
{
sister=Toyota
sister=Dubai
...
}
...
First the file is read into a series of temp classes, ParsedCity, where the name of the city and the names to the sister cities are held. After I have all the cities in the file I create the actual City class.
class City
{
private:
std::string name;
std::vector<City*> sisterCities;
public:
City(const std::string& aName);
CreateRelations(const ParsedCity& pcs);
std::string Name() const { return name; }
};
//If this were to represent Detroit, pc would contain a vector of strings
//containing Toyota and Dubai. Cities contain the actual classes that sister
//cities should point to. It holds all cities of the world.
City::CreateRelations(const ParsedCity& pc, std::vector<City>& cities)
{
for (unsigned int i = 0; i < pc.ParsedSisterCities().size(); i++)
{
for (unsigned int j = 0; j < cities.size(); j++)
{
if (pc.ParsedSisterCities()[i] == cities[j].Name())
{
sisterCities.push_back(&cities[j]);
break;
}
}
}
}
My worry is that if more cities are pushed into the main City vector, that the vector will re-size, relocate to somewhere else, and all my Cities will be pointing to sisterCities that are dangling pointers. At least this is my thought that will happen based on my knowledge of the vector class. If all the cities in the world and sister cities were stored in a linked-list would this solve my problems? I would like a guarantee that once a city is built it does not move (in memory. Bad pun?)
This seems like a tricky problem to me. As if I call a sister city of Detroit, I can call it's sister cities, etc. Then I could end up back at Detroit! If Topeka changes its name to Google, all of Topeka's sister cities should automatically know (as they are all pointing at the same spot in memory that Topeka is located).
Any advice is appreciated!
If you have a vector of pointers, and the vector resizes, the pointees locations in memory are not affected, so all your pointers remain valid.
The biggest problem with this solution is that any recursive algorithm you apply to your data-structure will have to have some mechanism to detect cycles, otherwise you will end up with stack-overflows due to infinite recursion.
Edit:
I just realized that I misread your question originally. If the cities-vector resizes, any pointer to its elements will become invalid. The best alternative would be store pointers to cities in that vector. To make this more manageable, I suggest you use a boost::ptr_vector. This has the benefit that pointers to cities remain valid even if you delete a city from your vector, or you reorder the cities in your vector (for instance if you want to sort them by name for faster lookup).
This seems like a tricky problem to me. As if I call a sister city of
Detroit, I can call it's sister cities, etc. Then I could end up back
at Detroit!
That's a "circular reference". This is not necessarily a problem in C++, since objects are deleted manually. However, it may introduce complications in your design, as you percieve.

Order like a list but access by a key?

I used list to place cities into a trip. Then I iterate over
the list to display the trip itinerary. I would like to access
the cities by the name rather than by the trip order. So, I
thought I could use a map rather than a list but the key determines
the order. I would still like to control the order of the sequence
but be able to access the entries by a key.
Can these features be combined? Is there some standard way to address
this?
#include <list>
#include <iostream>
struct City{
City(std::string a_n, int a_d):name(a_n), duration(a_d){}
std::string name;
int duration;
};
int main(){
std::list<City*> trip;
trip.push_back(new City("NY", 5));
trip.push_back(new City("LA", 2));
for (std::list<City*>::iterator ii=trip.begin(); ii!=trip.end(); ++ii)
std::cout << (*ii)->name << " for " << (*ii)->duration << " days." <<std::endl;
}
Often times you will need to compose multiple lists and maps. The common way is to store a pointer to the Cities in your by city lookup map from the pointers in your list. Or you can use a class like Boost.MultiIndex to do what you want in what I would say is much cleaner. It also scales much better and there is a lot less boiler plate code if you want to add new indexes. It is also usually more space and time efficient
typedef multi_index_container<
City,
indexed_by<
sequenced<>, //gives you a list like interface
ordered_unique<City, std::string, &City::name> //gives you a lookup by name like map
>
> city_set;
Create a map<string,int> m;, where the values are indexes to a vector<City>, for example m["NY"] == 0 and m["LA"] == 1.
Use two collections:
A list to store the actual objects in the order you are interested in.
A map to map names to the objects.
The best solution is to use Boost.MultiIndex, though that's slightly more involved. Unfortunately, I don't have time now to provide sample code; sorry.