-1

I want to ask for help to complete my C++ project. The question is about reading from a text file that contains a story with 18 names. The names repeat a lot in the story. The program should count the repeating names.

#include <iostream>
#include <fstream>
#include <cstdlib>
#include <string>
using namespace std;
int main()
{
    ifstream in("Romeo and Juliet.txt");
    ofstream out;

    string name[] = {"Escalus","Paris","Montague","Capulet","Romeo","Tybalt",
                     "Mercutio","Benvolio","Friar Laurence","Friar 
                      John","Balthasar","Abram","Sampson","Gregory",
                      "Peter","Lady Montague","Lady Capulet","Juliet"};
    string str;
    int scount=0,v[18];

    for(int i=0;i<18;i++)
    {
     scount=0;
        while(!in.eof())
       {

           while(getline(in,str))
           {
               if(str==name[i])
               {
                   scount++;
               }
           }
       }
        v[i]=scount;
    }

      for(int i=0;i<18;i++)
      {
         cout<<v[i]<<endl;
      }
       in.close();
 }
Remy Lebeau
  • 454,445
  • 28
  • 366
  • 620
Yusuf
  • 31
  • 1
  • 6
  • 1. You should not use spaces in a filename/path 2. Have you tried the c++ regex library yet? http://www.cplusplus.com/reference/regex/ – forthe May 18 '18 at 03:00
  • "Friar John" seems to be broken – set0gut1 May 18 '18 at 03:01
  • @set0gut1 Maybe it is because the string literal extends across a line. In order to make it extend to the next line, you need a backslash. – forthe May 18 '18 at 03:02
  • @Yusuf, i think that better to read file at once and increment v[i] online. – Vladimir Ch. May 18 '18 at 03:09
  • @VladimirCh. could you explain more clearly? – Yusuf May 18 '18 at 03:12
  • @forthe there is nothing wrong with using spaces in paths/filenames – Remy Lebeau May 18 '18 at 03:12
  • `while(!in.eof())` [is wrong](https://stackoverflow.com/questions/5605125/). And the code is reading the file line-by-line and comparing whole lines to the names array. The code needs to read the file word-by-word instead, and needs extra logic to handle the 2-word names. – Remy Lebeau May 18 '18 at 03:16
  • @RemyLebeau do you have any advice to read word by word ? could share? – Yusuf May 18 '18 at 03:20
  • A lot of looping action going on there. Not sure most of them are needed, but if they are you're going to have to rewind the file at the end of the inner loop. – user4581301 May 18 '18 at 03:23
  • @Yusuf check my answer. – Vladimir Ch. May 18 '18 at 03:31
  • @RemyLebeau It is discouraged. In a command line environment, you would have to escape the spaces. MinGW installment tells you not to install into a path with spaces. – forthe May 18 '18 at 12:16
  • @forthe just because MinGW can't handle spaces does not discourage users from using spaces in their own code. – Remy Lebeau May 18 '18 at 15:12

2 Answers2

1

If you use std::string i think you can use std::map.

Map is using for counting names in the text.

map < string, int > v;
for (int i = 0; i < 18; i++)
    v[name[i]] = 0;

For word by word input i use:

freopen("Romeo and Juliet.txt", "r", stdin);
while (cin >> str)
{
    if (str == "Friar" || str == "Lady")
    {
        string s;
        cin >> s;
        if (s.empty())
            continue;
        if ( ((s == "Montague" || s == "Capulet") && str == "Lady") ||
            str == "Friar" && s == "Laurence")
        {
            v[str + " " + s]++;
        }
        else if (v.find(s) != v.end())
            v[s]++;

    }
    else if (v.find(str) != v.end())
        v[str]++;
}    

Output values from map:

for (auto it = v.begin(); it != v.end(); it++)
{
    cout << it->first << " " << it->second << endl; 
}

This is working.

Vladimir Ch.
  • 1,014
  • 1
  • 8
  • 16
  • thank everyone for paying attention to my simple problem, here ,i do not understand what you mean,could explain? map < string, int > v; for (int i = 0; i < 18; i++) v[name[i]] = 0; – Yusuf May 18 '18 at 03:35
  • 1
    Map contains pairs of < key, value >: key - name from names[] value - count of name in the text. Cycle for initialize map with zeros. – Vladimir Ch. May 18 '18 at 03:37
0

the main problem is that you need to split by spaces instead of newlines

#include <iostream>
#include <fstream>
#include <sstream>
#include <string.h>
#include <map>
#include <algorithm>

using namespace std;

typedef map<string, int> word_count_t;

template<typename A, typename B>
std::pair<B,A> flip_pair(const std::pair<A,B> &p)
{
    return std::pair<B,A>(p.second, p.first);
}

template<typename A, typename B>
std::multimap<B,A> flip_map(const std::map<A,B> &src)
{
    std::multimap<B,A> dst;
    std::transform(src.begin(), src.end(), std::inserter(dst, dst.begin()),
                   flip_pair<A,B>);
    return dst;
}

int main() {
    std::ifstream in("/tmp/test.txt");

    word_count_t word_count;
    word_count.insert(make_pair("Escalus", 0));
    word_count.insert(make_pair("Paris", 0));
    word_count.insert(make_pair("Friar Laurence", 0));

    string line;
    string previous;
    string current;

    bool first_line(true);

    while(getline(in, line)) {
        stringstream ss(line);

        if(first_line && getline(ss, previous, ' ')) {
            word_count_t::iterator itr = word_count.find(previous);

            if(itr != word_count.end()) {
                itr->second++;
            }

            first_line = false;
        }

        while(getline(ss, current, ' ')) {
            word_count_t::iterator itr = word_count.find(current);

            if(itr != word_count.end()) {
                itr->second++;
            }

            itr = word_count.find(previous + " " + current);

            if(itr != word_count.end()) {
                itr->second++;
            }

            previous = current;
        }
    }

    std::multimap<int, string> sorted = flip_map(word_count);

    for(std::multimap<int, string>::iterator itr = sorted.begin(); itr != sorted.end(); itr++) {
        cout << itr->first << ": [" << itr->second << "]" << endl;
    }

    cout << endl;

    for(std::multimap<int, string>::reverse_iterator itr = sorted.rbegin(); itr != sorted.rend(); itr++) {
        cout << itr->first << ": [" << itr->second << "]" << endl;
    }
}

Edit: you may need to add some code like:

#include <boost/algorithm/string.hpp>

boost::replace_all(current, ",", "");
boost::replace_all(current, ".", "");
boost::replace_all(current, ";", "");
boost::replace_all(current, ":", "");
boost::replace_all(current, "\t", "");
boost::replace_all(current, "[", "");
boost::replace_all(current, "]", "");
boost::replace_all(current, "(", "");
boost::replace_all(current, ")", "");
Abdul Ahad
  • 742
  • 7
  • 16
  • here,word_count.insert(make_pair("the", 0)); word_count.insert(make_pair("from", 0)); word_count.insert(make_pair("for", 0)); what the lines mean? – Yusuf May 18 '18 at 03:45
  • @Yusuf just use the "names" you want to measure in those statements – Abdul Ahad May 18 '18 at 03:49
  • @Yusuf actually, the names like "Friar Joseph" make it a bit more complicated, I'll update the answer – Abdul Ahad May 18 '18 at 03:50
  • i have 18 names ,you mean i should write all names like this ,word_count.insert(make_pair("the", 0)); word_count.insert(make_pair("from", 0)); word_count.insert(make_pair("for", 0)); ,right? – Yusuf May 18 '18 at 04:17
  • @Yusuf yeah, I edited the answer to account for two word names. I guess there are other ways to do it using regex matching or something like that. should work. – Abdul Ahad May 18 '18 at 04:22
  • if(itr != word_count.end()) { itr->second++; } here for counting repeated name ,right. And second is a variable? – Yusuf May 18 '18 at 08:10
  • itr->second is how you access the "value" in a std map from it's iterator. the code uses the value as the count. itr->first is the "key" in the map, or the name – Abdul Ahad May 18 '18 at 08:15
  • thank you ,bro!! very much, thank everyone for giving your advices !! – Yusuf May 18 '18 at 08:56
  • one more question ,if i want to sort the result according to their quantity ?now i do not know how the map library works? – Yusuf May 18 '18 at 09:22
  • Could you help me in this part as well,because i don't know how to use that method on the link you sent in my code? – Yusuf May 18 '18 at 21:47
  • just cut and paste the code and call std::multimap dst = flip_map(word_count); – Abdul Ahad May 19 '18 at 01:57
  • actually i have not ever used this map ,thats way i dont understand – Yusuf May 19 '18 at 02:10
  • Bro, now i have joined ,got the new bugs ,could help me to join that two codes together ,please? – Yusuf May 19 '18 at 03:13
  • when while loop is reading the file map cannot store all names at one time ,some names are missed ,can i read the file again untill read all names completely? like foor loop 16 times because we have 16 names in the file – Yusuf May 19 '18 at 12:35
  • some names are missed ,so the quantity of them is not full – Yusuf May 19 '18 at 12:36
  • the map should be able to handle it in one pass. you need to add a line like word_count.insert(make_pair("Escalus", 0)); for every name you want to measure – Abdul Ahad May 19 '18 at 19:38
  • firstly i added all 16 names as you showed ,is it impossible to read the file over with map? – Yusuf May 20 '18 at 01:32
  • almost nothing is impossible. it should count all the names with one pass. if you read the file twice, they would be double-counted. what are you observing after you added all 18 names? – Abdul Ahad May 20 '18 at 02:07
  • i mean in the first pass only the first name should be counted ,in the second pass the second one and etc. can we restrict as i thought? – Yusuf May 20 '18 at 02:36
  • the map is used to count all the names with one pass. If you want to read the file 18 times, just use a string, not a map – Abdul Ahad May 20 '18 at 06:20
  • ok, I update the story, perhaps it's things like commas that are throwing off the count – Abdul Ahad May 20 '18 at 06:38