{ }codelper
等価比較と型変換P0287

錠の記録を照らし合わせる

自転車置き場の錠には、持ち主の名前と秘密の番号がいくつか、順番どおりに登録されている。
記録一つは「名前 k 番号1 … 番号k」の形の一行である。
k は番号の個数である。
一行目に登録された記録が与えられる。
二行目に試した回数 n が、続く n 行に人々が入力した試しが同じ形の記録で一つずつ与えられる。
二つの記録が同じであるとは、名前が同じで、番号の個数が同じで、どの位置でも番号が同じであることをいう。
番号は数として比べる。
番号の前には 0 が付いていることがあり、007 と 7 は書き方が違っても同じ番号である。
番号が同じでも順番が違えば別の記録であり、前の部分だけ同じで個数が違えば別の記録である。
試しごとに一行ずつ出力せよ。
試しが登録された記録と同じなら "解錠" を出力する。
そうでなく、すぐ前の試しと同じなら "くり返し" を出力する。
どちらでもなければ "不一致" を出力する。
最初の試しには、すぐ前の試しがない。

入力
1 ≤ n ≤ 1,000。
名前は英小文字 1〜10 文字。
1 ≤ k ≤ 10 で、番号は 0 以上 1,000,000,000 以下の整数。
番号はコンマなしの数字 1〜10 文字で書かれ、前に 0 が付いていることがある(007 は 7、00 は 0)。
k と n の前に 0 は付かない。
一行の語の間は空白一つ。
出力
n 行。
各行は "解錠"、"くり返し"、"不一致" のどれか
例 1 · stdin
mina 3 7 300 12
6
mina 3 7 300 12
mina 2 7 300
mina 2 7 300
mino 3 7 300 12
mina 3 7 12 300
mina 3 7 300 12
stdout
解錠
不一致
くり返し
不一致
不一致
解錠
例 2 · stdin
bike 2 1 23
6
bike 2 12 3
bike 1 123
bike 1 0123
bike 2 01 23
bike 2 1 023
bike 2 10 23
stdout
不一致
不一致
くり返し
解錠
解錠
不一致
main.py
入力(stdin)

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

$ 準備完了

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

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

