Advertisement
Guest User

con

a guest
Nov 13th, 2019
118
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C++ 31.59 KB | None | 0 0
  1. /*                                                                              
  2.   main.cpp                                                                      
  3.   S. Edward Dolan                                                              
  4.   Saturday, November  9 2019                                                    
  5.                                                                                
  6.   A text concordance. Load a plain ASCII file, then search for words in the    
  7.   file. A word is defined as [a-zA-Z]+. The words are stored in lower case and  
  8.   searches ingore case.                                                        
  9.                                                                                
  10.   Some problems:                                                                
  11.                                                                                
  12.   * Only plain ASCII text files are permitted. The load() function will throw  
  13.     on characters > 0x7e but this doesn't guarantee stable operation. The      
  14.     problem is the current column is tracked and stored in a Ref, but a        
  15.     multi-byte character will throw that off.                                  
  16.                                                                                
  17.   * Tabs are not dealt with at all. This will also throw the column off. load()
  18.     will print a warning if a tab is encountered.                              
  19. */                                                                              
  20.                                                                                
  21. #include <iostream>                                                            
  22. #include <fstream>                                                              
  23. #include <vector>                                                              
  24. #include <forward_list>                                                        
  25. #include <algorithm>                                                            
  26. #include <sstream>                                                              
  27. #include <chrono>                                                              
  28. #include <iomanip>                                                              
  29. #include <cassert>                                                              
  30.                                                                                
  31. typedef std::string str_t;                                                      
  32. struct Ref;                                                                    
  33. typedef std::forward_list<Ref*> listref_t;                                      
  34. typedef std::pair<uint32_t, uint8_t> location_t; // line, column                
  35. typedef std::vector<location_t> veclocation_t;                                  
  36. typedef std::vector<str_t> vecstr_t;                                            
  37.                                                                                
  38. /*                                                                              
  39.   A word reference from a text file.                                            
  40.                                                                                
  41.   A Ref holds a lookup ID to the source file name where the word is found and  
  42.   a vector of <line, column> pairs where each occurrance of the word can be    
  43.   found. The line is one-based, the column is zero-based.                      
  44.  */
  45. struct Ref {                                                                    
  46.     Ref(const str_t& fname) : fileNameID(0), locs() {                          
  47.         for (size_t i=0; i<fileNames.size(); ++i) // meh...                    
  48.             if (fileNames[i] == fname) {                                        
  49.                 fileNameID = i;                                                
  50.                 return;                                                        
  51.             }                                                                  
  52.         fileNameID = fileNames.size();                                          
  53.         fileNames.push_back(fname);                                            
  54.     }                                                                          
  55.     Ref(const str_t& fname, uint32_t line, uint8_t column)                      
  56.         : Ref(fname) {                                                          
  57.         locs.push_back(std::make_pair(line, column));                          
  58.     }                                                                          
  59.     ~Ref() {                                                                    
  60.         fileNames.clear();                                                      
  61.         locs.clear();                                                          
  62.     }                                                                          
  63.     str_t fileName() { return fileNames[fileNameID]; }                          
  64.     const veclocation_t& getLocations() { return locs; }                        
  65.     bool hasLocation(uint32_t line, uint8_t column) {                          
  66.         for (auto loc : locs)                                                  
  67.             if (loc.first == line && loc.second == column)                      
  68.                 return true;                                                    
  69.         return false;                                                          
  70.     }                                                                          
  71.     bool addLocation(uint32_t line, uint8_t column) {                          
  72.         if (!hasLocation(line, column)) {                                      
  73.             locs.push_back(std::make_pair(line, column));                      
  74.             return true;                                                        
  75.         }                                                                      
  76.         return false;                                                          
  77.     }                                                                          
  78.     void write(std::ostream& s) {                                              
  79.         (void)s;                                                                
  80.     }                                                                          
  81. protected:                                                                      
  82.     static vecstr_t fileNames;                                                  
  83.     size_t fileNameID;                                                          
  84.     veclocation_t locs;                                                        
  85. };                                                                              
  86.                                                                                
  87. vecstr_t Ref::fileNames;                                                        
  88.  
  89. /*                                                                              
  90.   A node in a trie.                                                            
  91.                                                                                
  92.   A Node holds a singly linked list of Ref*, a bool flag to mark the end of a  
  93.   word, and an array of Node*: [a, b, ..., z].                                  
  94.  */                                                                            
  95. struct Node {                                                                  
  96.     // Will initialize C-style array (C++11) --------------v                    
  97.     Node(bool term = false) : refs(), term(term), children{0} {                
  98.     }                                                                          
  99.     ~Node() {                                                                  
  100.         for (auto r : refs)                                                    
  101.             delete r;                                                          
  102.         for (size_t i=0; i<26; ++i)                                            
  103.             delete children[i];                                                
  104.     }                                                                          
  105.     Node* getChild(char c) {                                                    
  106.         return children[std::tolower(c) - 97];                                  
  107.     }                                                                          
  108.     Node* addChild(char c, bool term = false) {                                
  109.         return children[std::tolower(c) - 97] = new Node(term);                
  110.     }                                                                          
  111.     void addRef(const str_t& fname, uint32_t line, uint8_t column,              
  112.                 bool term = false) {                                            
  113.         if (term)                                                              
  114.             this->term = true;                                                  
  115.         for (auto r : refs)                                                    
  116.             if (r->fileName() == fname) {                                      
  117.                 r->addLocation(line, column); // update ref                    
  118.                 return;                                                        
  119.             }                                                                  
  120.         refs.push_front(new Ref(fname, line, column)); // new ref              
  121.     }                                                                          
  122.     const listref_t& getRefs() { return refs; }                                
  123.     bool isTerm() { return term; }                                              
  124.     void write(std::ostream& s) {                                              
  125.         (void)s;                                                                
  126.     }                                                                          
  127. protected:                                                                      
  128.     listref_t refs;                                                            
  129.     bool term;            // true if the path that led here terminates a word  
  130.     Node* children[26];                                                        
  131. };
  132.  
  133. /*                                                                              
  134.   A fast but memory-hungry word look-up tree structure.                        
  135.                                                                                
  136.   The tree can hold the words, and the location (line/column) of those words,  
  137.   from multiple plain-text files. The tree is case-insensitive and will only    
  138.   contain lower-case alphabetic words [a-z].                                    
  139.                                                                                
  140.   The insert() and find() methods ignore the case of the supplied word.        
  141.                                                                                
  142.   {}  -- a Ref or Node struct                                                  
  143.   []  -- a std::vector                                                          
  144.   ()  -- a std::forward_list                                                    
  145.   <>  -- a std::pair                                                            
  146.                                                                                
  147.   If "cat" was the first word read from foo.txt at line 3, column 16:          
  148.                                                                                
  149.               .---------------------------- empty list of Ref*                  
  150.              /                                                                  
  151.            {(), false, [0, c, 0]}       <--- root Node                          
  152.         .------------------'                                                    
  153.         `->{(), false, [a, 0]}                                                  
  154.         .---------------'                                                      
  155.         `->{(), false, [0, t, 0]}                                              
  156.         .------------------'                                                    
  157.         `->{                                                                    
  158.             ({footxtID, [<3, 16>]}),        <--- one Ref                        
  159.             true,                           <--- terminates the word            
  160.             [0, ..., 0]                     <--- no children                    
  161.            }                                                                    
  162.                                                                                
  163.    If another "cat" was read @ line 8, column 3, the tree would be identical    
  164.    except the terminating node would be:                                        
  165.                                                                                
  166.             ({footxtID, true, [<3, 16>, <8, 3>]})  <--- new location appended  
  167.                                                                                
  168.    If "cat" was read from bar.txt @ line 56, column 0, the tree structure      
  169.    would still be unchanged. A new Ref would be added to the terminating node:  
  170.                                                                                
  171.         `->{                                                                    
  172.             ({footxtID, [<3, 16>, <8, 3>]}         <--- now two Refs            
  173.              {bartxtID, [<56, 0>]}),                                            
  174.             true,                                                              
  175.             [0, ..., 0]                                                        
  176.            }                                                                    
  177.  */
  178. struct Trie {                                                                  
  179.     Trie() : root(new Node), maxLine(1), maxColumn(0) {}                        
  180.     ~Trie() { delete root; }                                                    
  181.     int getMaxLine() { return maxLine; }                                        
  182.     int getMaxColumn() { return maxColumn; }                                    
  183.     void insert(const str_t& word, const str_t& fname, uint32_t line,          
  184.                 uint8_t column) {                                              
  185.         if (word.empty())                                                      
  186.             throw std::runtime_error("Trie can't insert empty strings");        
  187.         maxLine = std::max<int>(maxLine, line);                                
  188.         maxColumn = std::max<int>(maxColumn, column);                          
  189.         doInsert(root, word, fname, line, column);                              
  190.     }                                                                          
  191.     Node* find(const str_t& word) {                                            
  192.         assert(!word.empty());                                                  
  193.         return doFind(root, word);                                              
  194.     }                                                                          
  195.     void write(std::ostream& s) {                                              
  196.         root->write(s);                                                        
  197.     }                                                                          
  198. protected:                                                                      
  199.     void doInsert(Node* node, const str_t& word, const str_t& fname,            
  200.                   uint32_t line, uint8_t column) {                              
  201.         assert(node);                                                          
  202.         if (word.empty())                                                      
  203.             node->addRef(fname, line, column, true);                            
  204.         else {                                                                  
  205.             Node* n = node->getChild(word[0]);                                  
  206.             if (n)                                                              
  207.                 doInsert(n, word.substr(1), fname, line, column);              
  208.             else {                                                              
  209.                 Node* nn = node->addChild(word[0]);                            
  210.                 doInsert(nn, word.substr(1), fname, line, column);              
  211.             }                                                                  
  212.         }                                                                      
  213.     }                                                                          
  214.     Node* doFind(Node* node, const str_t& word) {                              
  215.         if (word.empty())                                                      
  216.             return node && node->isTerm() ? node : NULL;                        
  217.         if (!node)                                                              
  218.             return NULL;                                                        
  219.         return doFind(node->getChild(word[0]), word.substr(1));                
  220.     }                                                                          
  221.     Node* root;                                                                
  222.     int maxLine, maxColumn;     // for pretty printing                          
  223. };
  224.  
  225. /*                                                                              
  226.   Load the trie with the words in the file.                                    
  227. */                                                                              
  228. void doLoad(Trie& trie, const str_t& fname) {                                  
  229.     std::ifstream f(fname.c_str());                                            
  230.     if (!f) {                                                                  
  231.         std::stringstream ss;                                                  
  232.         ss << "failed to open: " << fname;                                      
  233.         throw std::runtime_error(ss.str());                                    
  234.     }                                                                          
  235.     int c;                                                                      
  236.     str_t word;                                                                
  237.     uint32_t line = 1;                                                          
  238.     uint8_t column = 0;                                                        
  239.     while (true) {                                                              
  240.         c = f.get();                                                            
  241.         if (c > 0x7e) {                                                        
  242.             // TODO: If this occurs, the trie will be in a crap state with      
  243.             //       only some of the file processed, depending on where the    
  244.             //       error occurred. So... possibly create a new trie for each  
  245.             //       load and merge it with the one supplied to this function.  
  246.             std::stringstream ss;                                              
  247.             ss << "line " << line << " of " << fname                            
  248.                << "\nInput must be plain ASCII, got char > 0x7e.";              
  249.             throw std::runtime_error(ss.str());                                
  250.         }                                                                      
  251.         if (c == '\t')                                                          
  252.             std::cerr << "WARNING: an unsupported TAB character was read."      
  253.                       << " This will result in inaccurate column tracking."    
  254.                       << std::endl;                                            
  255.         if (!f) {                                                              
  256.             if (!word.empty())                                                  
  257.                 trie.insert(word, fname, line, column);                        
  258.             break;                                                              
  259.         }                                                                      
  260.         else if (std::isalpha(c))                                              
  261.             word += (char)c;          // column is not advanced                
  262.         else {                                                                  
  263.             if (!word.empty()) {                                                
  264.                 trie.insert(word, fname, line, column);                        
  265.                 column += word.size();                                          
  266.                 word = "";                                                      
  267.             }                                                                  
  268.             // handle the char that may have terminted the last word            
  269.             if (c == '\r')                                                      
  270.                 continue;    // useless  sack  of  donkey  shit                
  271.             if (c == '\n') {                                                    
  272.                 column = 0;                                                    
  273.                 ++line;                                                        
  274.             }                                                                  
  275.             else                    // char not [a-zA-Z] CR NL                  
  276.                 ++column;                                                      
  277.         }                                                                      
  278.     }                                                                          
  279.     f.close();                                                                  
  280. }
  281.  
  282. /*                                                                              
  283.   Print the result of a search.                                                
  284.  */                                                                            
  285. void display(Node* node, const str_t& word, int maxLine, int maxColumn,        
  286.              bool showLines, bool align) {                                      
  287.     int maxPrintColumn = 79;                                                    
  288.     std::stringstream ss;                                                      
  289.     ss << maxLine;                                                              
  290.     int lineField = ss.str().size();                                            
  291.     ss.str("");                                                                
  292.     ss << maxColumn;                                                            
  293.     int columnField = ss.str().size();                                          
  294.     int textField = maxPrintColumn - (lineField + columnField + 2);            
  295.     int wordPos = textField / 2 - word.size() / 2;                              
  296.     for (auto ref : node->getRefs()) {                                          
  297.         std::ifstream f(ref->fileName());                                      
  298.         if (!f) {                                                              
  299.             std::stringstream ss;                                              
  300.             ss << "failed to open: " << ref->fileName();                        
  301.             throw std::runtime_error(ss.str());                                
  302.         }                                                                      
  303.         std::cout << ref->getLocations().size() << " occurrences of `"          
  304.                   << word << "' in " << ref->fileName() << std::endl;          
  305.         if (showLines) {                                                        
  306.             std::cout << str_t(79, '=') << std::endl;                          
  307.             str_t buf;                                                          
  308.             uint32_t thisLine = 1, lastLine = 0;                                
  309.             for (auto loc : ref->getLocations()) {                              
  310.                 if (lastLine != loc.first) {                                    
  311.                     do {                                                        
  312.                         std::getline(f, buf);                                  
  313.                         lastLine = thisLine;                                    
  314.                     } while (thisLine++ != loc.first);                          
  315.                 }                                                              
  316.                 std::cout << std::right << std::setw(lineField)                
  317.                           << loc.first << ":" << std::left                      
  318.                           << std::setw(columnField) << (int)loc.second;        
  319.                 str_t out = buf;                                                
  320.                 if (align) {                                                    
  321.                     if (loc.second < wordPos) // right shift                    
  322.                         out = str_t(wordPos - loc.second, ' ') + buf;          
  323.                     else if (loc.second > wordPos) // left shift                
  324.                         out = buf.substr(loc.second - wordPos);                
  325.                 }                                                              
  326.                 std::cout << ' '                                                
  327.                           << out.substr(0, std::min<int>(out.size(),            
  328.                                                          textField))            
  329.                           << std::endl;                                        
  330.             }                                                                  
  331.         }                                                                      
  332.         f.close();                                                              
  333.     }                                                                          
  334. }
  335.  
  336. void load(Trie& trie, const std::string& fname) {                              
  337.     std::cout << "loading...";                                                
  338.     std::cout.flush();                                                          
  339.     try {                                                                      
  340.         // auto start = std::chrono::high_resolution_clock::now();              
  341.         doLoad(trie, fname);                                                    
  342.         // auto end = std::chrono::high_resolution_clock::now();                
  343.         // std::chrono::duration<double> t = end - start;                      
  344.         // std::cout << " time: " << t.count() << " seconds" << std::endl;      
  345.     }                                                                          
  346.     catch (std::exception& e) {                                                
  347.         std::cout << "\nError: " << e.what() << std::endl;                      
  348.     }                                                                          
  349. }                                                                              
  350.                                                                                
  351. void help() {                                                                  
  352.     std::cout << ",align - toggle vertical word alignment in matched lines\n"  
  353.               << ",help  - print this help\n"                                  
  354.               << ",load  - load a text file into the trie\n"                    
  355.               << ",quit  - exit the program\n"                                  
  356.               << ",show  - toggle printing matched lines"                      
  357.               << std::endl;                                                    
  358. }
  359.  
  360. int main(int argc, char** argv) {                                              
  361.     Trie trie;                                                                  
  362.     if (argc > 1) {                                                            
  363.         for (int i=1; i<argc; ++i)                                              
  364.             load(trie, argv[i]);                                                
  365.     }                                                                          
  366.     str_t word;                                                                
  367.     bool show = false;                                                          
  368.     bool align = false;                                                        
  369.     str_t legalChars("abcdefghijklmnopqrstuvwxyz"                              
  370.                      "ABCDEFGHIJKLMNOPQRSTUVWXYZ");                            
  371.     std::cout << "All input files must be plain ASCII text. Chaos will reign"  
  372.               << " supreme otherwise." << std::endl;                            
  373.     help();                                                                    
  374.     while (std::cout << "> " && std::cin >> word) {                            
  375.         if (word == ",align") {                                                
  376.             align = !align;                                                    
  377.             continue;                                                          
  378.         }                                                                      
  379.         else if (word == ",help")                                              
  380.             help();                                                            
  381.          else if (word == ",load") {                                            
  382.             std::cout << "File name to load: ";                                
  383.             std::string fname;                                                  
  384.             std::cin >> fname;                                                  
  385.             load(trie, fname);                                                  
  386.             continue;                                                          
  387.         }                                                                      
  388.         else if (word == ",quit")                                              
  389.             break;                                                              
  390.         else if (word == ",show") {                                            
  391.             show = !show;                                                      
  392.             continue;                                                          
  393.         }                                                                      
  394.         if (word.find_first_not_of(legalChars) != std::string::npos)            
  395.             continue;                                                          
  396.         // auto start = std::chrono::high_resolution_clock::now();              
  397.         Node* node = trie.find(word);                                          
  398.         // auto end = std::chrono::high_resolution_clock::now();                
  399.         if (node) {                                                            
  400.             display(node, word, trie.getMaxLine(), trie.getMaxColumn(), show,  
  401.                     align);                                                    
  402.             // std::chrono::duration<double> t = end - start;                  
  403.             // std::cout << "*** Seach time: " << t.count() * 1e3              
  404.             //           << " milliseconds" << std::endl;                      
  405.         }                                                                      
  406.     }                                                                          
  407.     return 0;                                                                  
  408. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement