{ }codelper
声明变量作用域常量P0087

块与被遮蔽的变量

依次执行用一种很小的语言写的 n 行程序。
开始时只有最外层的块是打开的。
每行是下列之一: - {:打开一个新块(在当前块里面)。
- }:关闭最内层的块。
该块中声明的名字全部消失,被它遮住的外层名字重新可见。
- let x v:在当前块中声明变量 x,值为 v。
若外层块有同名的,则在本块关闭前遮住它。
- const x v:和 let 相同,但 x 是常量。
- set x v:把 v 存进当前可见的 x(最内层拥有 x 的块中的那个)。
若那个 x 是常量,则什么都不变。
不会创建新名字。
- print x:在一行中输出当前可见的 x 的值。
除了 print 之外都不输出(一个 print 都没有时输出为空)。

输入
第一行 n(1 ≤ n ≤ 1000),接下来 n 行是命令。
名字为 1~10 个小写英文字母,-10^9 ≤ v ≤ 10^9。
最外层块不会被关闭,同时打开的块不超过 100 个。
同一块中不会两次声明同一名字,set 和 print 的名字总是可见的。
输出
每个 print 一行
样例 1 · stdin
11
let x 1
const y 2
{
let x 10
set y 5
set x 11
print x
print y
}
print x
set x -3
stdout
11
2
1
main.py
输入(stdin)

运行时这些行会依次作为 input()/标准输入传入(运行中不会等待输入)。

$ 就绪

请在电脑上打开以编写和运行代码。在手机上可以查看题目和题解。

其他语言怎么写?查看各语言题解 · 8 种语言题解

块作用域因语言而异。
JS/TS 的 let/const 和 C/C++ 的局部变量像本题一样每个块产生新名字,内层可以用同名遮住外层(JS 旧的 var 无视块,整个函数只有一个)。
Java 和 C# 在内层块中重新声明一个仍然存活的同名局部变量会编译出错(Java "already defined",C# CS0136)。
Python 的 if、for 不产生新作用域,只有函数才会。
所以无视块、只用一个字典处理的话,内层的 let x 10 会覆盖外层的 x。
Craykon 中在块里第一次创建的变量在块外不可见,但给外面已有的名字赋值会改到外层变量(没有单独的遮蔽声明)。
常量也不同:真正的语言会阻止对常量再赋值(JS 运行时 TypeError,TS 报 TS2588,Java final、C# const、C/C++ const 编译出错,Python 不阻止,Craykon 没有常量)。
本题中则静默忽略。
解法是每个块压一个字典的栈(Python dict、JS/TS Map、Java HashMap、C# Dictionary、C++ unordered_map、Craykon 字典())。
C 没有字典,就把声明连同块深度压进数组,在 } 时去掉。
陷阱:查找时要从最内层(栈尾)开始。
从外层开始找、在 } 时不去掉该块的名字,或者把 set 当成“在当前块新放一个”(Python 函数里的赋值就是这样),块关闭后的值都会出错。

