Finding the max lengths of strings to format a table output - c++

I want to find the max length of specific attributes from a vector of Person objects.
Below is an example of a Person object:
Person::Person(string first_name, string last_name, int the_age){
first = first_name;
last = last_name;
age = the_age;
}
I have a vector that stores Person objects, and I must print all the people out in a table, like so:
First Name Last Name Age
---------- ------------ ----
John Cool-Johnson 15
Paul Bob 1000
2 people
I need to find the max length of each attribute of a Person in order to grow each column according to the maximum length of name or age. How can I do this?
So far, I have tried lambdas using this code:
unsigned int max_name = *max_element(generate(people.begin(),people.end(), [](Person a){return a.getFirstName()})).size();
But I am not sure if this even works at all.
I must use <iomanip>, but I have no clue how it works.
Is there a better way?

Your use of std::max_element() is wrong. It takes 2 iterators for input, which you are not providing to it. It would need to look more like this:
auto max_name = max_element(
people.begin(), people.end(),
[](const Person &a, const Person &b){
return a.getFirstName().size() < b.getFirstName().size();
}
)->getFirstName().size();
Online Demo
Alternatively:
vector<string> names;
names.reserve(people.size());
for(const Person &p : people) {
names.push_back(p.getFirstName());
}
auto max_name = max_element(
names.begin(), names.end(),
[](const string &a, const string &b){
return a.size() < b.size();
}
)->size();
Online Demo
However, since you will probably also want to do the same thing for the Last Name and Age columns, I would suggest simply looping though the people vector manually, keeping track of the max lengths as you go along, eg:
string::size_type max_fname = 10;
string::size_type max_lname = 9;
string::size_type max_age = 3;
for(const Person &p : people)
{
max_fname = max(max_fname, p.getFirstName().size());
max_lname = max(max_lname, p.getLastName().size());
max_age = max(max_age, to_string(p.getAge()).size());
}
Then you can output everything in a table, eg:
cout << left << setfill(' ');
cout << setw(max_fname) << "First Name" << " " << setw(max_lname) << "Last Name" << " " << setw(max_age) << "Age" << "\n";
cout << setfill('-');
cout << setw(max_fname) << "" << " " << setw(max_lname) << "" << " " << setw(max_age) << "" << "\n";
cout << setfill(' ');
for(const Person &p : people)
{
cout << setw(max_fname) << p.getFirstName() << " " << setw(max_lname) << p.getLastName() << " " << setw(max_age) << p.getAge() << "\n";
}
cout << people.size() << " people\n";
Online Demo

Related

Print 2 for loops next to each other

I was wondering how can I print 2 for loops next to each other?
These are loops:
for(vector<Student>::iterator it = studenti.begin(); it != studenti.end(); ++it)
cout << "| " << it->brojIndeksa << " " << it->ime << " " << it->prezime;
for(map<string,string>::iterator it = ocjene.begin(); it!= ocjene.end(); ++it)
cout << " " << it->first << " - " << it->second << endl;
the output I want:
| brojIndeksa ime prezime first - second
| brojIndeksa ime prezime first - second
| brojIndeksa ime prezime first - second
the output I have:
| brojIndeksa ime prezime| brojIndeksa ime prezime first - second
first - second
EDIT:
struct Predmet {
string naziv;
string odsjek;
istream& dodaj_predmet(istream &);
void sort_predmeti();
};
struct Student {
string brojIndeksa;
string ime;
string prezime;
map<std::string, string> ocjene;
istream& dodaj_studenta(istream &);
void sort_studenti();
};
map<std::string, string> ocjene contains 2 strings for input, 1st one needs to be naziv from Predmet structure.
NEW OUTPUT:
| 1808 John Doe EJ 10
| PIM 10
| 1809 Jessica Doe PIM 10
You could iterate using both iterators in the same loop:
auto studenti_it = studenti.begin();
auto ocjene_it = ocjene.begin();
for (; studenti_it != studenti.end() && ocjene_it != ocjene.end(); ++studenti_it, ++ocjene_it)
{
// Print using both iterators...
}
After your edit where we see that the map is a member of the Student structure, I'm guessing what you want is to iterate over first the studenti vector, and then inside it have a loop for the Student::ocjene map?
Then you would have something like
// First iterate over the vector
for (auto const& student : studenti)
{
std::cout << "| " << student.brojIndeksa << " " << student.ime << " " << student.prezime << '\n';
// Then inside iterate over the map
for (auto const& oj : student.ocjene)
{
std::cout << '\t' << oj.first << ' ' << oj.second << '\n';
}
}

