{ }codelper
変数の宣言スコープ定数P0087

ブロックと隠された変数

とても小さな言語で書いた n 行のプログラムを順に実行せよ。
最初は一番外側のブロック一つだけが開いている。
各行は次のいずれかである。
- {:新しいブロックを開く(いまのブロックの内側)。
- }:一番内側のブロックを閉じる。
そのブロックで宣言した名前はすべて消え、それが隠していた外側の名前がまた見えるようになる。
- let x v:いまのブロックに変数 x を値 v で宣言する。
外側のブロックに同じ名前があれば、このブロックが閉じるまでそれを隠す。
- const x v:let と同じだが x は定数である。
- set x v:いま見えている x(一番内側のブロックの x)に v を入れる。
その x が定数なら何も変わらない。
新しい名前は作らない。
- print x:いま見えている x の値を一行に出力する。
print 以外は何も出力しない(print が一つもなければ出力もない)。

入力
1 行目に n(1 ≤ n ≤ 1000)、続く n 行に命令。
名前は英小文字 1〜10 文字、-10^9 ≤ v ≤ 10^9。
一番外側のブロックは閉じられず、開いているブロックは 100 個を超えない。
同じブロックで同じ名前を二回宣言することはなく、set と print の名前はいつも見えている名前である。
出力
print ごとに 1 行
例 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]]);            抜ける;    }}