Simple pivot table display in c++? (not full pivot table feature) - c++

I am writing a program making the inventory of the computers of a school.
I have a class Equip with 2 string as variable members m_owner and m_type.
I have a vector<Equip> v filled with a bunch of objects Equip.
I am using a csv file as argument to my program to fill the vector.
Say v contains 5 objects :
Equip e1("LaboA", "laptop");
Equip e2("LobaA", "server");
Equip e3("HR", "printer");
Equip e4("LobaA", "laptop");
Equip e5("LobaC", "router");
I am trying trying to display the content of v in the form of a pivot table like on the following screenshot :
I have tried the following :
void matrix(const vector<Equip>& v)
{
vector<Equip> tmp = v;
sort(tmp.begin(), tmp.end());
vector<Equip>::iterator it = unique(tmp.begin(), tmp.end(), SameOwner());
tmp.resize(std::distance(tmp.begin(), it));
for(auto i = tmp.begin(); i != tmp.end(); ++i)
{
int j = count_if(v.begin(), v.end(), IsPrinter());
cout << i->getOwner() << " : " << j << endl;
}
}
I have all the methods written so the code compiles and executes but it does not do what I want.
I understand why but I don't understand how to get this right.
Can you guys give me hints on how to achieve that please? I am at a loss here.
Thank you,
J.

Related

When iterating through a list, how do I keep track of a list element to be deleted later on?

In this example, I build a linked list of 4 Person objects, each of which has an age. I'd like to iterate through the list of Persons, keep track of the youngest person, and then remove that person from the list.
Here's what I have so far
#include <iostream>
#include <vector>
#include <list>
struct Person
{
Person(int x): age(x) {}
int age;
};
int main() {
// Create 4 Person instances
Person p1 = Person(50);
Person p2 = Person(35);
Person p3 = Person(99);
Person p4 = Person(17);
// Build a list of persons
std::list<Person> persons = {p1, p2, p3, p4};
// Delete the list-element of the youngest person
int minAge = 999; // track the lowest age so far
Person* youngin = nullptr; // track the person with the lowest age so far
// Iterate through each person in the list, looking for someone with a lower age than all previous
for(auto const &p : persons){
if(p.age < minAge){
std::cout << "Found someone younger. Age: " << p.age << std::endl;
// Update minAge and youngin
minAge = p.age;
// youngin = ???;
}
}
// Delete the youngest person from the list
// persons.erase(youngin);
return 0;
}
How do I 1) retain a pointer (I think?) to the youngest person "so far" and 2) remove that element from the list at the end?
UPDATE
It seems I've oversimplified my minimal reproducible example. #NathanOliver appears to have the best solution for what I asked, so I'll choose his as the accepted but if anyone is willing to help me further,
Suppose that there is a 5th person not in the list,
Person p5 = Person(30);
Now, how do I identify which of the 4 people in the list is closest in age to p5, and then remove that person from the list? (In this case p2 should be identified and removed.) I don't see how to apply Nathan's solution using std::min_element to this situation.
Cheers
You can use the standard algorithms and a lambda to have this done for you. std::min_element can be used to find the minimum element and the iterator it returns can be passed to the lists erase member function to have it removed from the list. That would look like
persons.erase(std::min_element(persons.begin(),
persons.end(),
[](auto const& lhs, auto const& rhs){ return lhs.age < rhs.age; }));
If you need to use the minimum before you dispose of it yo can split up the call like
auto min_it = std::min_element(persons.begin(),
persons.end(),
[](auto const& lhs, auto const& rhs){ return lhs.age < rhs.age; }));
//use min_it here
person.erase(min_it); // get rid of the minimum
Update:
You can still use min_element to find the element, we just need to find the minimum difference instead of the minimum element. We just need to change the lambda to to compare the differences of the ages and the value to find instead of the just the ages.
Person p5 = Person(30);
persons.erase(std::min_element(persons.begin(),
persons.end(),
[&](auto const& lhs, auto const& rhs){
return std::abs(lhs.age - p5.age) < std::abs(rhs.age - p5.age);
}));
I used [&] in the lambda so it capture p5 by reference so we can compute the difference. You can see it working in this live example.
The type of p is a std::list<Person>::iterator (As it needs to be a "node" class, in most cases storing a pointer to the next node and the value of the node).
std::list::erase takes an iterator, whereas that for loop dereferences the iterator, so you don't get the iterator.
So, you need to use std::list::begin and std::list::end to get iterators.
typename std::list<Person>::iterator youngin; // track the person with the lowest age so far
// Or:
decltype(persons.cbegin()) youngin;
// Iterate through each person in the list, looking for someone with a lower age than all previous
for (auto it = persons.cbegin(); it != persons.cend(); ++it){
if (p->age < minAge) {
std::cout << "Found someone younger. Age: " << p.age << std::endl;
// Update minAge and youngin
minAge = p->age;
youngin = it;
}
}
// Delete the youngest person from the list
persons.erase(youngin);
If you don't have to use pointer, this code should do :
int main() {
// Build a list of persons
std::list<Person> persons = {Person(50), Person(35), Person(99), Person(17)};
Person yongestPerson = persons[0];
// looking for someone with a lower age than all previous yongestPerson
for(persons p : persons){
if(p.age < yongestPerson.age){
std::cout << "Found someone younger. Age: " << p.age << std::endl;
// Update minAge and youngin
yongestPerson = p
}
}
// Delete the youngest person from the list
persons.remove(yongestPerson);
return 0;
}

