{ }codelper
値の入れ替え文字列の分割P0272

まとめて代入する電卓のマス

小さな電卓に、英小文字 a から z までの名前が付いたマスが 26 個ある。
すべてのマスは最初 0 である。
一行目に命令の数 q、続く q 行に命令が一つずつ与えられる。
命令の中の単語は空白一つで区切られる。
set x v はマス x に整数 v を入れる。
swap x y はマス x とマス y の値を入れ替える。
x と y は同じこともあり、そのときは何も変わらない。
rot x y z は異なる三つのマスの値を回す。
x は y の元の値を、y は z の元の値を、z は x の元の値を受け取る。
let x1 x2 … xk = e1 e2 … ek はマス xi に項 ei の値を入れる(i = 1, …, k)。
左側のマスの名前はすべて異なる。
このとき右側の k 個の項をすべて、この命令の直前の値で先に計算し、そのあと k 個のマスにまとめて入れる。
項はマスの名前一つ、0 以上の整数一つ、または u+w か u-w の形である。
u と w はそれぞれマスの名前か 0 以上の整数で、間に空白はない。
show x1 x2 … xk はマス x1, x2, …, xk の値をこの順に空白一つずつ空けて一行に出力する。
同じマスが何度も出ることもある。
命令を順に処理し、show ごとに一行ずつ出力せよ。

入力
1 ≤ q ≤ 2,000。
set の v は -1,000,000 以上 1,000,000 以下。
項の中の整数は 0 以上 1,000,000 以下。
let と show のマスの数 k は 1 以上 26 以下。
show は 1 個以上 200 個以下。
すべてのマスの値は常に -1,000,000,000 以上 1,000,000,000 以下
出力
show の数だけの行
例 1 · stdin
7
set a 1
let a b = a+b a
let a b = a+b a
let a b = a+b a
show a b
swap a b
show a b
stdout
3 2
2 3
例 2 · stdin
9
set x 5
set y -7
set z 9
swap x x
show x
rot x y z
show x y z
let x y z = z-1 x+y 100
show x y z
stdout
5
-7 9 5
4 2 100
main.py
入力(stdin)

実行すると、これらの行が順に input()・標準入力に渡されます(実行中に入力を求めません)。

$ 準備完了

コードを入力して解くにはパソコンで開いてください。モバイルでは問題と解答を見られます。

ほかの言語では?言語別の解答を見る · 8 言語の解答

二つの値の入れ替え方は言語ごとに違います。
Python a, b = b, a、JS・TS [a, b] = [b, a]、C# (a, b) = (b, a) のようにまとめて代入する書き方があれば、右側をすべて計算してから代入するので rot も一行です。
C++ は書き方の代わりに std::swap(a, b) という関数があり、Java・C・Craykon は一時変数 tmp に一つを預けておきます。
let はマスの数 k が毎回違うので、どの言語でも右側の値を先に配列(Python・Craykon はリスト)に集めてから代入します。
落とし穴 ① let を一つ計算するたびにすぐ代入する:let a b = a+b a で a を先に変えると b は新しい a を受け取ります。
② コピーではなく名前を付けただけ:Python old = reg、JS const old = reg は同じ辞書・配列を指すので、スナップショットになりません。
dict(reg)、[...reg] のようにコピーします。
③ 一時変数なしの入れ替えの小技:XOR(a ^= b; b ^= a; a ^= b)と足し引き(a = a + b; b = a - b; a = a - b)は、二つの名前が同じマスを指すと(swap x x)値が 0 になります。
足し引きは大きな数であふれることもあります。
④ rot を一時変数なしで x = y; y = z; z = x と書くと、z はもう変わった x(元の y)を受け取ります。
向きも間違えやすく、x が y の値を受け取る側です。
⑤ 項の - は常に引き算です。
項の中の整数には符号がないので、最初の文字より後ろで + か - を探せば十分です。
Craykon は関数の中で代入した名前をプログラムのいちばん外側でも使っていると(引数でなければ)その外の変数が変わるので、関数の中のループ変数には外と違う名前を付けます。

