第37次CCF第三题--模板展开--stringstream读取字符串

1 a hello
1 b world
2 c $a $b
1 d good $c
1 a hi
1 e good $c

 

1 a hello
1 b world
2 c $a $b
3 c
1 a hi
3 c

将会输出:10 和 7,对应的变量的值为:

helloworld
hiworld

需要注意的是,在使用间接赋值语句时,在变量的值之间建立了依赖关系,在上述模板中,变量 c 的值依赖于变量 a 和 b 的值。 可以想见,如果变量的值间的依赖关系形成了环,那么模板将无法执行。我们约定,给定的表达式中不存在这样的环。

形式化地,模板语言用 BNF 表示如下:

CHAR ::= [a-z0-9]
SPACE ::= ' '
DOLLAR ::= '$'
NONEMPTY_STRING ::= CHAR | NONEMPTY_STRING CHAR
STRING ::= '' | NONEMPTY_STRING
VAR ::= NONEMPTY_STRING
OPERAND ::= DOLLAR VAR | NONEMPTY_STRING
EXPR ::= OPERAND | EXPR SPACE OPERAND
ASSIGN_OP ::= '1' | '2'
ASSIGN ::= ASSIGN_OP SPACE VAR SPACE EXPR
OUTPUT ::= '3' SPACE VAR
STATEMENT ::= ASSIGN | OUTPUT

输入格式

从标准输入读入数据。

输入的第一行为一个整数 nn,表示模板语言的语句数量。接下来的 nn 行,每行为一个语句,语句的格式如上所述。

输出格式

输出到标准输出。

依次执行输入的语句:如果语句为输出语句,则相应输出一行变量的值的长度除以 1000000007 的余数。

样例1输入

 

6
1 a hello
1 b world
2 c $a $b
3 c
1 a hi
3 c

样例1输出

10
7

样例1解释

本样例即为题目描述中的例子。

样例2输入

5
1 var value
3 var
3 val
1 var $var $val $var
3 var

样例2输出

5
0
10

样例2解释

执行第一条语句后,变量 var 的值为 value,因此第二条语句输出 5;执行第三条语句时,变量 val 并未被赋值,因此其值为初始的空字符串,输出 0;执行第四条语句后,变量 var 的值为 valuevalue,因此第五条语句输出 10

 

子任务

测试点nn语句类型表达式的性质
1, 2≤200≤200无间接赋值语句每个表达式仅有1个字符串操作数
3, 4每个表达式仅有1个操作数
5, 6每个表达式包含不超过100个操作数
7, 8包含所有类型语句
9, 10≤2000≤2000

全部的数据满足:变量的总数不超过 1000 个,且每个变量的名称、字符串的长度不超过 50 个字符。

 解答

 题目描述很长,是一道字符串题,难度方面,我觉得要是把stringstream和map结合起来用就是一道中等难度或者送分题,废话不多说,直接开整:

题目大意:

输入n行字符串作为表达式,且表达式第一个字符代表着操作类型

第一个字符为1:直接赋值表达式

格式:1 + 变量名 + 一些操作数(空格分开)

如样例2:1  var  $var $val $var  就是var赋值三个操作数,分别是$var,$val,$var

其中$符号代表该操作数是一个变量,需要根据变量名(不含$)找到它的存的字符串

如果该变量不存在,则缺省为空字符串

第一个字符为2:动态赋值表达式

如样例1中:2 c $a $b

这里c是赋值为$a $b,可知是变量a和变量b储存的字符串相加,但是不能直接把a和b储存的字符串赋值给c,因为c是动态赋值,c需要随着a和b的改变而改变,所以我们想到把c赋值为$a $b,等到需要用c的时候再用一个函数读取变量a和变量b。

第一个字符串为3:输出表达式

5
1 var value
3 var
3 val
1 var $var $val $var
3 var

输出表达式格式:3 + 变量名

根据变量名输出该变量储存的字符串长度

如样例2:

先把value赋值给var

输出变量var字符串长度为5

然后输出变量val长度,因为val不存在,故缺省为空字符串,输出0

把var赋值为$var $val $var,也就是valuevalue,输出10