Pythonmain.py · 25 行
# 블록마다 사전 하나: 이름 → [값, 상수인가]. 맨 끝이 지금 블록scopes = [{}]  def find(name):    for scope in reversed(scopes):  # 안쪽 블록부터        if name in scope:            return scope[name]  n = int(input())for _ in range(n):    cmd, *rest = input().split()    if cmd == "{":        scopes.append({})    elif cmd == "}":        scopes.pop()    elif cmd in ("let", "const"):        scopes[-1][rest[0]] = [int(rest[1]), cmd == "const"]    elif cmd == "set":        var = find(rest[0])        if not var[1]:            var[0] = int(rest[1])    else:        print(find(rest[0])[0])
JavaScriptmain.js · 24 行
const lines = require("fs").readFileSync(0, "utf8").split("\n"); // 블록마다 Map 하나: 이름 → { value, isConst }. 맨 끝이 지금 블록const scopes = [new Map()]; function find(name) {  for (let i = scopes.length - 1; i >= 0; i--) {    if (scopes[i].has(name)) return scopes[i].get(name);  }} const n = Number(lines[0]);const out = [];for (let i = 1; i <= n; i++) {  const [cmd, name, v] = lines[i].trim().split(" ");  if (cmd === "{") scopes.push(new Map());  else if (cmd === "}") scopes.pop();  else if (cmd === "let" || cmd === "const") scopes.at(-1).set(name, { value: Number(v), isConst: cmd === "const" });  else if (cmd === "set") {    const entry = find(name);    if (!entry.isConst) entry.value = Number(v);  } else out.push(find(name).value);}console.log(out.join("\n"));
TypeScriptmain.ts · 32 行
import { readFileSync } from "node:fs";const lines: string[] = readFileSync(0, "utf8").split("\n"); interface Entry {  value: number;  readonly isConst: boolean;} // 블록마다 Map 하나. 맨 끝이 지금 블록const scopes: Map<string, Entry>[] = [new Map()]; function find(name: string): Entry {  for (let i = scopes.length - 1; i >= 0; i--) {    const entry = scopes[i].get(name);    if (entry !== undefined) return entry;  }  throw new Error(`${name} is not declared`); // 문제에서 일어나지 않는다} const n: number = Number(lines[0]);const out: number[] = [];for (let i = 1; i <= n; i++) {  const [cmd, name, v] = lines[i].trim().split(" ");  if (cmd === "{") scopes.push(new Map());  else if (cmd === "}") scopes.pop();  else if (cmd === "let" || cmd === "const") scopes[scopes.length - 1].set(name, { value: Number(v), isConst: cmd === "const" });  else if (cmd === "set") {    const entry = find(name);    if (!entry.isConst) entry.value = Number(v);  } else out.push(find(name).value);}console.log(out.join("\n"));
JavaMain.java · 51 行
import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader;import java.util.ArrayList;import java.util.HashMap;import java.util.List;import java.util.Map; public class Main {    static class Entry {        int value;        final boolean isConst;         Entry(int value, boolean isConst) {            this.value = value;            this.isConst = isConst;        }    }     // 블록마다 HashMap 하나. 맨 끝이 지금 블록    static final List<Map<String, Entry>> scopes = new ArrayList<>();     static Entry find(String name) {        for (int i = scopes.size() - 1; i >= 0; i--) {            Entry e = scopes.get(i).get(name);            if (e != null) return e;        }        return null; // 문제에서 일어나지 않는다    }     public static void main(String[] args) throws IOException {        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));        int n = Integer.parseInt(br.readLine().trim());        scopes.add(new HashMap<>());        StringBuilder out = new StringBuilder();        for (int i = 0; i < n; i++) {            String[] p = br.readLine().trim().split(" ");            switch (p[0]) {                case "{" -> scopes.add(new HashMap<>());                case "}" -> scopes.remove(scopes.size() - 1);                case "let", "const" -> scopes.get(scopes.size() - 1).put(p[1], new Entry(Integer.parseInt(p[2]), p[0].equals("const")));                case "set" -> {                    Entry e = find(p[1]);                    if (!e.isConst) e.value = Integer.parseInt(p[2]);                }                default -> out.append(find(p[1]).value).append('\n');            }        }        System.out.print(out);    }}
C#Main.cs · 46 行
using System;using System.Collections.Generic;using System.Text; class Entry{    public int Value;    public readonly bool IsConst;    public Entry(int value, bool isConst) { Value = value; IsConst = isConst; }} class Program{    // 블록마다 Dictionary 하나. 맨 끝이 지금 블록    static readonly List<Dictionary<string, Entry>> scopes = new() { new() };     static Entry Find(string name)    {        for (int i = scopes.Count - 1; i >= 0; i--)            if (scopes[i].TryGetValue(name, out Entry e)) return e;        return null; // 문제에서 일어나지 않는다    }     static void Main()    {        int n = int.Parse(Console.ReadLine());        var sb = new StringBuilder();        for (int i = 0; i < n; i++)        {            string[] p = Console.ReadLine().Trim().Split(' ');            switch (p[0])            {                case "{": scopes.Add(new()); break;                case "}": scopes.RemoveAt(scopes.Count - 1); break;                case "let":                case "const": scopes[^1][p[1]] = new Entry(int.Parse(p[2]), p[0] == "const"); break;                case "set":                    Entry e = Find(p[1]);                    if (!e.IsConst) e.Value = int.Parse(p[2]);                    break;                default: sb.Append(Find(p[1]).Value).Append('\n'); break;            }        }        Console.Write(sb);    }}
C++main.cpp · 45 行
#include <iostream>#include <string>#include <unordered_map>#include <vector> struct Var {    int value;    bool isConst;}; // 블록마다 사전 하나. back()이 지금 블록std::vector<std::unordered_map<std::string, Var>> scopes(1); Var* find(const std::string& name) {    for (auto it = scopes.rbegin(); it != scopes.rend(); ++it) {        auto found = it->find(name);        if (found != it->end()) return &found->second;    }    return nullptr;  // 문제에서 일어나지 않는다} int main() {    int n;    std::cin >> n;    for (int i = 0; i < n; i++) {        std::string cmd, name;        int v;        std::cin >> cmd;        if (cmd == "{") {            scopes.emplace_back();        } else if (cmd == "}") {            scopes.pop_back();        } else if (cmd == "let" || cmd == "const") {            std::cin >> name >> v;            scopes.back()[name] = Var{v, cmd == "const"};        } else if (cmd == "set") {            std::cin >> name >> v;            Var* p = find(name);            if (!p->isConst) p->value = v;        } else {            std::cin >> name;            std::cout << find(name)->value << '\n';        }    }}
Cmain.c · 51 行
#include <stdio.h>#include <string.h> /* C에는 사전이 없다: 선언을 차례로 쌓고, 블록이 닫히면 그 블록 것만 걷어 낸다 */struct Var {    char name[11];    int value;    int is_const;    int depth;}; static struct Var vars[1000];static int count = 0; /* 맨 뒤(가장 안쪽)부터 찾는다 */static struct Var *find(const char *name) {    for (int i = count - 1; i >= 0; i--)        if (strcmp(vars[i].name, name) == 0) return &vars[i];    return NULL; /* 문제에서 일어나지 않는다 */} int main(void) {    int n, depth = 0;    char cmd[8], name[11];    int v;    scanf("%d", &n);    for (int i = 0; i < n; i++) {        scanf("%7s", cmd);        if (strcmp(cmd, "{") == 0) {            depth++;        } else if (strcmp(cmd, "}") == 0) {            while (count > 0 && vars[count - 1].depth == depth) count--;            depth--;        } else if (strcmp(cmd, "let") == 0 || strcmp(cmd, "const") == 0) {            scanf("%10s %d", name, &v);            struct Var *p = &vars[count++];            strcpy(p->name, name);            p->value = v;            p->is_const = cmd[0] == 'c';            p->depth = depth;        } else if (strcmp(cmd, "set") == 0) {            scanf("%10s %d", name, &v);            struct Var *p = find(name);            if (!p->is_const) p->value = v;        } else {            scanf("%10s", name);            printf("%d\n", find(name)->value);        }    }    return 0;}
Craykonmain.cray · 49 行
// 블록마다 사전 두 개: 값과 '상수인가'. 맨 끝이 지금 블록이다값들 = [字典()];상수들 = [字典()]; // 이름을 가진 가장 안쪽 블록의 번호(없으면 -1)찾기(이름){    循环 (j = 값들.数量 - 1; j >= 0; j--)    {        如果 (값들[j].包含(이름))        {            返回 j;        }    }    返回 -1;} n = 控制台.读行().转数字();循环 (i = 0; i < n; i++){    parts = 控制台.读行().分割();    cmd = parts[0];    选择 (cmd)    {        情况 "{":            값들.添加(字典());            상수들.添加(字典());            跳出;        情况 "}":            값들.按位置移除(값들.数量 - 1);            상수들.按位置移除(상수들.数量 - 1);            跳出;        情况 "let":        情况 "const":            값들[값들.数量 - 1][parts[1]] = parts[2].转数字();            상수들[상수들.数量 - 1][parts[1]] = cmd == "const";            跳出;        情况 "set":            b = 찾기(parts[1]);            如果 (!상수들[b][parts[1]])            {                값들[b][parts[1]] = parts[2].转数字();            }            跳出;        默认:            控制台.输出(값들[찾기(parts[1])][parts[1]]);            跳出;    }}