find the longest word in a vector c++

So i have a c++ vector which contains about 106 thousand words which are stored on vector<string>words i need to find the longest word in this vector and i also need to get the location at which the word is, for example (1,2,3) in my vector. I need this location because i have two other vectors which has the meaning and the type for the words. vector<string>definition, vector<string>type
Please help
My current code
this code is not working at all
copy_if(words.begin(), words.end(), back_inserter(length), [](const string& x) { return x.length() > 40; });// looks for words longer than 7 letters
for (const string& s : length)
{
cout << "found!!" << endl;
auto i = find(words.begin(), words.end(), s);//looks for the word in the words vector
if (i != words.end())
{
auto pos = i - words.begin();
//displays the word, type and the definition of the word that the user has entered
cout << "Word : " << words[pos] << '\n';
cout << "Type : " << definitions[pos] << '\n';
cout << "Definition: " << types[pos] << '\n';
cout << '\n';
}
else
cout << "word not found" << endl;
}
You could use the standard algorithm std::max_element to search through the vector<string>.
Example:
#include <algorithm> // max_element
#include <iostream>
#include <iterator> // distance
#include <string>
#include <vector>
int main() {
std::vector<std::string> words{"a", "bb", "ccc"};
auto it = std::max_element(words.begin(), words.end(),
[](const auto& a, const auto& b) {
return a.size() < b.size();
});
std::cout << "The longest word is " << *it << " at (zero-based) pos "
<< std::distance(words.begin(), it) << '\n';
}
Output:
The longest word is ccc at (zero-based) pos 2
I would prefer thinking simply: just check length of elements according to each indice and update information according to that.
std::vector<std::string> length;
// initialize the vector length
size_t max_length = 0; // the length of longest word(s)
std::vector<size_t> max_indice; // the indice of longest word(s)
for (size_t i = 0; i < length.size(); i++) {
size_t this_len = length[i].length();
if (this_len > max_length) {
// new record
max_length = this_len;
max_indice.clear();
max_indice.push_back(i);
} else if (this_len == max_length) {
// tie
max_indice.push_back(i);
}
}
for (size_t pos : max_indice) {
cout << "Word : " << words[pos] << '\n';
cout << "Type : " << definitions[pos] << '\n';
cout << "Definition: " << types[pos] << '\n';
cout << '\n';
}

C++ Array using string thats read in

my program is reading in 2 text files, one is going into an array and one is priming read normally. The one that is being read into an array has an item code, price, quantity, and item name. When the item code matches with the code on the other text document I need to get the price associated with it and cant figure out how.
while (!purchasesFile.eof())
{
purchasesFile >> PurchaseItem >> purchaseQty;
cout << purchaseNum << " " << PurchaseItem << " " << setw(4) <<
purchaseQty << " # " << dollarSign << endl;
int n = 0;
if (inventoryRec[n].itemCode != PurchaseItem)
{
inventoryRec[n+1];
}
else
{
cout << inventoryRec[n].itemPrice << endl;
inventoryRec[n+1];
}
if (PurchaseItem == inventoryRec[itemCount].itemCode)
{
inventoryRec[itemCount].itemOnHand - purchaseQty;
purchaseAmount = inventoryRec[itemCount].itemPrice * purchaseQty;
cout << purchaseAmount << " " <<
inventoryRec[itemCount].itemOnHand;
purchaseCount++;
}
purchasesFile >> purchaseNum;
}
purchasesFile.close();
There are several statements in your code that do nothing:
inventoryRec[n+1];
inventoryRec[itemCount].itemOnHand - purchaseQty;
What you are looking for is probably something like the STL map
typedef struct inventory_item_t {
inventory_item_t(const std::string& item_code, double price, int quantity) :
item_code(item_code),
price(price),
quantity(quanity) { }
std::string item_code;
double price;
int quantity;
} inventory_item_t;
typedef std::map<std::string, inventory_item_t> inventory_items_t;
inventory_items_t inventory_items;
inventory_items.insert(make_pair("item1", inventory_item_t("item1", 1.0, 1)));
inventory_items.insert(make_pair("item2", inventory_item_t("item2", 1.1, 2)));
inventory_items.insert(make_pair("item3", inventory_item_t("item3", 1.2, 3)));
inventory_items_t::iterator inventory_item = inventory_items.find("item1");
if(inventory_item != inventory_items.end()) {
std::cout << "Inventory Item found - item_code: ["
<< inventory_item->first
<< "], price: ["
<< inventory_item->second.price
<< "]"
<< std::endl;
} else {
std::cout << "Inventory Item not found" << std::endl;
}