思路大致就是这样,解决这个问题用 stringstream 很合适,很舒服,因为这题的输入有大量包含空格的字符串,而stringstream可以按照空格分隔读取字符串

1. stringstream 是什么?

stringstream 是 C++ 中 <sstream> 头文件提供的类,用于在字符串中进行类似 cin/cout 的格式化输入输出操作。stringstream 常用于一次性解析一行中的多个字段。

2. 使用 stringstream 读取字符串的方法

方法一:使用 >> 按空格分割读取,这是最常用的方式,适用于将字符串按空格分词读入多个变量:

#include <iostream>
#include <sstream>
#include <string>
using namespace std;

int main() {
    string input = "hello world 123";
    stringstream ss(input);
    string word1, word2;
    int num;

    ss >> word1 >> word2 >> num;

    cout << "Word1: " << word1 << endl; // hello
    cout << "Word2: " << word2 << endl; // world
    cout << "Num: " << num << endl;     // 123
}

方法二:使用 getline() 读取整行或指定分隔符的内容

如果你想以某个特定符号作为分隔符,比如逗号 , 或冒号 :,可以这样写:

#include <iostream>
#include <sstream>
#include <string>
using namespace std;

int main() {
    string input = "apple,banana,orange";
    stringstream ss(input);
    string token;

    while (getline(ss, token, ',')) {
        cout << token << endl;
    }
}

总结:stringstream 读取字符串方法对比

方法

用途

示例

ss >> var

按空格/换行/Tab 分隔读取

ss >> a >> b;

getline(ss, str, delim)

按指定分隔符读取整段字符串

getline(ss, token, ',');

特点1:stringstream 可以控制读取变量的个数

它不会自动读取所有内容,而是根据你写多少次 >> 来决定读几个变量。所以你可以通过控制使用 >> 的次数来精确地控制从 stringstream 中读取多少个变量。

#include <iostream>
#include <sstream>
#include <string>

int main() {
    std::string line = "apple banana cherry date";
    std::stringstream ss(line);

    std::string a, b, c;

    // 仅读取前两个变量
    if (ss >> a >> b) {
        std::cout << "a: " << a << "\n";  // 输出: apple
        std::cout << "b: " << b << "\n";  // 输出: banana
    }

    // 再读一个(第三个)
    if (ss >> c) {
        std::cout << "c: " << c << "\n";  // 输出: cherry
    }

    // 剩下的部分可以继续读,也可以忽略
}

在这个例子中:

  • 我们只读了三个变量 (a, b, c),即使字符串中有四个单词;
  • 第四个单词 "date" 没有被读取。

 控制方式总结

方法

说明

ss >> a >> b;

明确读取两个变量

使用if (ss >> var)判断是否成功读取

避免越界或格式错误

结合while (ss >> word)

可以循环读取全部变量

如果你想读取不确定数量的变量

比如一行中有多个空格分隔的单词,你可以用循环:

#include <iostream>
#include <sstream>
#include <string>
#include <vector>

int main() {
    std::string line = "one two three four five";
    std::stringstream ss(line);
    std::vector<std::string> words;
    std::string word;

    while (ss >> word) {
        words.push_back(word);
    }

    std::cout << "Total words: " << words.size() << "\n";
    for (const auto& w : words)
        std::cout << w << "\n";
}
  • >> 会跳过空白字符(空格、换行、Tab);
  • 如果你希望保留空格,应该使用 getline()
  • 如果输入格式不一致,建议加判断防止程序崩溃;
  • 多余的数据不会报错,只是留在流里未被读取;

 

特点二:接着读

stringstream 在读取时会维护一个内部的位置指针(读指针) ,如果你没有一次性把内容全部读完,那么下一次读取的时候,它会从上次结束的位置继续读取


🧠 类比理解

你可以把 stringstream 想象成一个“文件流”或者“输入流”,就像你读一个文件一样,有一个“当前读到哪了”的概念。

#include <iostream>
#include <sstream>
#include <string>

int main() {
    std::string line = "apple banana cherry date";
    std::stringstream ss(line);

    std::string a, b;

    // 第一次读两个单词
    ss >> a >> b;
    std::cout << "First read: " << a << " " << b << "\n";  // apple banana

    std::string c, d;

    // 再次读两个单词
    ss >> c >> d;
    std::cout << "Second read: " << c << " " << d << "\n";  // cherry date
}

