【问题标题】:How can i split adjacent numbers and letters in c++?如何在 C++ 中拆分相邻的数字和字母?
【发布时间】:2021-09-27 17:54:26
【问题描述】:

我有一个包含相邻数字和字母的大文本文档。 就这样, JACK1940383DAVID30284HAROLD68372TROY4392等

我如何在 C++ 中像下面这样拆分它

名单:杰克/1940383,大卫/30284,...

【问题讨论】:

  • 可能是regex_iterator
  • 从数字到字符串相对容易,但反过来……当调用isdigit 的结果改变状态时,我不会逐个字符地阅读字符串并分开。
  • 您可以找到第一个不是字母(字母)的字符,然后创建一个子字符串。下一个子字符串将由第一个不是数字的字符终止。
  • 请提供足够的代码,以便其他人更好地理解或重现问题。

标签: c++ parsing split char


【解决方案1】:

您可以在循环中使用std::string::find_first_of()std::string::find_first_not_of(),使用std::string::substr() 提取每个片段,例如:

std::string s = "JACK1940383DAVID30284HAROLD68372TROY4392";
std::string::size_type start = 0, end;

while ((end = s.find_first_of("0123456789", start)) != std::string::npos) {
    std::string name = s.substr(start, end-start);
    start = end;

    int number;
    if ((end = s.find_first_not_of("0123456789", start)) != std::string::npos) {
        number = std::stoi(s.substr(start, end-start));
    }
    else {
        number = std::stoi(s.substr(start));
    }
    start = end;

    // use name and number as needed...
}

Online Demo

