{ }codelper
재귀변수 범위P0183

겹친 블록에서 이름 찾기

작은 언어로 쓴 프로그램이 주어진다. 프로그램은 빈칸으로 나뉜 토큰들이고, 토큰은 네 가지뿐이다: 블록을 여는 {, 블록을 닫는 }, 넣기 이름=값, 질문 ?이름. 프로그램 전체도 하나의 블록(가장 바깥 블록)이고, { 와 } 는 늘 짝이 맞는다. 토큰은 앞에서부터 한 번씩 차례로 실행되고, 안쪽 블록은 그 { 가 있는 자리에서 실행된다. 어떤 블록 안에(그 안의 안쪽 블록은 빼고) 이름=값 이 하나라도 있으면, 그 이름은 그 블록 전체의 것이다.
넣기가 질문보다 뒤에 있어도 같다. 질문 ?이름 은 그 질문을 바로 감싼 블록부터 바깥쪽으로 가며, 그 이름을 가진 블록을 처음 만날 때까지 찾는다. 그런 블록이 없으면 "없는 이름"를 출력한다. 찾은 블록이 그 이름에 아직 한 번도 값을 넣지 않았으면 "아직 값 없음"를 출력한다. 그렇지 않으면 그 질문 전까지 그 블록이 그 이름에 마지막으로 넣은 값을 출력한다. 블록이 끝나면 그 블록의 이름과 값은 사라지고, 다른 블록에는 영향을 주지 않는다. 질문마다 한 줄씩 차례로 출력하라.

입력
여러 줄에 걸쳐 토큰이 빈칸 하나로 나뉘어 주어진다(줄 끝도 토큰의 끝이다).
토큰은 10,000개 이하, 블록이 겹치는 깊이는 바깥 블록을 빼고 1,000 이하, 질문은 하나 이상.
이름은 1~10글자 영어 소문자, 값은 0 이상 1,000,000,000 이하의 정수(앞에 0을 붙이지 않음).
출력
질문마다 한 줄: 값, "없는 이름", "아직 값 없음" 가운데 하나
예시 1 · stdin
x=1 ?x {
?x x=2 ?x ?y
{ ?x }
} ?x ?z
stdout
1
아직 값 없음
2
없는 이름
2
1
없는 이름
main.py
입력(stdin)

실행하면 이 줄들이 차례로 input()·표준 입력으로 들어가요(실행 중에 묻지 않아요).

$ 준비됐어요

코드를 입력해 풀려면 PC에서 열어 주세요. 모바일에서는 문제와 풀이를 볼 수 있어요.

다른 언어로는?언어별 풀이 보기 · 8개 언어 풀이

이 규칙은 Python 함수의 규칙과 같아요: 함수 안 어디에서든 넣는 이름은 함수 전체의 지역 변수라서, 넣기 전에 읽으면 바깥 값이 아니라 UnboundLocalError가 나요(JavaScript의 let도 같은 블록의 let 줄보다 먼저 읽으면 바깥 값이 아니라 ReferenceError가 나요).
풀이는 블록마다 재귀 함수 한 번을 부르고, 먼저 그 블록이 가진 이름마다 '아직 값 없음' 칸을 이름별 더미 맨 위에 올린 뒤 차례로 실행하고, 끝나면 그 칸을 걷어요.
그러면 질문은 그 이름 더미의 맨 위 칸만 보면 돼요.
이때 풀이 코드 자체의 변수 범위가 중요해요.
호출마다 따로여야 하는 것(블록 번호, 그 블록이 가진 이름 목록, 훑는 자리)은 지역 변수로, 모든 호출이 함께 쓰는 것(블록 수 세기, 출력)은 바깥에 둬요.
Python은 함수 안에서 바깥 이름에 넣으려면 global block_count가 있어야 하고, 없으면 그 이름이 지역 변수가 되어 UnboundLocalError가 나요.
JavaScript·TypeScript는 바깥 let을 클로저로 그대로 고치고, Java·C#은 static 필드, C·C++는 전역 변수로 둬요.
크레이콘은 본문 맨 바깥(함수 밖, 블록 밖)에서 만든 변수는 함수 안에서도 같은 하나라서, 함수 안에서 쓰는 k를 본문에서도 쓰면 재귀 호출이 바깥 호출의 k를 바꿔 버려요 — 함수 안에서만 쓰는 이름을 골라요.
또 블록이 1,000겹이라 재귀도 1,000단계 넘게 들어가요.
Python 기본 한도는 1,000이라 sys.setrecursionlimit를 늘려야 하고, 크레이콘은 몇천 단계까지 돼요.
함정은 ① global 빠뜨리기, ② 재귀 한도, ③ 넣는 순간에 이름을 만들기(그러면 넣기 전 질문이 "unbound" 대신 바깥 값을 내요), ④ 크레이콘에서 본문과 함수가 같은 이름 쓰기예요.

Pythonmain.py · 65 줄
import sys sys.setrecursionlimit(10_000)  # 블록이 1,000겹까지 겹친다. Python 기본 한도는 1,000 tokens = sys.stdin.read().split() # 짝 괄호 자리: match[i] = i번 '{'와 짝인 '}'의 자리match = [0] * len(tokens)opened = []for i, tok in enumerate(tokens):    if tok == "{":        opened.append(i)    elif tok == "}":        match[opened.pop()] = i # 이름마다 칸 더미: 맨 위 칸이 지금 보이는 블록의 것. 칸은 [블록 번호, 값], 값 -1은 아직 넣지 않음cells = {}block_count = 0out = []  def run(lo, hi):    """lo..hi-1 칸이 한 블록이다. 이 블록이 가진 이름을 먼저 모두 세운 뒤 차례로 실행한다."""    global block_count    block_count += 1    me = block_count    owned = []    i = lo    while i < hi:  # 1단계: 블록 안 어디에서든 넣는 이름은 블록 전체의 것        tok = tokens[i]        if tok == "{":            i = match[i] + 1            continue        if "=" in tok:            name = tok[: tok.index("=")]            stack = cells.setdefault(name, [])            if not stack or stack[-1][0] != me:                stack.append([me, -1])                owned.append(name)        i += 1    i = lo    while i < hi:  # 2단계: 차례로 실행        tok = tokens[i]        if tok == "{":            run(i + 1, match[i])            i = match[i] + 1            continue        if tok[0] == "?":            stack = cells.get(tok[1:])            if not stack:                out.append("없는 이름")            elif stack[-1][1] == -1:                out.append("아직 값 없음")            else:                out.append(str(stack[-1][1]))        else:            name, value = tok.split("=")            cells[name][-1][1] = int(value)  # 맨 위 칸은 이 블록의 것        i += 1    for name in owned:  # 블록을 나가면 이 블록의 칸을 걷는다        cells[name].pop()  run(0, len(tokens))print("\n".join(out))
JavaScriptmain.js · 58 줄
const tokens = require("fs").readFileSync(0, "utf8").split(/\s+/).filter((t) => t !== ""); // 짝 괄호 자리: match[i] = i번 '{'와 짝인 '}'의 자리const match = new Array(tokens.length).fill(0);const opened = [];tokens.forEach((tok, i) => {  if (tok === "{") opened.push(i);  else if (tok === "}") match[opened.pop()] = i;}); // 이름마다 칸 더미: 맨 위 칸이 지금 보이는 블록의 것. 칸은 { block, value }, value -1은 아직 넣지 않음const cells = new Map();let blockCount = 0; // 함수 밖에서 만든 let — 안에서 고치면 이 하나가 바뀐다const out = []; // lo..hi-1 칸이 한 블록이다. 이 블록이 가진 이름을 먼저 모두 세운 뒤 차례로 실행한다function run(lo, hi) {  const me = ++blockCount;  const owned = [];  for (let i = lo; i < hi; i++) {    // 1단계: 블록 안 어디에서든 넣는 이름은 블록 전체의 것    const tok = tokens[i];    if (tok === "{") {      i = match[i];      continue;    }    const eq = tok.indexOf("=");    if (eq < 0) continue;    const name = tok.slice(0, eq);    if (!cells.has(name)) cells.set(name, []);    const stack = cells.get(name);    if (stack.length === 0 || stack[stack.length - 1].block !== me) {      stack.push({ block: me, value: -1 });      owned.push(name);    }  }  for (let i = lo; i < hi; i++) {    // 2단계: 차례로 실행    const tok = tokens[i];    if (tok === "{") {      run(i + 1, match[i]);      i = match[i];    } else if (tok[0] === "?") {      const stack = cells.get(tok.slice(1));      if (!stack || stack.length === 0) out.push("없는 이름");      else if (stack[stack.length - 1].value === -1) out.push("아직 값 없음");      else out.push(String(stack[stack.length - 1].value));    } else {      const [name, value] = tok.split("=");      const stack = cells.get(name);      stack[stack.length - 1].value = Number(value); // 맨 위 칸은 이 블록의 것    }  }  for (const name of owned) cells.get(name).pop(); // 블록을 나가면 이 블록의 칸을 걷는다} run(0, tokens.length);console.log(out.join("\n"));
TypeScriptmain.ts · 66 줄
import { readFileSync } from "node:fs";const tokens: string[] = readFileSync(0, "utf8").split(/\s+/).filter((t) => t !== ""); // 짝 괄호 자리: match[i] = i번 '{'와 짝인 '}'의 자리const match: number[] = new Array<number>(tokens.length).fill(0);const opened: number[] = [];tokens.forEach((tok, i) => {  if (tok === "{") opened.push(i);  else if (tok === "}") match[opened.pop()!] = i;}); // 이름마다 칸 더미: 맨 위 칸이 지금 보이는 블록의 것. value -1은 아직 넣지 않음interface Cell {  block: number;  value: number;}const cells = new Map<string, Cell[]>();let blockCount = 0; // 함수 밖에서 만든 let — 안에서 고치면 이 하나가 바뀐다const out: string[] = []; // lo..hi-1 칸이 한 블록이다. 이 블록이 가진 이름을 먼저 모두 세운 뒤 차례로 실행한다function run(lo: number, hi: number): void {  const me = ++blockCount;  const owned: string[] = [];  for (let i = lo; i < hi; i++) {    // 1단계: 블록 안 어디에서든 넣는 이름은 블록 전체의 것    const tok = tokens[i];    if (tok === "{") {      i = match[i];      continue;    }    const eq = tok.indexOf("=");    if (eq < 0) continue;    const name = tok.slice(0, eq);    let stack = cells.get(name);    if (stack === undefined) {      stack = [];      cells.set(name, stack);    }    if (stack.length === 0 || stack[stack.length - 1].block !== me) {      stack.push({ block: me, value: -1 });      owned.push(name);    }  }  for (let i = lo; i < hi; i++) {    // 2단계: 차례로 실행    const tok = tokens[i];    if (tok === "{") {      run(i + 1, match[i]);      i = match[i];    } else if (tok[0] === "?") {      const stack = cells.get(tok.slice(1));      if (stack === undefined || stack.length === 0) out.push("없는 이름");      else if (stack[stack.length - 1].value === -1) out.push("아직 값 없음");      else out.push(String(stack[stack.length - 1].value));    } else {      const [name, value] = tok.split("=");      const stack = cells.get(name)!;      stack[stack.length - 1].value = Number(value); // 맨 위 칸은 이 블록의 것    }  }  for (const name of owned) cells.get(name)!.pop(); // 블록을 나가면 이 블록의 칸을 걷는다} run(0, tokens.length);console.log(out.join("\n"));
JavaMain.java · 70 줄
import java.io.*;import java.util.*; public class Main {    // 이름마다 칸 더미의 한 칸. value -1은 아직 넣지 않음    static final class Cell {        final int block;        long value = -1;         Cell(int block) {            this.block = block;        }    }     static String[] tokens;    static int[] match;    static final Map<String, ArrayDeque<Cell>> cells = new HashMap<>();    static int blockCount = 0; // static 필드: 모든 호출이 같은 하나를 본다    static final StringBuilder out = new StringBuilder();     // lo..hi-1 칸이 한 블록이다. 이 블록이 가진 이름을 먼저 모두 세운 뒤 차례로 실행한다    static void run(int lo, int hi) {        int me = ++blockCount;        List<String> owned = new ArrayList<>(); // 지역 변수: 호출마다 따로        for (int i = lo; i < hi; i++) { // 1단계: 블록 안 어디에서든 넣는 이름은 블록 전체의 것            String tok = tokens[i];            if (tok.equals("{")) {                i = match[i];                continue;            }            int eq = tok.indexOf('=');            if (eq < 0) continue;            String name = tok.substring(0, eq);            ArrayDeque<Cell> stack = cells.computeIfAbsent(name, k -> new ArrayDeque<>());            if (stack.isEmpty() || stack.peek().block != me) {                stack.push(new Cell(me));                owned.add(name);            }        }        for (int i = lo; i < hi; i++) { // 2단계: 차례로 실행            String tok = tokens[i];            if (tok.equals("{")) {                run(i + 1, match[i]);                i = match[i];            } else if (tok.charAt(0) == '?') {                ArrayDeque<Cell> stack = cells.get(tok.substring(1));                if (stack == null || stack.isEmpty()) out.append("없는 이름\n");                else if (stack.peek().value == -1) out.append("아직 값 없음\n");                else out.append(stack.peek().value).append('\n');            } else {                int eq = tok.indexOf('=');                cells.get(tok.substring(0, eq)).peek().value = Long.parseLong(tok.substring(eq + 1));            }        }        for (String name : owned) cells.get(name).pop(); // 블록을 나가면 이 블록의 칸을 걷는다    }     public static void main(String[] args) throws IOException {        String all = new String(System.in.readAllBytes()).trim();        tokens = all.split("\\s+");        match = new int[tokens.length];        ArrayDeque<Integer> opened = new ArrayDeque<>();        for (int i = 0; i < tokens.length; i++) {            if (tokens[i].equals("{")) opened.push(i);            else if (tokens[i].equals("}")) match[opened.pop()] = i;        }        run(0, tokens.length);        System.out.print(out);    }}
C#Main.cs · 83 줄
using System;using System.Collections.Generic;using System.Text; class Program{    // 이름마다 칸 더미의 한 칸. Value -1은 아직 넣지 않음    class Cell    {        public int Block;        public long Value = -1;    }     static string[] tokens;    static int[] match;    static readonly Dictionary<string, Stack<Cell>> cells = new Dictionary<string, Stack<Cell>>();    static int blockCount = 0; // static 필드: 모든 호출이 같은 하나를 본다    static readonly StringBuilder output = new StringBuilder();     // lo..hi-1 칸이 한 블록이다. 이 블록이 가진 이름을 먼저 모두 세운 뒤 차례로 실행한다    static void Run(int lo, int hi)    {        int me = ++blockCount;        var owned = new List<string>(); // 지역 변수: 호출마다 따로        for (int i = lo; i < hi; i++) // 1단계: 블록 안 어디에서든 넣는 이름은 블록 전체의 것        {            string tok = tokens[i];            if (tok == "{")            {                i = match[i];                continue;            }            int eq = tok.IndexOf('=');            if (eq < 0) continue;            string name = tok.Substring(0, eq);            if (!cells.TryGetValue(name, out var stack))            {                stack = new Stack<Cell>();                cells[name] = stack;            }            if (stack.Count == 0 || stack.Peek().Block != me)            {                stack.Push(new Cell { Block = me });                owned.Add(name);            }        }        for (int i = lo; i < hi; i++) // 2단계: 차례로 실행        {            string tok = tokens[i];            if (tok == "{")            {                Run(i + 1, match[i]);                i = match[i];            }            else if (tok[0] == '?')            {                if (!cells.TryGetValue(tok.Substring(1), out var stack) || stack.Count == 0) output.Append("없는 이름\n");                else if (stack.Peek().Value == -1) output.Append("아직 값 없음\n");                else output.Append(stack.Peek().Value).Append('\n');            }            else            {                int eq = tok.IndexOf('=');                cells[tok.Substring(0, eq)].Peek().Value = long.Parse(tok.Substring(eq + 1));            }        }        foreach (string name in owned) cells[name].Pop(); // 블록을 나가면 이 블록의 칸을 걷는다    }     static void Main()    {        tokens = Console.In.ReadToEnd().Split((char[])null, StringSplitOptions.RemoveEmptyEntries);        match = new int[tokens.Length];        var opened = new Stack<int>();        for (int i = 0; i < tokens.Length; i++)        {            if (tokens[i] == "{") opened.Push(i);            else if (tokens[i] == "}") match[opened.Pop()] = i;        }        Run(0, tokens.Length);        Console.Write(output);    }}
C++main.cpp · 70 줄
#include <iostream>#include <string>#include <unordered_map>#include <vector> // 이름마다 칸 더미의 한 칸. value -1은 아직 넣지 않음struct Cell {    int block;    long long value;}; std::vector<std::string> tokens;std::vector<int> match;std::unordered_map<std::string, std::vector<Cell>> cells;int blockCount = 0;  // 전역 변수: 모든 호출이 같은 하나를 본다std::string out; // lo..hi-1 칸이 한 블록이다. 이 블록이 가진 이름을 먼저 모두 세운 뒤 차례로 실행한다void run(int lo, int hi) {    int me = ++blockCount;    std::vector<std::string> owned;  // 지역 변수: 호출마다 따로    for (int i = lo; i < hi; i++) {  // 1단계: 블록 안 어디에서든 넣는 이름은 블록 전체의 것        const std::string& tok = tokens[i];        if (tok == "{") {            i = match[i];            continue;        }        size_t eq = tok.find('=');        if (eq == std::string::npos) continue;        std::string name = tok.substr(0, eq);        auto& stack = cells[name];        if (stack.empty() || stack.back().block != me) {            stack.push_back({me, -1});            owned.push_back(name);        }    }    for (int i = lo; i < hi; i++) {  // 2단계: 차례로 실행        const std::string& tok = tokens[i];        if (tok == "{") {            run(i + 1, match[i]);            i = match[i];        } else if (tok[0] == '?') {            auto it = cells.find(tok.substr(1));            if (it == cells.end() || it->second.empty()) out += "없는 이름\n";            else if (it->second.back().value == -1) out += "아직 값 없음\n";            else out += std::to_string(it->second.back().value) + "\n";        } else {            size_t eq = tok.find('=');            cells[tok.substr(0, eq)].back().value = std::stoll(tok.substr(eq + 1));        }    }    for (const auto& name : owned) cells[name].pop_back();  // 블록을 나가면 이 블록의 칸을 걷는다} int main() {    std::ios::sync_with_stdio(false);    std::string tok;    while (std::cin >> tok) tokens.push_back(tok);    match.assign(tokens.size(), 0);    std::vector<int> opened;    for (int i = 0; i < (int)tokens.size(); i++) {        if (tokens[i] == "{") opened.push_back(i);        else if (tokens[i] == "}") {            match[opened.back()] = i;            opened.pop_back();        }    }    run(0, (int)tokens.size());    std::cout << out;}
Cmain.c · 100 줄
#include <stdio.h>#include <string.h> #define MAXT 10000#define HASH 32768 /* 토큰을 미리 풀어 둔다: kind 0 '{', 1 '}', 2 넣기, 3 질문 */static int kind[MAXT], nid[MAXT], match[MAXT], ntok = 0;static long long val[MAXT]; /* 이름 → 번호(열린 주소 해시) */static char names[MAXT][12];static int nnames = 0, table[HASH]; int name_id(const char *s, int len) {    unsigned h = 0;    for (int i = 0; i < len; i++) h = h * 31 + (unsigned char)s[i];    for (h %= HASH;; h = (h + 1) % HASH) {        if (table[h] == -1) {            memcpy(names[nnames], s, len);            names[nnames][len] = '\0';            table[h] = nnames;            return nnames++;        }        const char *t = names[table[h]];        if ((int)strlen(t) == len && memcmp(t, s, len) == 0) return table[h];    }} /* 이름마다 칸 더미: top[이름]이 맨 위 칸, 칸마다 블록 번호·값(-1은 아직 넣지 않음)·아래 칸 */static int top[MAXT], cell_block[MAXT], cell_prev[MAXT], ncells = 0;static long long cell_value[MAXT];/* 블록마다 가진 이름: 호출마다 base부터 쌓고 나갈 때 base로 되돌린다 */static int owned[MAXT], nowned = 0;static int block_count = 0; /* 전역 변수: 모든 호출이 같은 하나를 본다 */ /* lo..hi-1 칸이 한 블록이다. 이 블록이 가진 이름을 먼저 모두 세운 뒤 차례로 실행한다 */void run(int lo, int hi) {    int me = ++block_count;    int base = nowned; /* 지역 변수: 호출마다 따로 */    for (int i = lo; i < hi; i++) { /* 1단계: 블록 안 어디에서든 넣는 이름은 블록 전체의 것 */        if (kind[i] == 0) {            i = match[i];            continue;        }        if (kind[i] != 2) continue;        int id = nid[i];        if (top[id] == -1 || cell_block[top[id]] != me) {            cell_block[ncells] = me;            cell_value[ncells] = -1;            cell_prev[ncells] = top[id];            top[id] = ncells++;            owned[nowned++] = id;        }    }    for (int i = lo; i < hi; i++) { /* 2단계: 차례로 실행 */        if (kind[i] == 0) {            run(i + 1, match[i]);            i = match[i];        } else if (kind[i] == 3) {            int c = top[nid[i]];            if (c == -1) puts("없는 이름");            else if (cell_value[c] == -1) puts("아직 값 없음");            else printf("%lld\n", cell_value[c]);        } else if (kind[i] == 2) {            cell_value[top[nid[i]]] = val[i]; /* 맨 위 칸은 이 블록의 것 */        }    }    while (nowned > base) { /* 블록을 나가면 이 블록의 칸을 걷는다 */        int id = owned[--nowned];        top[id] = cell_prev[top[id]];    }} int main(void) {    memset(table, -1, sizeof table);    memset(top, -1, sizeof top);    static char buf[32];    int opened[MAXT], nopen = 0;    while (scanf("%31s", buf) == 1) {        int i = ntok++;        if (buf[0] == '{') {            kind[i] = 0;            opened[nopen++] = i;        } else if (buf[0] == '}') {            kind[i] = 1;            match[opened[--nopen]] = i;        } else if (buf[0] == '?') {            kind[i] = 3;            nid[i] = name_id(buf + 1, (int)strlen(buf + 1));        } else {            char *eq = strchr(buf, '=');            kind[i] = 2;            nid[i] = name_id(buf, (int)(eq - buf));            sscanf(eq + 1, "%lld", &val[i]);        }    }    run(0, ntok);    return 0;}
Craykonmain.cray · 116 줄
// 이름마다 칸 더미: 맨 위 칸이 지금 보이는 블록의 것. 칸은 [블록 번호, 값], 값 -1은 아직 넣지 않음// 함수 안에서 쓰는 이름(lo, hi, blockId, owned, k, tok …)은 아래 본문에서 쓰지 않는다:// 본문 맨 바깥에서 만든 이름은 모든 호출이 함께 쓰는 하나가 되기 때문이다실행(lo, hi){    blockCount += 1;    blockId = blockCount;    owned = [];    k = lo;    동안 (k < hi)    {        tok = tokens[k];        만약 (tok == "{")        {            k = match[k] + 1;        }        아니면        {            eq = tok.자리찾기("=");            만약 (eq >= 0)            {                name = tok.부분문자열(0, eq);                만약 (!cells.있나(name))                {                    cells[name] = [];                }                stack = cells[name];                만약 (stack.개수 == 0 || stack[stack.개수 - 1][0] != blockId)                {                    stack.넣기([blockId, -1]);                    owned.넣기(name);                }            }            k += 1;        }    }    k = lo;    동안 (k < hi)    {        tok = tokens[k];        만약 (tok == "{")        {            실행(k + 1, match[k]);            k = match[k];        }        아니면 만약 (tok[0] == "?")        {            name = tok.부분문자열(1);            만약 (!cells.있나(name) || cells[name].개수 == 0)            {                out += "없는 이름\n";            }            아니면            {                stack = cells[name];                cell = stack[stack.개수 - 1];                만약 (cell[1] == -1)                {                    out += "아직 값 없음\n";                }                아니면                {                    out += cell[1] + "\n";                }            }        }        아니면        {            eq = tok.자리찾기("=");            stack = cells[tok.부분문자열(0, eq)];            stack[stack.개수 - 1][1] = tok.부분문자열(eq + 1).수로변환(); // 맨 위 칸은 이 블록의 것        }        k += 1;    }    각각 (name 에서 owned)    {        stack = cells[name];        stack.자리빼기(stack.개수 - 1);    }} tokens = [];line = 콘솔.줄읽기();동안 (line != 빈값){    각각 (t 에서 line.나누기(" "))    {        만약 (t != "")        {            tokens.넣기(t);        }    }    line = 콘솔.줄읽기();} // 짝 괄호 자리: match[i] = i번 '{'와 짝인 '}'의 자리match = 새목록(tokens.개수, 0);opened = [];반복 (i = 0; i < tokens.개수; i++){    만약 (tokens[i] == "{")    {        opened.넣기(i);    }    아니면 만약 (tokens[i] == "}")    {        match[opened[opened.개수 - 1]] = i;        opened.자리빼기(opened.개수 - 1);    }} cells = 사전();blockCount = 0; // 본문에서 만든 이름이라 함수 안에서 바꾸면 이 하나가 바뀐다out = "";실행(0, tokens.개수);콘솔.이어쓰기(out);