输出 

First read: apple banana
Second read: cherry date

补充知识点:如何重置位置指针?

如果你想让 stringstream 重新从开头读一遍 ,可以使用 .seekg() 方法:

std::stringstream ss("hello world");

std::string s1, s2;
ss >> s1 >> s2;  // s1=hello, s2=world

ss.seekg(0);     // 回到开头

std::string s3, s4;
ss >> s3 >> s4;  // s3=hello, s4=world
  • stringstream 的读写行为和 cin 类似,不是像字符串那样可以反复访问;

总结

问题

回答

stringstream是否记住上一次读到哪里?

✅ 是的,它维护了一个读指针

没读完再次读取会不会从头开始?

❌ 不会,会接着上次的位置继续读

如何让它从头读?

使用ss.seekg(0)

如何清空整个 stringstream?

可以用

ss.str(""); ss.clear();

进阶(可跳过,不用这个用substr截取字符串也行)

如果你想获取 还未被读取的部分字符串 ,可以用下面这种方法: 

#include <iostream>
#include <sstream>
#include <string>

int main() {
    std::stringstream ss("hello world this is a test");

    std::string a, b;
    ss >> a >> b;  // 已经读了 "hello" 和 "world"

    // 获取剩余部分
    std::string rest;
    std::getline(ss, rest);  // 从当前指针位置读到结尾

    std::cout << "Remaining string: " << rest << std::endl;
    // 输出: " this is a test"
}

std::getline(ss, rest); 会从当前位置开始读到结尾,并且自动跳过前面的空白字符 (如空格、Tab)。如果你不希望它跳过空白,可以这样写:

std::string rest(std::istreambuf_iterator<char>(ss),
                 std::istreambuf_iterator<char>());

 

map<string, string> mp 的作用详解

✅ 它是一个存储变量名和对应值的容器

map<string, string> mp;

 map<key,value>mp

  • mp 用于保存用户定义的所有变量名及其对应的字符串值。
  • 键(key)是变量名(string 类型)
  • 值(value)是变量的内容(string 类型)

 本题变量名是字符串,储存的值也是字符串,用map建立变量名到储存的实际字符串的映射效果很好,可以用 mp[a] 直接访问变量a储存的字符串

总结:map<string, string> 的好处一览

优势

描述

✅ 自动排序(这里没用到)

键值有序,方便遍历和调试

✅ 支持默认访问

mp[key]用键可以直接访问值value

✅ 无哈希冲突

更加理论安全

✅ 小数据高效

对少量变量完全胜任

代码一览 

#include <bits/stdc++.h>
using namespace std;
map<string, string> mp;
string f(string s)
{
    if (s[0] == '$')
        s.erase(0, 1);
    else
        return s;
    if (mp.find(s) != mp.end())
        return mp[s];
    else
        return "!NOT  FOUND!";
}
int main()
{
    int n;
    cin >> n;
    cin.ignore();
    for (int i = 0; i < n; i++)
    {
        string s;
        getline(cin, s);
        // 直接赋值变量
        if (s[0] == '1')
        {
            s.erase(0, 2);
            stringstream ss(s);
            string a, b, word;
            ss >> a;
            while (ss >> word)
            {
                b += f(word);
                // cout << b << endl;
            }
            mp[a] = b;
            // cout << "1:" << a << " " << b << endl;
        }
        if (s[0] == '2')
        {
            s.erase(0, 2);
            stringstream ss(s);
            string a, b;
            ss >> a;

            getline(ss, b);
            b.erase(0, 1);
            mp[a] = b;
            // cout << "2:" << a << b << endl;
        }
        if (s[0] == '3')
        {
            s.erase(0, 2);
            if (mp.count(s))
            {
                stringstream ss(mp[s]);
                string ans, word;
                while (ss >> word)
                {
                    ans += f(word);
                }
                cout << ans.size() % 1000000007 << endl;
                // cout << ans << endl;
            }
            else
            {
                mp[s] = "";
                cout << 0 << endl;
            }
        }
    }
}

 优化:

使用unordered_map<string,string>不需要排序,节省时间 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值