{ }codelper
辞書の更新キーの存在P0388

アルバムのタグをまとめる

アルバムの写真にはタグを一つずつ付ける。
アルバムはタグごとに、そのタグが付いた写真の枚数を覚えている。
写真が 1 枚以上付いているタグを「あるタグ」と呼び、最初はあるタグが一つもない。
命令 q 個を順に処理する。
add t k は、タグ t が付いた写真を k 枚増やす。
タグ t がなかったなら新しくできる。
そのあとタグ t の写真の枚数を出力する。
move a b は、タグ a が付いた写真をすべてタグ b に付け替える。
タグ a がなければ何も変わらず、0 を出力する。
タグ a があって a と b が同じなら何も変わらず、そのタグの写真の枚数を出力する。
タグ a があって a と b が違うなら、a の写真の枚数を b の写真の枚数に足し、タグ a をなくす。
タグ b がなかったなら新しくできる。
そのあとタグ b の写真の枚数を出力する。
すべての命令を処理したあと、あるタグの数を出力する。

入力
1 行目に命令の数 q が与えられる(1 ≤ q ≤ 2,000)。
続く q 行に命令が 1 行に一つずつ与えられる。
命令は add t k または move a b の形で、語の間は空白一つで区切る。
タグの名前 t、a、b は英小文字だけからなる長さ 1 以上 10 以下の文字列である。
k は整数である(1 ≤ k ≤ 1,000)。
出力
命令ごとに 1 行に一つずつ、上で決めた数を命令が与えられた順に出力する。
最後の行に、すべての命令を処理したあとのあるタグの数を出力する。
例 1 · stdin
8
add cat 3
add dog 2
move cat pet
move dog pet
move bird dog
add cat 4
move cat cat
move pet cat
stdout
3
2
3
5
0
4
4
9
1
main.py
入力(stdin)

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

$ 準備完了

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

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

辞書からキーを消しながらその値を取り出す方法と、ないかもしれないキーに値を足す方法が言語ごとに違います。
Python は moved = tags.pop(a) が消しながら値を返し、tags[b] = tags.get(b, 0) + moved で足します。
Java は tags.remove(a) が消した値を返し(なければ null)、tags.merge(b, moved, Integer::sum) がなければ入れ、あれば足します。
C# は tags.Remove(a, out int moved) で値を受け取ります。
足すときは TryGetValue で先に取り出します(なければ 0)。
JavaScript・TypeScript の Map は delete が値ではなく true/false を返すので、get で先に読んでおき、(tags.get(b) ??
0) + moved で足します。
C++ は find で見つけた位置から値を読み、erase で消します。
tags[b] += moved はないキーを 0 で作ってくれるので、そのまま使えます。
C には辞書がないので、名前の配列と枚数の配列を並べて前から探します。
Craykon はキーを取り除く操作が値ではなく真偽を返すので先に読んでおきます。
ないキーは読めないので、キーがあるか確かめて、なければ 0 を入れてから足します。
罠その一: 同じタグへの付け替えです。
例 1 の七つ目の命令 move cat cat は 4 を出力して何も変えません。
a と b が同じかどうかを見ずに「b に足して a を消す」と、cat が 8 になったあと丸ごと消えるので、その行が 8、次の行が 9 ではなく 5 になります。
罠その二: 上書きです。
四つ目の命令 move dog pet では pet にもう 3 枚あるので 5 になります。
足さずに Java の put で入れると 2 になります。
罠その三: 聞いただけでできるキーです。
C++ でタグがあるかどうかを tags[a] == 0 で聞くと、ないキーが 0 で作られます。
五つ目の命令 move bird dog のあとに写真 0 枚の bird が残り、最後の行が 1 ではなく 2 になります。
罠その四: ないタグからの取り出しです。
同じ五つ目の命令で Python の tags.pop(a) を確かめずに呼ぶと KeyError で止まります。
罠その五: ないキーへの足し算です。
三つ目の命令 move cat pet では pet はまだありません。
C# で tags[b] += moved と書くと KeyNotFoundException で止まります。