How do I search a book list class by the author's last name but with multiple authors?

So I finished my project, but my only problem is that I can only search one author at a time. I seem to not be able to figure it out.
This is what I have..am I missing something thats not making me able to find more than one author's last name?
void BookRecordUI::FindBookLast() //allows us to search a book by the last name of the author from the book record...
{
string Last;
cout << "Enter Book by Last Name of Author: " << endl;
getline(cin, Last);
Collection.FindBookAuthorLast(Last);
}
Any help will be much appreciated!
EDIT: So basically I want to find multiple authors..for example, if I inputted John Hopkins and Wilson Greene, I want to pull both authors last name at the same time. Sorry for not clearly explaining it.
I also have this part as well..
void BookRecordList::FindBookAuthorLast(string Last)
{
int K;
for(K = 0; K < (int)List.size(); K++)
if(List[K].GetAuthorLast() == Last)
cout << List[K].GetTitle() << " " << List[K].GetAuthorFirst() << " " << List[K].GetAuthorLast() << " " << List[K].GetPublisher() << " " << List[K].GetPublisherAddress() << " " << List[K].GetPublisherPhone() << " "
<< List[K].GetPublisherContact() << " "<< List[K].GetCategory() << " " << List[K].GetDate() << endl;
};
My whole program is really long, so I dont want to overwhelm you guys by posting the whole thing up..
You may need to change your Book definition to enable searching for multiple authors:
struct Author
{
std::string& get_name() const;
};
struct Book
{
std::vector<Author> m_authors; // A book can have 1 or more authors
bool has_author(std::string author_name) const
{
std::vector<Author>::const_iterator iter;
for (iter = m_authors.begin();
iter != m_authors.end();
++iter)
{
if (iter.get_name() == author_name)
{
return true;
}
}
return false;
};
The objective now is to write a predicate or functor that will call Book::has_author.
This is one solution to your issue, there are probably others when you give it more thought.
You do not give us a lot of informations about what is a "book" and what is a Collection.
But, it seems that you already implement a function that returns you the expected result for one string (the last name of an author).
What you can do is use multiples times your function FindBookAuthorLast with a different last name each time.
Or, implement a function that takes a vector of string in parameters and that returns you a vector of Book (or whatever is your class containing the books).
EDIT :
With the new informations you posted, here is a way to do it :
(This is not the only solution to do it, there is a lot)
(Code not compiled, not tested)
void BookRecordList::FindBookAuthorLast(vector<string> Last)
{
int K;
vector<string>::iterator author_it = Last.begin();
for ( ; author_it != Last.end(); ++author_it)
{
for(K = 0; K < (int)List.size(); K++)
if(List[K].GetAuthorLast() == *author_it)
cout << List[K].GetTitle() << " " << List[K].GetAuthorFirst() << " " << List[K].GetAuthorLast() << " " << List[K].GetPublisher() << " " << List[K].GetPublisherAddress() << " " << List[K].GetPublisherPhone() << " "
<< List[K].GetPublisherContact() << " "<< List[K].GetCategory() << " " << List[K].GetDate() << endl;
}
};
To build the vector<string> to give to the function FindBookAuthorLast, iterate on getline().
Maybe all you want is to loop over your function:
void BookRecordUI::FindBookLast() //allows us to search a book by the last name of the author from the book record...
{
string Last;
do {
cout << "Enter Book by Last Name of Author: " << endl;
getline(cin, Last);
Collection.FindBookAuthorLast(Last);
}
while(!Last.empty());
}
(not tested).

Formatting C++ console output

I've been trying to format the output to the console for the longest time and nothing is really happening. I've been trying to use as much of iomanip as I can and the ofstream& out functions.
void list::displayByName(ostream& out) const
{
node *current_node = headByName;
// I have these outside the loop so I don't write it every time.
out << "Name\t\t" << "\tLocation" << "\tRating " << "Acre" << endl;
out << "----\t\t" << "\t--------" << "\t------ " << "----" << endl;
while (current_node)
{
out << current_node->item.getName() // Equivalent tabs don't work?
<< current_node->item.getLocation()
<< current_node->item.getAcres()
<< current_node->item.getRating()
<< endl;
current_node = current_node->nextByName;
}
// The equivalent tabs do not work because I am writing names,
// each of different length to the console. That explains why they
// are not all evenly spaced apart.
}
Is their anything that I can use to get it all properly aligned with each other?
The functions that I'm calling are self-explanatory and all of different lengths, so that don't align very well with each other.
I've tried just about everything in iomanip.
Think of it like using Microsoft Excel :)
You think of your stream as fields. So you set the width of the field first then you insert your text in that field. For example:
#include <iostream>
#include <iomanip>
#include <string>
int main()
{
using namespace std;
string firstName = "firstName",
secondName = "SecondName",
n = "Just stupid Text";
size_t fieldWidth = n.size(); // length of longest text
cout << setw(fieldWidth) << left << firstName << endl // left padding
<< setw(fieldWidth) << left << secondName << endl
<< setw(fieldWidth) << left << n << endl;
cout << setw(fieldWidth) << right << firstName << endl // right padding
<< setw(fieldWidth) << right << secondName << endl
<< setw(fieldWidth) << right << n << endl;
}
......
......
The field width means nothing but the width of the text + spaces. You could fill anything other than spaces:
string name = "My first name";
cout << setfill('_') << setw(name.size() + 10) << left << name;
.....
output::
My first name__________
......
I think the best way is to figure out your format then, write a new formatter that does all what you want:
#include <iostream>
#include <iomanip>
#include <string>
std::ostream& field(std::ostream& o)
{
// usually the console is 80-character wide.
// divide the line into four fields.
return o << std::setw(20) << std::right;
}
int main()
{
using namespace std;
string firstName = "firstName",
secondName = "SecondName",
n = "Just stupid Text";
size_t fieldWidth = n.size();
cout << field << firstName << endl
<< field << secondName << endl
<< field << n << endl;
}
If you started thinking about parametrized manipulators, only that accept one int or long parameter are easy to implement, other types are really obscure if you are not familiar with streams in C++.
Boost has a format library that allows you to easily format the ourput like the old C printf() but with type safety of C++.
Remember that the old C printf() allowed you to specify a field width. This space fills the field if the output is undersized (note it does not cope with over-sized fields).
#include <iostream>
#include <iomanip>
#include <boost/format.hpp>
struct X
{ // this structure reverse engineered from
// example provided by 'Mikael Jansson' in order to make this a running example
char* name;
double mean;
int sample_count;
};
int main()
{
X stats[] = {{"Plop",5.6,2}};
// nonsense output, just to exemplify
// stdio version
fprintf(stderr, "at %p/%s: mean value %.3f of %4d samples\n",
stats, stats->name, stats->mean, stats->sample_count);
// iostream
std::cerr << "at " << (void*)stats << "/" << stats->name
<< ": mean value " << std::fixed << std::setprecision(3) << stats->mean
<< " of " << std::setw(4) << std::setfill(' ') << stats->sample_count
<< " samples\n";
// iostream with boost::format
std::cerr << boost::format("at %p/%s: mean value %.3f of %4d samples\n")
% stats % stats->name % stats->mean % stats->sample_count;
}
Give up on the tabs. You should be able to use io manipulators to set the field width, the fill character, and the format flag (to get left or right justification). Use the same values for the headings as you do for the data, and everything should come out nicely.
Also beware that you've switched Rating and Acres in your example.
You can write a procedure that always print the same number of characters to standard output.
Something like:
string StringPadding(string original, size_t charCount)
{
original.resize(charCount, ' ');
return original;
}
And then use like this in your program:
void list::displayByName(ostream& out) const
{
node *current_node = headByName;
out << StringPadding("Name", 30)
<< StringPadding("Location", 10)
<< StringPadding("Rating", 10)
<< StringPadding("Acre", 10) << endl;
out << StringPadding("----", 30)
<< StringPadding("--------", 10)
<< StringPadding("------", 10)
<< StringPadding("----", 10) << endl;
while ( current_node)
{
out << StringPadding(current_node->item.getName(), 30)
<< StringPadding(current_node->item.getLocation(), 10)
<< StringPadding(current_node->item.getRating(), 10)
<< StringPadding(current_node->item.getAcres(), 10)
<< endl;
current_node = current_node->nextByName;
}
}