「値が同じか」と「同じ物か」を分ける問題です。
入力から別々に読んだ二つの記録は、中身が同じでも別のオブジェクトなので、== がどちらを聞くかで答えが変わります。
Python は == が値の比較なので、(名前, 番号のリスト) == key が長さと各位置の値を比べてくれます。
同じオブジェクトかどうかは is が聞きます。
C++ も std::string・std::vector の == が値の比較なので、構造体に bool operator==(const Record&) const = default;(C++20)の一行で済みます。
Java の == はオブジェクトでは同じオブジェクトかどうかを聞きます。
文字列は a.equals(b)、配列は Arrays.equals(a, b) で比べます(List の equals は要素を比べます)。
C# は string の == が中身を比べますが、配列は == も Equals も同じ配列かどうかしか聞かないので a.SequenceEqual(b) を使います。
JavaScript・TypeScript は文字列の === が中身の比較で、配列の === は同じ配列かどうかしか聞かないので、長さを比べてから every で位置ごとに比べます。
C は配列名どうしの == がアドレスの比較なので、文字列は strcmp(a, b) == 0、番号はループで比べます(構造体どうしの == はそもそもコンパイルできません)。
Craykon は文字列の == が中身の比較で、リストの == は同じリストかどうかを聞くので、位置ごとに比べる関数を作ります。
落とし穴は六つ:① Java で名前を == で比べると、入力から読んだ文字列はいつも別のオブジェクトなので全部 "no" になります。
② Java の List<Integer> の要素を a.get(i) != b.get(i) で比べたり、Python で数を is で比べたりすると、小さい数(Java -128〜127、CPython -5〜256)はあらかじめ作ったオブジェクトを共有するので合っているように見え、300 のような大きい数で間違えます。
③ 配列を join("") でつないで比べると [1, 23] と [12, 3] と [123] がどれも "123" です(例 2)。
つなぐなら join(",") のように区切りを入れます。
④ Python の zip は短いほうで止まるので、先に長さを比べないと前の部分だけ同じ記録も同じと出ます。
⑤ C で前の試しをコピーせずアドレス(prev = &cur)だけ覚えると、同じ場所に次の試しを読んだ瞬間に前の試しも変わり、自分自身と比べることになります。
構造体の代入 prev = cur は中身をコピーします。
⑥ 行や語を文字列のまま比べると、007 と 7 が違うと出ます。
番号は数に変えてから比べます(Python int、JS Number、Java Integer.parseInt、C# int.Parse、Craykon 数に)。
C では scanf の %d で読みます。
%i は 0 で始まる数を 8 進数として読むので、010 が 8 になります。

Pythonmain.py · 19 行
def read_record(line):    """'이름 개수 번호…' 한 줄을 (이름, 번호 목록)으로."""    parts = line.split()    return parts[0], [int(x) for x in parts[2:]]  key = read_record(input())n = int(input())prev = None  # 첫 시도에는 앞 시도가 없다for _ in range(n):    attempt = read_record(input())    # 튜플·목록의 ==는 길이와 자리마다의 값을 비교한다(`is`는 같은 객체인지를 묻는다)    if attempt == key:        print("解錠")    elif attempt == prev:        print("くり返し")    else:        print("不一致")    prev = attempt
JavaScriptmain.js · 25 行
const lines = require("fs").readFileSync(0, "utf8").split("\n"); // "이름 개수 번호…" 한 줄을 기록으로function readRecord(line) {  const parts = line.trim().split(" ");  return { name: parts[0], codes: parts.slice(2).map(Number) };} // 배열의 ===는 같은 배열인지만 물으므로, 길이와 자리마다의 값을 직접 비교한다function sameRecord(a, b) {  return a.name === b.name && a.codes.length === b.codes.length && a.codes.every((x, i) => x === b.codes[i]);} const key = readRecord(lines[0]);const n = Number(lines[1]);const out = [];let prev = null; // 첫 시도에는 앞 시도가 없다for (let i = 2; i < 2 + n; i++) {  const attempt = readRecord(lines[i]);  if (sameRecord(attempt, key)) out.push("解錠");  else if (prev !== null && sameRecord(attempt, prev)) out.push("くり返し");  else out.push("不一致");  prev = attempt;}console.log(out.join("\n"));
TypeScriptmain.ts · 31 行
import { readFileSync } from "node:fs";const lines: string[] = readFileSync(0, "utf8").split("\n"); interface LockRecord {  name: string;  codes: number[];} // "이름 개수 번호…" 한 줄을 기록으로function readRecord(line: string): LockRecord {  const parts = line.trim().split(" ");  return { name: parts[0], codes: parts.slice(2).map(Number) };} // 배열의 ===는 같은 배열인지만 물으므로, 길이와 자리마다의 값을 직접 비교한다function sameRecord(a: LockRecord, b: LockRecord): boolean {  return a.name === b.name && a.codes.length === b.codes.length && a.codes.every((x, i) => x === b.codes[i]);} const key = readRecord(lines[0]);const n = Number(lines[1]);const out: string[] = [];let prev: LockRecord | null = null; // 첫 시도에는 앞 시도가 없다for (let i = 2; i < 2 + n; i++) {  const attempt = readRecord(lines[i]);  if (sameRecord(attempt, key)) out.push("解錠");  else if (prev !== null && sameRecord(attempt, prev)) out.push("くり返し");  else out.push("不一致");  prev = attempt;}console.log(out.join("\n"));
JavaMain.java · 43 行
import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader;import java.util.Arrays; public class Main {    static String name(String[] parts) {        return parts[0];    }     static int[] codes(String[] parts) {        int[] codes = new int[parts.length - 2];        for (int i = 0; i < codes.length; i++) codes[i] = Integer.parseInt(parts[i + 2]);        return codes;    }     // 문자열은 equals, 배열은 Arrays.equals로 내용을 비교한다(==는 같은 객체인지만 묻는다)    static boolean same(String nameA, int[] codesA, String nameB, int[] codesB) {        return nameA.equals(nameB) && Arrays.equals(codesA, codesB);    }     public static void main(String[] args) throws IOException {        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));        String[] first = in.readLine().trim().split(" ");        String keyName = name(first);        int[] keyCodes = codes(first);        int n = Integer.parseInt(in.readLine().trim());        String prevName = null; // 첫 시도에는 앞 시도가 없다        int[] prevCodes = null;        StringBuilder out = new StringBuilder();        for (int i = 0; i < n; i++) {            String[] parts = in.readLine().trim().split(" ");            String curName = name(parts);            int[] curCodes = codes(parts);            if (same(curName, curCodes, keyName, keyCodes)) out.append("解錠\n");            else if (prevName != null && same(curName, curCodes, prevName, prevCodes)) out.append("くり返し\n");            else out.append("不一致\n");            prevName = curName;            prevCodes = curCodes;        }        System.out.print(out);    }}
C#Main.cs · 35 行
using System;using System.Linq;using System.Text; class Program{    static int[] Codes(string[] parts) => parts.Skip(2).Select(int.Parse).ToArray();     // string의 ==는 내용을 비교하지만, 배열의 ==는 같은 배열인지만 물어서 SequenceEqual을 쓴다    static bool Same(string nameA, int[] codesA, string nameB, int[] codesB) =>        nameA == nameB && codesA.SequenceEqual(codesB);     static void Main()    {        string[] first = Console.ReadLine().Trim().Split(' ');        string keyName = first[0];        int[] keyCodes = Codes(first);        int n = int.Parse(Console.ReadLine());        string prevName = null; // 첫 시도에는 앞 시도가 없다        int[] prevCodes = null;        var sb = new StringBuilder();        for (int i = 0; i < n; i++)        {            string[] parts = Console.ReadLine().Trim().Split(' ');            string curName = parts[0];            int[] curCodes = Codes(parts);            if (Same(curName, curCodes, keyName, keyCodes)) sb.Append("解錠\n");            else if (prevName != null && Same(curName, curCodes, prevName, prevCodes)) sb.Append("くり返し\n");            else sb.Append("不一致\n");            prevName = curName;            prevCodes = curCodes;        }        Console.Write(sb);    }}
C++main.cpp · 35 行
#include <iostream>#include <string>#include <vector> struct Record {    std::string name;    std::vector<int> codes;    // std::string과 std::vector의 ==는 내용을 비교하므로, 멤버끼리 비교하게만 하면 된다    bool operator==(const Record&) const = default;}; Record read_record() {    Record r;    int count;    std::cin >> r.name >> count;    r.codes.resize(count);    for (int& c : r.codes) std::cin >> c;    return r;} int main() {    Record key = read_record();    int n;    std::cin >> n;    Record prev;    bool has_prev = false;  // 첫 시도에는 앞 시도가 없다    for (int i = 0; i < n; i++) {        Record cur = read_record();        if (cur == key) std::cout << "解錠\n";        else if (has_prev && cur == prev) std::cout << "くり返し\n";        else std::cout << "不一致\n";        prev = cur;        has_prev = true;    }}
Cmain.c · 41 行
#include <stdbool.h>#include <stdio.h>#include <string.h> typedef struct {    char name[11];    int count;    int codes[10];} Record; /* 번호는 %d로 읽어 수로 둔다: 앞에 0이 붙은 007도 7이 된다 */static void read_record(Record *r) {    scanf("%10s %d", r->name, &r->count);    for (int i = 0; i < r->count; i++) scanf("%d", &r->codes[i]);} /* 배열 이름끼리의 ==는 주소를 비교한다. 문자열은 strcmp로, 번호는 자리마다 비교한다 */static bool same_record(const Record *a, const Record *b) {    if (strcmp(a->name, b->name) != 0) return false;    if (a->count != b->count) return false;    for (int i = 0; i < a->count; i++)        if (a->codes[i] != b->codes[i]) return false;    return true;} int main(void) {    Record key, prev, cur;    read_record(&key);    int n;    scanf("%d", &n);    bool has_prev = false; /* 첫 시도에는 앞 시도가 없다 */    for (int i = 0; i < n; i++) {        read_record(&cur);        if (same_record(&cur, &key)) puts("解錠");        else if (has_prev && same_record(&cur, &prev)) puts("くり返し");        else puts("不一致");        prev = cur; /* 구조체 대입은 내용을 통째로 복사한다 */        has_prev = true;    }    return 0;}
Craykonmain.cray · 50 行
// "이름 개수 번호…" 한 줄을 [이름, 번호, 번호, …] 목록으로 바꾼다.// 번호는 수로 바꿔 둔다: 문자열 그대로면 "007"과 "7"이 다르다기록읽기(줄){    낱말 = 줄.空白除去().分ける(" ");    기록 = [낱말[0]];    繰り返す (i = 2; i < 낱말.個数; i++)    {        기록.追加(낱말[i].数に());    }    返す 기록;} // 목록끼리의 ==는 같은 목록인지만 물으므로, 길이와 자리마다의 값을 직접 비교한다같은기록인가(a, b){    もし (a.個数 != b.個数)    {        返す 偽;    }    繰り返す (i = 0; i < a.個数; i++)    {        もし (a[i] != b[i])        {            返す 偽;        }    }    返す 真;} 등록 = 기록읽기(コンソール.行読み());n = コンソール.行読み().数に();앞시도 = なし; // 첫 시도에는 앞 시도가 없다繰り返す (k = 0; k < n; k++){    시도 = 기록읽기(コンソール.行読み());    もし (같은기록인가(시도, 등록))    {        コンソール.書く("解錠");    }    でなければ もし (앞시도 != なし && 같은기록인가(시도, 앞시도))    {        コンソール.書く("くり返し");    }    でなければ    {        コンソール.書く("不一致");    }    앞시도 = 시도;}