Sorting of two vectors separately?

I have to make a program which uses the following two vectors:-
vector<double> age;
vector<string> name;
I take their input separately. I have to make a function sort() such that it sorts name alphabetically and then reorganizes age accordingly to match name.
Please help!!
If you can group them within struct or equivalent, you may create an additional vector for indexes that you sort and use for indirection:
std::vector<double> ages = /**/;
std::vector<string> names = /**/;
// ages.size() == names.size()
std::vector<std::size_t> indexes(names.size());
std::iota(indexes.begin(), indexes.end(), 0u);
std::sort(indexes.begin(), indexes.end(), [&](std::size_t lhs, std::size_t rhs) {
return names[lhs] < names[rhs];
});
for (auto index : indexes) {
std::cout << names[index] << " has " << ages[index] << std::endl;
}
And with range-v3 you can do:
std::vector<double> ages = /**/;
std::vector<string> names = /**/;
auto zip = ranges::view::zip(names, ages);
ranges::sort(zip);
for (const auto& z : zip) {
std::cout << std::get<0>(z) << " " << std::get<1>(z) << std::endl;
}
Demo
If the sort function accepts both the vectors, the easiest way is to copy everything to std::set<std::pair<string,double>> which sorts first on name and then copy the sorted entries to the input vectors. If you can't use sets, you can use vector and sort yourself.
The reason is that sorting changes the order so you lose the link between the entries of both vectors. If you can't or won't use the combined set method, you need to make sure that the link is maintained in another way, probably via a temporary container with references.
Assuming you really need a function that takes two vectors and modifies them.
The sort function can be implemented as:
void sort ( vector<double>& ages, vector<string>& names)
{
if ( ages.size() != names.size() )
return;
std::map< string, double > helper_map;
for ( size_t id = 0; id < names.size(); ++id)
{
helper_map.emplace( names[id], ages[id] );
}
names.clear();
ages.clear();
for (const auto& helper : helper_map)
{
names.push_back( helper.first );
ages.push_back( helper.second );
}
}
Working example:
http://coliru.stacked-crooked.com/a/2457c832c0b612b2
However keep in mind that this problem should be solved using different approaches as pointed out in the comments. As homework those things don't always apply though.

How to display values contained in LIST in a MAP stl: C++