Pythonmain.py · 42 行
import sys lines = sys.stdin.read().split("\n")q = int(lines[0])reg = {chr(c): 0 for c in range(ord("a"), ord("z") + 1)}  def atom(t):    return reg[t] if t.isalpha() else int(t)  def term(t):    # p+q, p-q, 또는 칸 이름·수 하나. 항 안의 수에는 부호가 없어서 -는 늘 빼기다    for op in "+-":        i = t.find(op)        if i > 0:            a, b = atom(t[:i]), atom(t[i + 1:])            return a + b if op == "+" else a - b    return atom(t)  out = []for line in lines[1:1 + q]:    p = line.split()    cmd = p[0]    if cmd == "set":        reg[p[1]] = int(p[2])    elif cmd == "swap":        x, y = p[1], p[2]        reg[x], reg[y] = reg[y], reg[x]  # x와 y가 같아도 그대로    elif cmd == "rot":        x, y, z = p[1], p[2], p[3]        reg[x], reg[y], reg[z] = reg[y], reg[z], reg[x]    elif cmd == "let":        eq = p.index("=")        targets = p[1:eq]        values = [term(t) for t in p[eq + 1:]]  # 오른쪽을 모두 먼저 계산한다        for x, v in zip(targets, values):            reg[x] = v    else:  # show        out.append(" ".join(str(reg[x]) for x in p[1:]))print("\n".join(out))
JavaScriptmain.js · 40 行
const lines = require("fs").readFileSync(0, "utf8").split("\n");const q = Number(lines[0]);const reg = new Array(26).fill(0);const at = (x) => x.charCodeAt(0) - 97; // 'a' → 0 const atom = (t) => (/^[a-z]$/.test(t) ? reg[at(t)] : Number(t)); function term(t) {  // 항 안의 수에는 부호가 없어서 첫 글자 뒤의 + 나 - 는 늘 계산 기호다  const i = t.search(/[+-]/);  if (i <= 0) return atom(t);  const a = atom(t.slice(0, i));  const b = atom(t.slice(i + 1));  return t[i] === "+" ? a + b : a - b;} const out = [];for (const line of lines.slice(1, 1 + q)) {  const p = line.trim().split(" ");  const cmd = p[0];  if (cmd === "set") {    reg[at(p[1])] = Number(p[2]);  } else if (cmd === "swap") {    const x = at(p[1]);    const y = at(p[2]);    [reg[x], reg[y]] = [reg[y], reg[x]]; // 구조 분해로 한꺼번에 바꾼다  } else if (cmd === "rot") {    const [x, y, z] = p.slice(1).map(at);    [reg[x], reg[y], reg[z]] = [reg[y], reg[z], reg[x]];  } else if (cmd === "let") {    const eq = p.indexOf("=");    const values = p.slice(eq + 1).map(term); // 오른쪽을 모두 먼저 계산한다    p.slice(1, eq).forEach((x, i) => {      reg[at(x)] = values[i];    });  } else {    out.push(p.slice(1).map((x) => reg[at(x)]).join(" "));  }}console.log(out.join("\n"));
TypeScriptmain.ts · 42 行
import { readFileSync } from "node:fs"; const lines: string[] = readFileSync(0, "utf8").split("\n");const q = Number(lines[0]);const reg: number[] = new Array<number>(26).fill(0);const at = (x: string): number => x.charCodeAt(0) - 97; // 'a' → 0 const atom = (t: string): number => (/^[a-z]$/.test(t) ? reg[at(t)] : Number(t)); function term(t: string): number {  // 항 안의 수에는 부호가 없어서 첫 글자 뒤의 + 나 - 는 늘 계산 기호다  const i = t.search(/[+-]/);  if (i <= 0) return atom(t);  const a = atom(t.slice(0, i));  const b = atom(t.slice(i + 1));  return t[i] === "+" ? a + b : a - b;} const out: string[] = [];for (const line of lines.slice(1, 1 + q)) {  const p = line.trim().split(" ");  const cmd = p[0];  if (cmd === "set") {    reg[at(p[1])] = Number(p[2]);  } else if (cmd === "swap") {    const x = at(p[1]);    const y = at(p[2]);    [reg[x], reg[y]] = [reg[y], reg[x]]; // 구조 분해로 한꺼번에 바꾼다  } else if (cmd === "rot") {    const [x, y, z] = p.slice(1).map(at);    [reg[x], reg[y], reg[z]] = [reg[y], reg[z], reg[x]];  } else if (cmd === "let") {    const eq = p.indexOf("=");    const values: number[] = p.slice(eq + 1).map(term); // 오른쪽을 모두 먼저 계산한다    p.slice(1, eq).forEach((x, i) => {      reg[at(x)] = values[i];    });  } else {    out.push(p.slice(1).map((x) => reg[at(x)]).join(" "));  }}console.log(out.join("\n"));
JavaMain.java · 68 行
import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader;import java.util.StringJoiner; public class Main {    static int[] reg = new int[26];     static int at(String x) {        return x.charAt(0) - 'a';    }     static int atom(String t) {        return Character.isLetter(t.charAt(0)) ? reg[at(t)] : Integer.parseInt(t);    }     static int term(String t) {        // 항 안의 수에는 부호가 없어서 첫 글자 뒤의 + 나 - 는 늘 계산 기호다        for (int i = 1; i < t.length(); i++) {            char c = t.charAt(i);            if (c == '+' || c == '-') {                int a = atom(t.substring(0, i));                int b = atom(t.substring(i + 1));                return c == '+' ? a + b : a - b;            }        }        return atom(t);    }     public static void main(String[] args) throws IOException {        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));        int q = Integer.parseInt(in.readLine().trim());        StringBuilder out = new StringBuilder();        for (int t = 0; t < q; t++) {            String[] p = in.readLine().trim().split(" ");            switch (p[0]) {                case "set" -> reg[at(p[1])] = Integer.parseInt(p[2]);                case "swap" -> {                    int x = at(p[1]), y = at(p[2]);                    int tmp = reg[x]; // Java에는 a, b = b, a가 없어서 임시 변수를 쓴다                    reg[x] = reg[y];                    reg[y] = tmp;                }                case "rot" -> {                    int x = at(p[1]), y = at(p[2]), z = at(p[3]);                    int tmp = reg[x];                    reg[x] = reg[y];                    reg[y] = reg[z];                    reg[z] = tmp;                }                case "let" -> {                    int eq = 1;                    while (!p[eq].equals("=")) eq++;                    int k = eq - 1;                    int[] values = new int[k]; // 오른쪽을 모두 먼저 계산해 둔다                    for (int i = 0; i < k; i++) values[i] = term(p[eq + 1 + i]);                    for (int i = 0; i < k; i++) reg[at(p[1 + i])] = values[i];                }                default -> {                    StringJoiner sj = new StringJoiner(" ");                    for (int i = 1; i < p.length; i++) sj.add(String.valueOf(reg[at(p[i])]));                    out.append(sj).append('\n');                }            }        }        System.out.print(out);    }}
C#Main.cs · 62 行
using System;using System.Linq;using System.Text; class Program{    static int[] reg = new int[26];     static int At(string x) => x[0] - 'a';     static int Atom(string t) => char.IsLetter(t[0]) ? reg[At(t)] : int.Parse(t);     static int Term(string t)    {        // 항 안의 수에는 부호가 없어서 첫 글자 뒤의 + 나 - 는 늘 계산 기호다        int i = t.IndexOfAny(new[] { '+', '-' }, 1);        if (i < 0) return Atom(t);        int a = Atom(t.Substring(0, i));        int b = Atom(t.Substring(i + 1));        return t[i] == '+' ? a + b : a - b;    }     static void Main()    {        int q = int.Parse(Console.ReadLine().Trim());        var sb = new StringBuilder();        for (int t = 0; t < q; t++)        {            string[] p = Console.ReadLine().Trim().Split(' ');            switch (p[0])            {                case "set":                    reg[At(p[1])] = int.Parse(p[2]);                    break;                case "swap":                {                    int x = At(p[1]), y = At(p[2]);                    (reg[x], reg[y]) = (reg[y], reg[x]); // 튜플로 한꺼번에 바꾼다                    break;                }                case "rot":                {                    int x = At(p[1]), y = At(p[2]), z = At(p[3]);                    (reg[x], reg[y], reg[z]) = (reg[y], reg[z], reg[x]);                    break;                }                case "let":                {                    int eq = Array.IndexOf(p, "=");                    // 오른쪽을 모두 먼저 계산해 배열로 받아 둔다                    int[] values = p.Skip(eq + 1).Select(Term).ToArray();                    for (int i = 0; i < values.Length; i++) reg[At(p[1 + i])] = values[i];                    break;                }                default:                    sb.Append(string.Join(" ", p.Skip(1).Select(x => reg[At(x)]))).Append('\n');                    break;            }        }        Console.Write(sb);    }}
C++main.cpp · 58 行
#include <array>#include <iostream>#include <sstream>#include <string>#include <utility>#include <vector> std::array<int, 26> reg{}; int atom(const std::string &t) {    return std::isalpha(static_cast<unsigned char>(t[0])) ? reg[t[0] - 'a'] : std::stoi(t);} int term(const std::string &t) {    // 항 안의 수에는 부호가 없어서 첫 글자 뒤의 + 나 - 는 늘 계산 기호다    std::size_t i = t.find_first_of("+-", 1);    if (i == std::string::npos) return atom(t);    int a = atom(t.substr(0, i)), b = atom(t.substr(i + 1));    return t[i] == '+' ? a + b : a - b;} int main() {    std::string line;    std::getline(std::cin, line);    int q = std::stoi(line);    std::string out;    for (int c = 0; c < q; c++) {        std::getline(std::cin, line);        std::istringstream ss(line);        std::vector<std::string> p;        for (std::string w; ss >> w;) p.push_back(w);        if (p[0] == "set") {            reg[p[1][0] - 'a'] = std::stoi(p[2]);        } else if (p[0] == "swap") {            std::swap(reg[p[1][0] - 'a'], reg[p[2][0] - 'a']);        } else if (p[0] == "rot") {            int &x = reg[p[1][0] - 'a'], &y = reg[p[2][0] - 'a'], &z = reg[p[3][0] - 'a'];            int tmp = x;            x = y;            y = z;            z = tmp;        } else if (p[0] == "let") {            std::size_t eq = 1;            while (p[eq] != "=") eq++;            std::vector<int> values; // 오른쪽을 모두 먼저 계산한다            for (std::size_t i = eq + 1; i < p.size(); i++) values.push_back(term(p[i]));            for (std::size_t i = 0; i < values.size(); i++) reg[p[1 + i][0] - 'a'] = values[i];        } else {            for (std::size_t i = 1; i < p.size(); i++) {                if (i > 1) out += ' ';                out += std::to_string(reg[p[i][0] - 'a']);            }            out += '\n';        }    }    std::cout << out;    return 0;}
Cmain.c · 60 行
#include <ctype.h>#include <stdio.h>#include <stdlib.h>#include <string.h> static int reg[26]; static int atom(const char *t, int len) {    if (isalpha((unsigned char)t[0])) return reg[t[0] - 'a'];    int v = 0;    for (int i = 0; i < len; i++) v = v * 10 + (t[i] - '0');    return v;} /* p+q, p-q, 또는 칸 이름·수 하나. 항 안의 수에는 부호가 없다 */static int term(const char *t) {    int n = (int)strlen(t);    for (int i = 1; i < n; i++)        if (t[i] == '+' || t[i] == '-') {            int a = atom(t, i), b = atom(t + i + 1, n - i - 1);            return t[i] == '+' ? a + b : a - b;        }    return atom(t, n);} int main(void) {    static char line[1024];    char *tok[64];    fgets(line, sizeof line, stdin);    int q = atoi(line);    for (int c = 0; c < q; c++) {        fgets(line, sizeof line, stdin);        int k = 0;        for (char *s = strtok(line, " \r\n"); s; s = strtok(NULL, " \r\n")) tok[k++] = s;        if (strcmp(tok[0], "set") == 0) {            reg[tok[1][0] - 'a'] = atoi(tok[2]);        } else if (strcmp(tok[0], "swap") == 0) {            int x = tok[1][0] - 'a', y = tok[2][0] - 'a';            int tmp = reg[x]; /* 임시 칸에 하나를 맡겨 둔다 */            reg[x] = reg[y];            reg[y] = tmp;        } else if (strcmp(tok[0], "rot") == 0) {            int x = tok[1][0] - 'a', y = tok[2][0] - 'a', z = tok[3][0] - 'a';            int tmp = reg[x];            reg[x] = reg[y];            reg[y] = reg[z];            reg[z] = tmp;        } else if (strcmp(tok[0], "let") == 0) {            int eq = 1;            while (strcmp(tok[eq], "=") != 0) eq++;            int n = eq - 1, values[26];            for (int i = 0; i < n; i++) values[i] = term(tok[eq + 1 + i]); /* 먼저 모두 계산 */            for (int i = 0; i < n; i++) reg[tok[1 + i][0] - 'a'] = values[i];        } else {            for (int i = 1; i < k; i++) printf(i > 1 ? " %d" : "%d", reg[tok[i][0] - 'a']);            putchar('\n');        }    }    return 0;}
Craykonmain.cray · 77 行
q = コンソール.行読み().数に();reg = 辞書();繰り返す (i = 0; i < 26; i++){    reg[文字.コードから(97 + i)] = 0;} 하나(t){    もし (reg.含む(t))    {        返す reg[t];    }    返す t.数に();} // p+q, p-q, 또는 칸 이름·수 하나. 항 안의 수에는 부호가 없다항(t){    // 함수 안에서 넣은 이름이 밖에도 있으면 밖의 변수가 바뀌니, 밖에서 안 쓰는 이름(w)을 쓴다    繰り返す (w = 1; w < t.長さ; w++)    {        もし (t[w] == "+" || t[w] == "-")        {            가 = 하나(t.切り出す(0, w));            나머지 = 하나(t.切り出す(w + 1));            もし (t[w] == "+")            {                返す 가 + 나머지;            }            返す 가 - 나머지;        }    }    返す 하나(t);} 繰り返す (c = 0; c < q; c++){    p = コンソール.行読み().空白除去().分ける(" ");    選択 (p[0])    {        場合 "set":            reg[p[1]] = p[2].数に();            抜ける;        場合 "swap":            임시 = reg[p[1]]; // 크레이콘에는 한꺼번에 넣기가 없어서 임시 변수에 맡긴다            reg[p[1]] = reg[p[2]];            reg[p[2]] = 임시;            抜ける;        場合 "rot":            임시 = reg[p[1]];            reg[p[1]] = reg[p[2]];            reg[p[2]] = reg[p[3]];            reg[p[3]] = 임시;            抜ける;        場合 "let":            eq = p.位置検索("=");            값들 = []; // 오른쪽을 모두 먼저 계산해 둔다            繰り返す (i = eq + 1; i < p.個数; i++)            {                값들.追加(항(p[i]));            }            繰り返す (i = 0; i < 값들.個数; i++)            {                reg[p[1 + i]] = 값들[i];            }            抜ける;        それ以外:            보일것 = [];            繰り返す (i = 1; i < p.個数; i++)            {                보일것.追加(reg[p[i]]);            }            コンソール.書く(보일것.つなぐ(" "));            抜ける;    }}