Pythonmain.py · 24 行
import sys lines = sys.stdin.read().split("\n")q = int(lines[0])tags = {}out = []for i in range(1, q + 1):    parts = lines[i].split()    if parts[0] == "add":        t = parts[1]        tags[t] = tags.get(t, 0) + int(parts[2])        out.append(str(tags[t]))    else:        a, b = parts[1], parts[2]        if a not in tags:            out.append("0")        elif a == b:            out.append(str(tags[a]))        else:            moved = tags.pop(a)  # 지우면서 값을 돌려받는다            tags[b] = tags.get(b, 0) + moved            out.append(str(tags[b]))out.append(str(len(tags)))print("\n".join(out))
JavaScriptmain.js · 27 行
const lines = require("fs").readFileSync(0, "utf8").split("\n");const q = Number(lines[0]);const tags = new Map();const out = [];for (let i = 1; i <= q; i++) {  const parts = lines[i].trim().split(" ");  if (parts[0] === "add") {    const t = parts[1];    tags.set(t, (tags.get(t) ?? 0) + Number(parts[2]));    out.push(String(tags.get(t)));  } else {    const a = parts[1];    const b = parts[2];    if (!tags.has(a)) {      out.push("0");    } else if (a === b) {      out.push(String(tags.get(a)));    } else {      const moved = tags.get(a); // delete는 값이 아니라 true/false를 돌려준다      tags.delete(a);      tags.set(b, (tags.get(b) ?? 0) + moved);      out.push(String(tags.get(b)));    }  }}out.push(String(tags.size));console.log(out.join("\n"));
TypeScriptmain.ts · 31 行
import { readFileSync } from "node:fs"; const lines: string[] = readFileSync(0, "utf8").split("\n");const q: number = Number(lines[0]);const tags: Map<string, number> = new Map();const out: string[] = [];for (let i = 1; i <= q; i++) {  const parts: string[] = lines[i].trim().split(" ");  if (parts[0] === "add") {    const t: string = parts[1];    const count: number = (tags.get(t) ?? 0) + Number(parts[2]);    tags.set(t, count);    out.push(String(count));  } else {    const a: string = parts[1];    const b: string = parts[2];    const moved: number | undefined = tags.get(a); // 없으면 undefined    if (moved === undefined) {      out.push("0");    } else if (a === b) {      out.push(String(moved));    } else {      tags.delete(a); // delete는 값이 아니라 true/false를 돌려준다      const count: number = (tags.get(b) ?? 0) + moved;      tags.set(b, count);      out.push(String(count));    }  }}out.push(String(tags.size));console.log(out.join("\n"));
JavaMain.java · 36 行
import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader;import java.util.HashMap;import java.util.Map; public class Main {    public static void main(String[] args) throws IOException {        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));        int q = Integer.parseInt(br.readLine().trim());        Map<String, Integer> tags = new HashMap<>();        StringBuilder sb = new StringBuilder();        for (int i = 0; i < q; i++) {            String[] parts = br.readLine().trim().split(" ");            if (parts[0].equals("add")) {                // merge: 없으면 넣고, 있으면 더한 값을 넣는다. 넣은 값을 돌려준다                int count = tags.merge(parts[1], Integer.parseInt(parts[2]), Integer::sum);                sb.append(count).append('\n');            } else {                String a = parts[1];                String b = parts[2];                if (!tags.containsKey(a)) {                    sb.append(0).append('\n');                } else if (a.equals(b)) {                    sb.append(tags.get(a)).append('\n');                } else {                    int moved = tags.remove(a); // 지우면서 값을 돌려받는다                    int count = tags.merge(b, moved, Integer::sum);                    sb.append(count).append('\n');                }            }        }        sb.append(tags.size()).append('\n');        System.out.print(sb);    }}
C#Main.cs · 48 行
using System;using System.Collections.Generic;using System.Text; class Program{    static void Main()    {        int q = int.Parse(Console.ReadLine().Trim());        var tags = new Dictionary<string, int>();        StringBuilder sb = new StringBuilder();        for (int i = 0; i < q; i++)        {            string[] parts = Console.ReadLine().Trim().Split(' ');            if (parts[0] == "add")            {                // 없는 열쇠에 +=를 하면 예외가 난다: TryGetValue로 먼저 꺼낸다(없으면 0)                tags.TryGetValue(parts[1], out int count);                count += int.Parse(parts[2]);                tags[parts[1]] = count;                sb.Append(count).Append('\n');            }            else            {                string a = parts[1];                string b = parts[2];                if (!tags.ContainsKey(a))                {                    sb.Append(0).Append('\n');                }                else if (a == b)                {                    sb.Append(tags[a]).Append('\n');                }                else                {                    tags.Remove(a, out int moved); // 지우면서 값을 돌려받는다                    tags.TryGetValue(b, out int count);                    count += moved;                    tags[b] = count;                    sb.Append(count).Append('\n');                }            }        }        sb.Append(tags.Count).Append('\n');        Console.Write(sb);    }}
C++main.cpp · 39 行
#include <iostream>#include <string>#include <unordered_map>using namespace std; int main() {    ios::sync_with_stdio(false);    cin.tie(nullptr);    int q;    cin >> q;    unordered_map<string, int> tags;    for (int i = 0; i < q; i++) {        string op;        cin >> op;        if (op == "add") {            string t;            int k;            cin >> t >> k;            tags[t] += k; // []는 없는 열쇠를 0으로 만들어 준다            cout << tags[t] << '\n';        } else {            string a, b;            cin >> a >> b;            auto it = tags.find(a); // find는 열쇠를 만들지 않는다            if (it == tags.end()) {                cout << 0 << '\n';            } else if (a == b) {                cout << it->second << '\n';            } else {                int moved = it->second;                tags.erase(it);                tags[b] += moved;                cout << tags[b] << '\n';            }        }    }    cout << tags.size() << '\n';    return 0;}
Cmain.c · 62 行
#include <stdio.h>#include <string.h> /* C에는 사전이 없다: 이름 배열과 수 배열을 나란히 두고 앞에서부터 찾는다 */static char names[2000][11];static int counts[2000];static int size = 0; static int find(const char *t) {    for (int i = 0; i < size; i++) {        if (strcmp(names[i], t) == 0) return i;    }    return -1;} /* 없으면 0장으로 새로 만들고 그 자리를 돌려준다 */static int find_or_add(const char *t) {    int i = find(t);    if (i < 0) {        i = size++;        strcpy(names[i], t);        counts[i] = 0;    }    return i;} int main(void) {    int q;    if (scanf("%d", &q) != 1) return 1;    for (int i = 0; i < q; i++) {        char op[8], a[11], b[11];        if (scanf("%7s", op) != 1) return 1;        if (strcmp(op, "add") == 0) {            int k;            if (scanf("%10s %d", a, &k) != 2) return 1;            int at = find_or_add(a);            counts[at] += k;            printf("%d\n", counts[at]);        } else {            if (scanf("%10s %10s", a, b) != 2) return 1;            int from = find(a);            if (from < 0) {                printf("0\n");            } else if (strcmp(a, b) == 0) {                printf("%d\n", counts[from]);            } else {                int moved = counts[from];                /* 지우기: 마지막 칸을 빈 자리로 옮긴다 */                size--;                if (from != size) {                    strcpy(names[from], names[size]);                    counts[from] = counts[size];                }                int to = find_or_add(b);                counts[to] += moved;                printf("%d\n", counts[to]);            }        }    }    printf("%d\n", size);    return 0;}
Craykonmain.cray · 42 行
명령수 = コンソール.行読み().数に();꼬리표들 = 辞書();繰り返す (i = 0; i < 명령수; i++){    칸들 = コンソール.行読み().空白除去().分ける();    もし (칸들[0] == "add")    {        이름 = 칸들[1];        // 없는 열쇠를 읽으면 멈춘다: 있는지 먼저 본다        もし (!꼬리표들.含む(이름))        {            꼬리표들[이름] = 0;        }        꼬리표들[이름] += 칸들[2].数に();        コンソール.書く(꼬리표들[이름]);    }    でなければ    {        원래 = 칸들[1];        새것 = 칸들[2];        もし (!꼬리표들.含む(원래))        {            コンソール.書く(0);        }        でなければ もし (원래 == 새것)        {            コンソール.書く(꼬리표들[원래]);        }        でなければ        {            옮길수 = 꼬리표들[원래]; // 빼기는 값이 아니라 참/거짓을 돌려준다            꼬리표들.取り除く(원래);            もし (!꼬리표들.含む(새것))            {                꼬리표들[새것] = 0;            }            꼬리표들[새것] += 옮길수;            コンソール.書く(꼬리표들[새것]);        }    }}コンソール.書く(꼬리표들.個数);