【讨论】:

    【解决方案2】:

    你可以像这样使用正则表达式:

    #include <iostream>
    #include <string>
    #include <regex>
    #include <vector>
    
    // create a struct to group your data
    // this makes it easy to store it in a vector.
    struct person_t
    {
        std::string name;
        std::string number;
    };
    
    
    // overloaded output operator for printing one person's details
    std::ostream& operator<<(std::ostream& os, const person_t& person)
    {
        std::cout << person.name << ": " << person.number << std::endl;
        return os;
    }
    
    // get a vector of person_t based on the input
    auto get_persons(const std::string& input)
    {
        // make a regex in this case a regex that will match one or more capital letters 
        // and groups them using the ()
        // then match one or more digits and group them too.
        static const std::regex rx{ "([A-Z]+)([0-9]+)" };
        std::smatch match;
    
        // a vector to hold all the persons
        std::vector<person_t> persons;
    
        // start at begin of string and look for first part of the string
        // that matches the regex.
        auto cbegin = input.cbegin();
    
        while (std::regex_search(cbegin, input.cend(), match, rx))
        {
            // match[0] will contain the whole match, 
            // match[1]-match[n] will contain the groups from the regular expressions
            // match[1] will contain the match with characters and thus the name
            // match[2] will contain the match with the numbers and thus the number.
            // create a person_t struct with this info
            person_t person{ match[1], match[2] };
    
            // and add it to the vector
            persons.push_back(person);
            cbegin = match.suffix().first;
        }
    
        return persons;
    }
    
    int main()
    {
        // parse and split the string
        auto persons = get_persons("JACK1940383DAVID30284HAROLD68372TROY4392");
    
        // show the output
        for (const auto& person : persons)
        {
            std::cout << person;
        }
    }
    

    【讨论】:

    • 好吧,让我添加一些 cmets ;)
    • 另外,prefer '\n' over std::endl。后者等同于前者,但会强制刷新输出,这通常是不需要的,并且可能会损害性能。
    • @UnholySheep 由于运行时正则表达式编译开销,我将正则表达式设为静态。但比赛不需要。所以修好了
    【解决方案3】:

    正如您可以使用的其他好的答案所指出的那样

    • find_first_of()find_first_not_of()substr() 来自 std::string 循环中
    • regex

    但这可能太多了。我将添加 3 个您可能会发现的示例 更简单。

    为了方便(我的)这里,前 2 个程序需要命令行上的文件名,测试文件是 in.txt。内容跟贴出来的一样

    JACK1940383DAVID30284HAROLD68372TROY4392
    

    最后一个例子只是解析声明为char[]的字符串数据

    1。使用fscanf()

    由于目标是使用格式化数据,fscanf() 是一个选项。由于数据结构很简单,所以程序就是一行循环:

        char  mask[] = "%50[^0-9]%50[0-9]";
        while ( 2 == fscanf(F, mask, tk_key, tk_value))
            std::cout << tk_key << "/" << tk_value << "\n";
    

    程序输出

    所有示例的输出都相同

    JACK/1940383
    DAVID/30284
    HAROLD/68372
    TROY/4392
    

    ex 的代码。 1

    #include <errno.h>
    #include <iostream>
    int main(int argc,char** argv)
    {
        if (argc < 2)
        {   std::cerr << "Use: pgm FileName\n";
            return -1;
        }
        FILE* F = fopen(argv[1], "r");
        if (F == NULL)
        {
            perror("Could not open file");
            return -1;
        }
        std::cerr << "File: \"" << argv[1] << "\"\n";
        char  tk_key[50], tk_value[50];
        char  mask[] = "%50[^0-9]%50[0-9]";
        while ( 2 == fscanf(F, mask, tk_key, tk_value))
            std::cout << tk_key << "/" << tk_value << "\n";
        fclose(F);
        return 0;
    }
    

    使用状态机

    只有 2 个状态,所以它不是一个花哨的 FSA ;) 状态机很适合表示这种东西,尽管在这里这似乎有点过头了。

    #define S_LETTER 0
    #define S_DIGIT 1
    #include <algorithm>
    #include <iostream>
    #include <fstream>
        using iich = std::istream_iterator<char>;
    
    int main(int argc,char** argv)
    {
        std::ifstream in_file{argv[1]};
        if ( not in_file.good()) return -1;
        iich p {in_file}, eofile{};
        std::string token{}; // string to build values
        char        st = S_LETTER; // state value for FSA
        std::for_each(p, eofile,
            [&token,&st](char ch)
            {
                char temp = 0;
                switch (st)
                {
                    case S_LETTER:
                        if ((ch >= '0') && (ch <= '9'))
                        {
                            std::cout << token << "/";
                            token = ch;
                            st    = S_DIGIT;  // now in number
                        }
                        else token += ch;  // concat in string
                        break;
    
                    case S_DIGIT:
                    default:
    
                        if ((ch < '0') || (ch > '9'))
                        {  // is a letter
                            std::cout << token << "\n";
                            token = ch;
                            st    = S_LETTER;  // now in name
                        }
                        else token += ch;  // concat in string
                        break;
                };  // switch()
            });
        std::cout << token << "\n";  // print last token
    }
    

    这里我们没有循环。 for_each 从迭代器中获取数据并将其传递给一个函数,该函数将名称和值构建为字符串和couts

    输出是一样的

    3。一个简单的 FSA 来消费数据

    #define     S_LETTER 0
    #define     S_DIGIT  1
    #include <iostream>
    
    int main(void)
    {
        char one[] = "JACK1940383DAVID30284HAROLD68372TROY4392";
        char*       p     = (char*)&one;
        char*       token = p;
        char        st    = S_LETTER;
        char        temp  = 0;
        while (*p != 0)
        {
            switch (st)
            {
                case S_LETTER:
                    if ((*p >= '0') && (*p <= '9'))
                    {
                        temp = *p;
                        *p   = 0;
                        std::cout << token << "/";
                        *p    = temp;
                        token = p;
                        st    = S_DIGIT;  // now in number
                    }
                    break;
    
                case S_DIGIT:
                default:
                    if ( (*p < '0') || (*p > '9'))
                    {   // letter
                        temp = *p;
                        *p   = 0;
                        std::cout << token << "\n";
                        *p    = temp;
                        token = p;
                        st    = S_LETTER;  // now in name
                    }
                    break;
            };  // switch()
            p += 1; // next symbol
        };  // while()
        std::cout << token << "\n"; // print last token
    }
    

    这段代码只是使用了一个 C 风格的循环来解析输入数据

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-09-15
      • 2021-06-15
      • 1970-01-01
      • 2021-07-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多