【问题标题】:Parsing doubles and words in a string解析字符串中的双精度和单词
【发布时间】:2020-05-16 18:03:02
【问题描述】:

我在 C++ 编程语言中进行以下练习:

读取一系列可能以空格分隔的(名称,值)对, 其中名称是单个空格分隔的单词,值是 整数或浮点值。计算并打印总和 每个名称的平均值以及所有名称的总和和平均值。

例如,给定:

hello world5.678popcorn 8.123 rock 123 hello world 8.761 popcorn 98 rock 1.9rock2.3

我的实现的输出是:

rock: Sum (127.2), Mean (42.4)
hello world: Sum (14.439), Mean (7.2195)
popcorn: Sum (106.123), Mean (53.0615)

我的实现:

#include <iostream>
#include <string>
#include <unordered_map>

std::unordered_map<std::string, double> pairs;
std::unordered_map<std::string, int> occurences;

void save(const std::string& name, const std::string& value);
void trim(std::string& s, const std::string& chars = " ");

int main() {
    std::string line;
    getline(std::cin, line);

    std::string name;
    std::string value;

    bool name_saved;
    for(char c : line) {
        if(!name_saved && isdigit(c)) { // reached end of name
            name_saved = true;
            trim(name);
            value += c;
        } else if(!name_saved) { // add char to name
            name += c;
        } else if(name_saved) {
            if(isdigit(c) || (c == '.' && (value.find_first_of(".") == std::string::npos))) { // add char to value
                value += c;
            } else { // reached end of value
                trim(value);
                save(name, value);
                name = "";
                value = "";
                name_saved = false;

                if(isalpha(c)) {
                    name += c;
                }
            }
        }
    }

    if(value != "") {
        save(name, value);
    }

    std::cout << "\n";
    for(auto pair : pairs) {
        std::cout << pair.first << ": " << "Sum (" << pair.second << "), Mean (" << (pair.second / occurences[pair.first]) << ")\n";
    }

    return 0;
}

void save(const std::string& name, const std::string& value) {
    pairs[name] += std::stod(value);
    occurences[name]++;
}

void trim(std::string& s, const std::string& chars) {
    s.erase(0, s.find_first_not_of(chars));
    s.erase(s.find_last_not_of(chars) + 1);
}

我想知道这个练习更有效的方法是什么?我觉得我的代码很乱,我想就我可以用什么来清理它并使它更紧凑一些输入。

【问题讨论】:

  • 你知道std::istringstream怎么用吗?
  • @SamVarshavchik 不,我没有。我会调查一下谢谢。
  • @SamVarshavchik:用于提取名称和值的```std::istringstream``` 将不起作用,因为名称和值之间有时没有空格。或者,我误解了你的想法,你的意思是不同的方法。

标签: c++ string input


【解决方案1】:

有许多不同的灵魂。这在一定程度上取决于您的个人编程风格以及您是否已经学过。

上面的例子呼唤“正则表达式”。您可以在 Cpp- 参考中阅读 here 关于它们的信息。尤其是函数std::regex_search 将成为你的朋友。

首先是正则表达式。您正在寻找带有嵌入空格的“文本”(“单个空格分隔的单词”),后跟一个 int 或 float。这可以用“正则表达式”轻松表达:

([a-zA-Z]+ ?[a-zA-Z]+) ?(\d+\.?\d*)

所以,首先我们有 1 个或多个字母字符,然后是一个可选空格,然后又是 1 个或多个字母字符。这构成了名称。

对于值,我们有 1 个或多个数字,可能后跟一个“。”可能还有更多数字。

如果您将正则表达式和测试字符串粘贴到一些在线正则表达式测试器中,例如this,您会得到更好的理解。

它会给你一些更详细的解释。特别是括号“()”,它形成组。

可以在std::regex_search 找到匹配项后提取组。含义:如果std::regex_search 将找到给定正则表达式的匹配项,它将返回true,并且可以在std::smatch 中找到结果组,有关说明,请参见here

有了这一切,我们可以定义一个简单的for 循环来从测试字符串中获取所有名称和值。

for (std::string s{ test }; std::regex_search(s, sm, re); s = sm.suffix())

首先我们将初始化“loop-run”变量,在本例中为std::string,并使用给定的测试字符串对其进行初始化。然后我们将在测试字符串中搜索匹配项。如果有匹配,那么我们将在sm[1]sm[2] 中找到结果。在循环体内完成所有操作后,我们将“loop-run”变量设置为尚未匹配的测试字符串的其余部分。后缀。

要汇总、计算和汇总这些值,我们使用std::map。键是“名称”,值是 std::pair,由名称的计数和相关值的总和组成。所以,在循环中:

    for (std::string s{ test }; std::regex_search(s, sm, re); s = sm.suffix()) {

        // Count the occurences of a text
        aggregator[sm[1]].first++;

        // Sum up the values for a text
        aggregator[sm[1]].second += std::stod(sm[2]);
    }

我们使用aggregator[sm[1]] 在地图中创建名称或从地图中检索名称。在任何情况下,我们都有一个对当前名称条目的引用,我们可以增加计数并构建运行总和。

这是一个非常简单的 3 行方法,它已经完成了几乎所有预期的工作。

剩下的就是对预期总和和平均值的简单计算,并在屏幕上显示所有内容。

请看下面的完整代码:

#include <iostream>
#include <string>
#include <regex>
#include <vector>
#include <iterator>
#include <map>

// The regex for words with embedded space and floats/ints
const std::regex re{ R"(([a-zA-Z]+ ?[a-zA-Z]+) ?(\d+\.?\d*))" };

int main() {

    // Definition Section --------------------------------------------------------------------------------
    // The input test string
    std::string test{"hello world5.678popcorn 8.123 rock 123 hello world 8.761 popcorn 98 rock 1.9rock2.3"};

    // Here we will store the result. The text and the associated "count" and "sum"
    std::map<std::string, std::pair<unsigned int, double>> aggregator{};
    std::smatch sm;


    // Find, store  and calculate data -------------------------------------------------------------------
    // Iterate though the string and get the text and the float value
    for (std::string s{ test }; std::regex_search(s, sm, re); s = sm.suffix()) {

        // Count the occurences of a text
        aggregator[sm[1]].first++;

        // Sum up the values for a text
        aggregator[sm[1]].second += std::stod(sm[2]);
    }


    // Output data ----------------------------------------------------------------------------------------
    // Since the task is to calculate also the overall results, we will do 
    unsigned int countOverall{};
    double sumOverall{};

    // Iterate over the "text" data and output sum and mean value per text and aggregate the overall values
    for (const auto& [text, agg] : aggregator) { 

        // Output sum and mean per text
        std::cout << "\n" << text << ": Sum (" << agg.second << "), Mean (" << agg.second / agg.first << ")";

        // Aggregate overall values
        countOverall += agg.first;
        sumOverall += agg.second;
    }

    // Show overall result to the user.
    std::cout << "\n\nSum overall: (" << sumOverall << "), Mean overall: (" << sumOverall / countOverall << ")\n\n";
    return 0;
}

这个解决方案是否“更好”?请自行决定。 . .

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-10-21
    • 2010-11-27
    • 1970-01-01
    • 2012-03-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多