I have a function where I am storing all the directories and its corresponding files.
I am facing problem in displaying the of VALUE the map. I have debugged and observed that the corresponding values are getting stored properly but I am not able to display it. Since value part of map is itself a list of pathiterator. Whenever I am getting same filename at different directory I am storing the reference or pathiterator in the map corresponding to the same key(file name). Kindly look into it.
void search()
{
using path_1 = std::string;
using paths_1 = std::set<path_1>;
using pathiter = paths_1::iterator;
using listofiter = std::list<pathiter>;
using file = std::string;
using store = std::map<file, listofiter>;
using store_iter = store::iterator;
paths_1 pp;
pathiter oo;
store s6;
for (recursive_directory_iterator i("."), end; i != end; ++i)
{
pp.insert(i->path().parent_path());
oo = pp.find(i->path().parent_path());
if (!is_directory(i->path()))
{
s6[i->path().filename()].push_back(oo);
}
}
store_iter d;
for ( d = s6.begin(); d != s6.end(); d++)
{
cout << "Key: " << d->first << " Value: "<<d->second;
// ^^not able
//to print it!!
}
}
I have added screenshot that I took while debugging. If the same file is present at different location the d->second {size} will be more than 1.
I thought of applying loop: Here is what I did. I think I messed it up.
for ( d = s6.begin(); d != s6.end(); d++)
{
cout << "Key: " << d->first << " Value: ";
for (int i = 0; i < d->second.size; i++)
{
cout << d->second[i];
// ^^Error at this place
}
}
ERROR: while using loop in a loop
<< d->second does not work because that is a collection.
You already knew one way to display a collection, using a for( ; ; ) loop. Since you have a container inside a container, you would need a for-loop inside a for-loop to print all elements.
As a bonus, here's how you can write more compact loops:
for (auto&& element : container) { ...
No need for iterators, begin and end calls. That's all handled for you. The body of the loop is executed once for each element in the container. Same restrictions apply: don't try to add or remove elements while inside the loop.
[edit]
You added a new, third kind of for-loop. For some reason you now tried to use for(int i = 0; i != container.size; ++i) to iterate over a container. Why? You already know one way that works, and I just showed you another way that works.
BTW, the reason that it doesn't work is that [i] requires operator[], which is not present in most containers. begin/end is present in all containers.

Iterating Multiple Multimaps

I'am having problems while trying to iterate some maps.
Basically i have a Deposit class. Each deposit class has a multimap containing a destination Deposit and a distance. (This will be used to create a graph).
When i try to iterate all the maps i'm getting a segmentation fault error.
Here's the code:
for (int j = 0; j < deposit.size(); j++) {
for (typename multimap< Deposit<Product>*, int>::iterator it = deposit.at(j)->getConnections().begin(); it != deposit.at(j)->getConnections().end(); it++) {
cout << "From the depo. " << deposit.at(j)->getKey() << " to " << it->first->getKey() << " with the distance " << it->second << endl;
}
}
EDIT:
Deposit Class:
template<class Product>
class Deposit {
private:
multimap <Deposit<Product>*, int> connections;
public:
void addConnection(Deposit<Product>* dep, int dist);
multimap <Deposit<Product>*, int> getConnections() const;
};
(...)
template<class Product>
void Deposit<Product> ::addConnection(Deposit<Product>* depKey, int dist) {
this->connections.insert(pair<Deposit<Product>*, int>(depKey, dist));
}
template<class Product>
multimap < Deposit<Product>*, int> Deposit<Product> ::getConnections() const {
return this->connections;
}
Storage Class - This is where I populate the multimaps.
(...)
ligs = rand() % 10;
do{
ligIdx = rand() % deposit.size();
dist = rand() % 100;
deposit.at(i)->addConnection(deposit.at(ligIdx), dist);
ligs--;
}while(ligs>0);
(...)
My deposit class has 2 subclasses. I dont know why the error occurs. Is there any problem with the iterator?
Thank you very much!!!
The problem you have is pretty nasty: getConnections() returns a multimap by value.
This means that successive calls to deposit.at(j)->getConnections() refer to different temporary copies of the original multimap. Thus the the iterator created on the begin of the first temporary copy, will never match the end of the second copy, without first accessing illegally some invalid places.
Two alternatives:
if you want to iterate on a copy, make one local copy auto cnx = deposit.at(j)->getConnections(); and change your inner loop to iterate on cnx.
if you intended to iterate on the original multimap, change the signature of getConnections() to return a reference.
By the way, if you use c++11 or higher, you could consider defining the iterator in a more readable way: for (auto it = ....) or even better, using the range-for syntax as proposed by Norah Attkins in her answer.
If you have a c++11 (or 14) compiler (and you should - unless it's a work/company barrier involved) consider using range based for loops to make your code clearer
for (auto const& elem : deposit)
{
for (auto const& product : elem)
{
}
}
Apart from the stylist guidance, lacking info on what the containers actrually hold, we'd just be guessing what's wrong when answering this question. My guess is that invalid reads happen and the pointers you're accessing are not allocated (but that's a guess)

Efficient way to re-order a C++ map-based collection

I have a large(ish - >100K) collection mapping a user identifier (an int) to the count of different products that they've bought (also an int.) I need to re-organise the data as efficiently as possible to find how many users have different numbers of products. So for example, how many users have 1 product, how many users have two products etc.
I have acheived this by reversing the original data from a std::map into a std::multimap (where the key and value are simply reversed.) I can then pick out the number of users having N products using count(N) (although I also uniquely stored the values in a set so I could be sure of the exact number of values I was iterating over and their order)
Code looks like this:
// uc is a std::map<int, int> containing the original
// mapping of user identifier to the count of different
// products that they've bought.
std::set<int> uniqueCounts;
std::multimap<int, int> cu; // This maps count to user.
for ( map<int, int>::const_iterator it = uc.begin();
it != uc.end(); ++it )
{
cu.insert( std::pair<int, int>( it->second, it->first ) );
uniqueCounts.insert( it->second );
}
// Now write this out
for ( std::set<int>::const_iterator it = uniqueCounts.begin();
it != uniqueCounts.end(); ++it )
{
std::cout << "==> There are "
<< cu.count( *it ) << " users that have bought "
<< *it << " products(s)" << std::endl;
}
I just can't help feeling that this is not the most efficient way of doing this. Anyone know of a clever method of doing this?
I'm limited in that I can't use Boost or C++11 to do this.
Oh, also, in case anyone is wondering, this is neither homework, nor an interview question.
Assuming you know the maximum number of products that a single user could have bought, you might see better performance just using a vector to store the results of the operation. As it is you're going to need an allocation for pretty much every entry in the original map, which likely isn't the fastest option.
It would also cut down on the lookup overhead on a map, gain the benefits of memory locality, and replace the call to count on the multimap (which is not a constant time operation) with a constant time lookup of the vector.
So you could do something like this:
std::vector< int > uniqueCounts( MAX_PRODUCTS_PER_USER );
for ( map<int, int>::const_iterator it = uc.begin();
it != uc.end(); ++it )
{
uniqueCounts[ uc.second ]++;
}
// Now write this out
for ( int i = 0, std::vector< int >::const_iterator it = uniqueCounts.begin();
it != uniqueCounts.end(); ++it, ++i )
{
std::cout << "==> There are "
<< *it << " users that have bought "
<< i << " products(s)" << std::endl;
}
Even if you don't know the maximum number of products, it seems like you could just guess a maximum and adapt this code to increase the size of the vector if required. It's sure to result in less allocations than your original example anyway.
All this is assuming that you don't actually require the user ids after you've processed this data of course (and as pointed out in the comments below, that the number of products bought for each user is a relatively small & contiguous set. Otherwise you might be better off using a map in place of a vector - you'll still avoid calling the multimap::count function, but potentially lose some of the other benefits)
It depends on what you mean by "more efficient". First off, is this really a bottle neck? Sure, 100k entries is a lot, but if you only have to this every few minutes, it's ok if the algorithm takes a couple seconds.
The only area for improvement I see is memory usage. If this is a concern, you can skip the generation of the multimap and just keep a counter map around, something like this (beware, my C++ is a little rusty):
std::map<int, int> countFrequency; // count => how many customers with that count
for ( std::map<int, int>::const_iterator it = uc.begin();
it != uc.end(); ++it )
{
// If it->second is not yet in countFrequency,
// the default constructor initializes it to 0.
countFrequency[it->second] += 1;
}
// Now write this out
for ( std::map<int, int>::const_iterator it = countFrequency.begin();
it != countFrequency.end(); ++it )
{
std::cout << "==> There are "
<< it->second << " users that have bought "
<< it->first << " products(s)" << std::endl;
}
If a user is added and buys count items, you can update countFrequency with
countFrequency[count] += 1;
If an existing user goes from oldCount to newCount items, you can update countFrequency with
countFrequency[oldCount] -= 1;
countFrequency[newCount] += 1;
Now, just as an aside, I recommend using an unsigned int for count (unless there's a legitimate reason for negative counts) and typedef'ing a userID type, for added readability.
If you can, I would recommend keeping both pieces of data current all the time. In other words, I would maintain a second map which is mapping number of products bought to number of customers who bought that many products. This map contains the exact answer to your question if you maintain it. Each time a customer buys a product, let n be the number of products this customer has now bought. Subtract one from the value at key n-1. Add one to the value at key n. If the range of keys is small enough this could be an array instead of a map. Do you ever expect a single customer to buy hundreds of products?
Just for larks, here's a mixed approach that uses a vector if the data is smallish, and a map to cover the case where one user has bought a truly absurd number of products. I doubt you'll really need the latter in a store app, but a more general version of the problem might benefit from it.
typedef std::map<int, int> Map;
typedef Map::const_iterator It;
template <typename Container>
void get_counts(const Map &source, Container &dest) {
for (It it = source.begin(); it != source.end(); ++it) {
++dest[it->second];
}
}
template <typename Container>
void print_counts(Container &people, int max_count) {
for (int i = 0; i <= max_count; ++i) {
if contains(people, i) {
std::cout << "==> There are "
<< people[i] << " users that have bought "
<< i << " products(s)" << std::endl;
}
}
}
// As an alternative to this overloaded contains(), you could write
// an overloaded print_counts -- after all the one above is not an
// efficient way to iterate a sparsely-populated map.
// Or you might prefer a template function that visits
// each entry in the container, calling a specified functor to
// will print the output, and passing it the key and value.
// This is just the smallest point of customization I thought of.
bool contains(const Map &c, int key) {
return c.count(key);
}
bool contains(const std::vector<int, int> &c, int key) {
// also check 0 < key < c.size() for a more general-purpose function
return c[key];
}
void do_everything(const Map &uc) {
// first get the max product count
int max_count = 0;
for (It it = uc.begin(); it != uc.end(); ++it) {
max_count = max(max_count, it->second);
}
if (max_count > uc.size()) { // or some other threshold
Map counts;
get_counts(uc, counts);
print_counts(counts, max_count);
} else {
std::vector<int> counts(max_count+1);
get_counts(uc, counts);
print_counts(counts, max_count);
}
}
From here you could refactor, to create a class template CountReOrderer, which takes a template parameter telling it whether to use a vector or a map for the counts.