{ }codelper
含まれるか判定文字列の走査P0273

商品コードの型チェック

倉庫で、商品コードが決められた型に合うかを調べる。
型はマスを左から並べたもので、マス一つがコードの文字一つを調べる。
型の中で角かっこの外にある一文字はマス一つで、そのマスはちょうどその文字だけを受け付ける。
型の中で [ から、そのあと最初に出てくる ] までは文字クラスのマス一つで、二つの角かっこの間の文字列を本体と呼ぶ。
本体が ^ で始まれば反転したクラスである。
この先頭の ^ は本体から除き、残りの本体が表す文字ではない文字だけを受け付ける。
ほかの位置の ^ はふつうの文字である。
本体は左から読む。
今の文字のすぐ後ろに - があり、その後ろにもう一文字あれば、三文字 a-b は ASCII コードが a 以上 b 以下の文字すべてを表し、三文字進む。
そうでなければ今の文字一つだけを表し、一文字進む。
したがって本体の先頭や末尾の - は - という文字そのものである。
一行目に型、二行目にコードの数 n、続く n 行にコードが一つずつ与えられる。
コードごとに、コードの i 番目の文字を型の i 番目のマスで調べる(位置は 1 から数える)。
次のどれかが最初に起きる位置 i を一行に出力せよ:コードに i 番目の文字がない、型に i 番目のマスがない、i 番目のマスが i 番目の文字を受け付けない。
コードの文字数と型のマスの数が同じで、すべてのマスが文字を受け付けるなら 0 を出力せよ。

入力
型は 1~200 文字、マスは 1~100 個。
型とコードの文字はすべて空白でない ASCII 文字(コード 33~126)。
型の [ にはどれも後ろにそのマスを閉じる ] があり、本体に [ はなく、角かっこの外に ] はない。
本体は先頭の ^ を除いても一文字以上で、範囲 a-b は常に a のコード ≤ b のコード。
1 ≤ n ≤ 1,000。
コードは 1~100 文字
出力
n 行
例 1 · stdin
[A-Z][a-z_-][0-9]
6
Ab3
A-7
a_1
B_
Cd45
Z^0
stdout
0
0
1
3
4
2
例 2 · stdin
#[^0-9^][A-z]
8
#a_
#^a
#x\
#x[
x
#5Q
#~`
#Q@
stdout
0
2
0
0
1
2
0
3
main.py
入力(stdin)

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

$ 準備完了

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

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

正規表現の文字クラス [...] と同じ規則です。
マスごとに受け付ける文字を先に集めておき、「その文字が入っているか」だけを聞きます:Python c in s(set)、JS・TS set.has(c)、Java・C#・C は文字コードで引く 128 個の bool 配列、C++ std::bitset<128>、Craykon は 辞書 の 含む。
反転したマスでは (入っているか) == 反転 なら不合格の文字です。
落とし穴 ① 範囲は両端を含みます。
Python の range(ord(a), ord(b)) は終わりの b を除きます。
② - は二文字の間にあるときだけ範囲です。
先頭・末尾の - は文字 - で、a-c-e は a-c、-、e です。
③ ^ は先頭でだけ反転です。
[a^] の ^ はただの文字です。
④ A-z は英字だけではありません。
コード 91~96 の [ \ ] ^ _ とバッククォート(` `) も入ります。だから isalpha・Character.isLetter・char.IsLetter で範囲の代わりにすると間違えます。⑤ 文字列で含まれるかを調べると範囲が消えます。Python c in "a-z"、JS "a-z".includes(c)、Craykon "a-z".含む(c) は a、-、z の三文字しか見ません。⑥ 長さが違っても不合格です。短いほうまでしか比べないと、B_ のように文字が足りないコードに 0 を出します。位置は 1 から数えます。C で文字を配列の添字にするときは (unsigned char) に変えます(char` は負になることがあります)。

Pythonmain.py · 46 行
import sys  def parse(pattern):    """틀을 칸 목록으로 바꾼다. 칸 하나는 (뒤집기, 받는 글자 집합)이다."""    slots = []    i = 0    while i < len(pattern):        if pattern[i] != "[":            slots.append((False, {pattern[i]}))  # 대괄호 밖 글자는 그 글자 하나만            i += 1            continue        end = pattern.index("]", i)        body = pattern[i + 1:end]        neg = body.startswith("^")  # ^는 맨 앞에 있을 때만 뒤집기        if neg:            body = body[1:]        chars = set()        j = 0        while j < len(body):            if j + 2 < len(body) and body[j + 1] == "-":                # a-b: 코드가 a 이상 b 이하인 글자 모두(두 끝 포함)                chars.update(chr(c) for c in range(ord(body[j]), ord(body[j + 2]) + 1))                j += 3            else:                chars.add(body[j])  # 맨 앞·맨 끝의 -는 그냥 글자                j += 1        slots.append((neg, chars))        i = end + 1    return slots  def first_bad(slots, t):    for i in range(max(len(slots), len(t))):        if i >= len(slots) or i >= len(t):            return i + 1  # 글자가 모자라거나 남는다        neg, chars = slots[i]        if (t[i] in chars) == neg:  # 뒤집힌 칸은 들어 있으면 안 된다            return i + 1    return 0  lines = sys.stdin.read().split("\n")slots = parse(lines[0].strip())n = int(lines[1])print("\n".join(str(first_bad(slots, t.strip())) for t in lines[2:2 + n]))
JavaScriptmain.js · 44 行
const lines = require("fs").readFileSync(0, "utf8").split("\n"); // 틀을 칸 목록으로 바꾼다. 칸 하나는 { neg, chars(Set) }다function parse(pattern) {  const slots = [];  let i = 0;  while (i < pattern.length) {    if (pattern[i] !== "[") {      slots.push({ neg: false, chars: new Set([pattern[i]]) }); // 대괄호 밖 글자는 그 글자 하나만      i++;      continue;    }    const end = pattern.indexOf("]", i);    let body = pattern.slice(i + 1, end);    const neg = body.startsWith("^"); // ^는 맨 앞에 있을 때만 뒤집기    if (neg) body = body.slice(1);    const chars = new Set();    for (let j = 0; j < body.length; ) {      if (j + 2 < body.length && body[j + 1] === "-") {        // a-b: 코드가 a 이상 b 이하인 글자 모두(두 끝 포함)        for (let c = body.charCodeAt(j); c <= body.charCodeAt(j + 2); c++) chars.add(String.fromCharCode(c));        j += 3;      } else {        chars.add(body[j]); // 맨 앞·맨 끝의 -는 그냥 글자        j++;      }    }    slots.push({ neg, chars });    i = end + 1;  }  return slots;} function firstBad(slots, t) {  for (let i = 0; i < Math.max(slots.length, t.length); i++) {    if (i >= slots.length || i >= t.length) return i + 1; // 글자가 모자라거나 남는다    if (slots[i].chars.has(t[i]) === slots[i].neg) return i + 1; // 뒤집힌 칸은 들어 있으면 안 된다  }  return 0;} const slots = parse(lines[0].trim());const n = Number(lines[1]);console.log(lines.slice(2, 2 + n).map((t) => firstBad(slots, t.trim())).join("\n"));
TypeScriptmain.ts · 51 行
import { readFileSync } from "node:fs"; interface Slot {  neg: boolean;  chars: Set<string>;} const lines: string[] = readFileSync(0, "utf8").split("\n"); // 틀을 칸 목록으로 바꾼다function parse(pattern: string): Slot[] {  const slots: Slot[] = [];  let i = 0;  while (i < pattern.length) {    if (pattern[i] !== "[") {      slots.push({ neg: false, chars: new Set([pattern[i]]) }); // 대괄호 밖 글자는 그 글자 하나만      i++;      continue;    }    const end = pattern.indexOf("]", i);    let body = pattern.slice(i + 1, end);    const neg = body.startsWith("^"); // ^는 맨 앞에 있을 때만 뒤집기    if (neg) body = body.slice(1);    const chars = new Set<string>();    for (let j = 0; j < body.length; ) {      if (j + 2 < body.length && body[j + 1] === "-") {        // a-b: 코드가 a 이상 b 이하인 글자 모두(두 끝 포함)        for (let c = body.charCodeAt(j); c <= body.charCodeAt(j + 2); c++) chars.add(String.fromCharCode(c));        j += 3;      } else {        chars.add(body[j]); // 맨 앞·맨 끝의 -는 그냥 글자        j++;      }    }    slots.push({ neg, chars });    i = end + 1;  }  return slots;} function firstBad(slots: Slot[], t: string): number {  for (let i = 0; i < Math.max(slots.length, t.length); i++) {    if (i >= slots.length || i >= t.length) return i + 1; // 글자가 모자라거나 남는다    if (slots[i].chars.has(t[i]) === slots[i].neg) return i + 1; // 뒤집힌 칸은 들어 있으면 안 된다  }  return 0;} const slots = parse(lines[0].trim());const n = Number(lines[1]);console.log(lines.slice(2, 2 + n).map((t) => firstBad(slots, t.trim())).join("\n"));
JavaMain.java · 57 行
import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader;import java.util.ArrayList;import java.util.List; public class Main {    // 칸 하나: 글자 코드 0~127마다 받는지(ok)를 적은 표    static List<boolean[]> parse(String pattern) {        List<boolean[]> slots = new ArrayList<>();        int i = 0;        while (i < pattern.length()) {            boolean[] ok = new boolean[128];            if (pattern.charAt(i) != '[') {                ok[pattern.charAt(i)] = true; // 대괄호 밖 글자는 그 글자 하나만                slots.add(ok);                i++;                continue;            }            int end = pattern.indexOf(']', i);            String body = pattern.substring(i + 1, end);            boolean neg = body.startsWith("^"); // ^는 맨 앞에 있을 때만 뒤집기            if (neg) body = body.substring(1);            for (int j = 0; j < body.length(); ) {                if (j + 2 < body.length() && body.charAt(j + 1) == '-') {                    for (char c = body.charAt(j); c <= body.charAt(j + 2); c++) ok[c] = true; // 두 끝 포함                    j += 3;                } else {                    ok[body.charAt(j)] = true; // 맨 앞·맨 끝의 -는 그냥 글자                    j++;                }            }            if (neg) for (int c = 0; c < 128; c++) ok[c] = !ok[c];            slots.add(ok);            i = end + 1;        }        return slots;    }     static int firstBad(List<boolean[]> slots, String t) {        int len = Math.max(slots.size(), t.length());        for (int i = 0; i < len; i++) {            if (i >= slots.size() || i >= t.length()) return i + 1; // 글자가 모자라거나 남는다            if (!slots.get(i)[t.charAt(i)]) return i + 1;        }        return 0;    }     public static void main(String[] args) throws IOException {        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));        List<boolean[]> slots = parse(in.readLine().trim());        int n = Integer.parseInt(in.readLine().trim());        StringBuilder out = new StringBuilder();        for (int k = 0; k < n; k++) out.append(firstBad(slots, in.readLine().trim())).append('\n');        System.out.print(out);    }}
C#Main.cs · 65 行
using System;using System.Collections.Generic;using System.Text; class Program{    // 칸 하나: 글자 코드 0~127마다 받는지(ok)를 적은 표    static List<bool[]> Parse(string pattern)    {        var slots = new List<bool[]>();        int i = 0;        while (i < pattern.Length)        {            var ok = new bool[128];            if (pattern[i] != '[')            {                ok[pattern[i]] = true; // 대괄호 밖 글자는 그 글자 하나만                slots.Add(ok);                i++;                continue;            }            int end = pattern.IndexOf(']', i);            string body = pattern.Substring(i + 1, end - i - 1);            bool neg = body.StartsWith("^"); // ^는 맨 앞에 있을 때만 뒤집기            if (neg) body = body.Substring(1);            for (int j = 0; j < body.Length;)            {                if (j + 2 < body.Length && body[j + 1] == '-')                {                    for (char c = body[j]; c <= body[j + 2]; c++) ok[c] = true; // 두 끝 포함                    j += 3;                }                else                {                    ok[body[j]] = true; // 맨 앞·맨 끝의 -는 그냥 글자                    j++;                }            }            if (neg) for (int c = 0; c < 128; c++) ok[c] = !ok[c];            slots.Add(ok);            i = end + 1;        }        return slots;    }     static int FirstBad(List<bool[]> slots, string t)    {        int len = Math.Max(slots.Count, t.Length);        for (int i = 0; i < len; i++)        {            if (i >= slots.Count || i >= t.Length) return i + 1; // 글자가 모자라거나 남는다            if (!slots[i][t[i]]) return i + 1;        }        return 0;    }     static void Main()    {        var slots = Parse(Console.ReadLine().Trim());        int n = int.Parse(Console.ReadLine().Trim());        var sb = new StringBuilder();        for (int k = 0; k < n; k++) sb.Append(FirstBad(slots, Console.ReadLine().Trim())).Append('\n');        Console.Write(sb);    }}
C++main.cpp · 62 行
#include <array>#include <bitset>#include <iostream>#include <string>#include <vector> using Slot = std::bitset<128>; // 글자 코드 0~127마다 받는지 std::vector<Slot> parse(const std::string &p) {    std::vector<Slot> slots;    std::size_t i = 0;    while (i < p.size()) {        Slot s;        if (p[i] != '[') {            s.set(p[i]); // 대괄호 밖 글자는 그 글자 하나만            slots.push_back(s);            i++;            continue;        }        std::size_t end = p.find(']', i);        std::string body = p.substr(i + 1, end - i - 1);        bool neg = !body.empty() && body[0] == '^'; // ^는 맨 앞에 있을 때만 뒤집기        if (neg) body.erase(0, 1);        for (std::size_t j = 0; j < body.size();) {            if (j + 2 < body.size() && body[j + 1] == '-') {                for (int c = body[j]; c <= body[j + 2]; c++) s.set(c); // 두 끝 포함                j += 3;            } else {                s.set(body[j]); // 맨 앞·맨 끝의 -는 그냥 글자                j++;            }        }        if (neg) s.flip();        slots.push_back(s);        i = end + 1;    }    return slots;} int firstBad(const std::vector<Slot> &slots, const std::string &t) {    std::size_t m = std::max(slots.size(), t.size());    for (std::size_t i = 0; i < m; i++) {        if (i >= slots.size() || i >= t.size()) return static_cast<int>(i) + 1; // 모자라거나 남는다        if (!slots[i].test(t[i])) return static_cast<int>(i) + 1;    }    return 0;} int main() {    std::ios::sync_with_stdio(false);    std::string p, t;    int n;    std::cin >> p >> n; // 틀과 질문에는 빈칸이 없다    auto slots = parse(p);    std::string out;    for (int k = 0; k < n; k++) {        std::cin >> t;        out += std::to_string(firstBad(slots, t)) + '\n';    }    std::cout << out;    return 0;}
Cmain.c · 54 行
#include <stdio.h>#include <string.h> static char ok[100][128]; /* 칸마다 글자 코드 0~127을 받는지 */static int nslots = 0; static void parse(const char *p) {    int len = (int)strlen(p), i = 0;    while (i < len) {        char *s = ok[nslots++];        if (p[i] != '[') {            s[(unsigned char)p[i]] = 1; /* 대괄호 밖 글자는 그 글자 하나만 */            i++;            continue;        }        int b = i + 1, end = (int)(strchr(p + i, ']') - p);        int neg = p[b] == '^'; /* ^는 맨 앞에 있을 때만 뒤집기 */        if (neg) b++;        for (int j = b; j < end;) {            if (j + 2 < end && p[j + 1] == '-') {                for (int c = (unsigned char)p[j]; c <= (unsigned char)p[j + 2]; c++) s[c] = 1; /* 두 끝 포함 */                j += 3;            } else {                s[(unsigned char)p[j]] = 1; /* 맨 앞·맨 끝의 -는 그냥 글자 */                j++;            }        }        if (neg)            for (int c = 0; c < 128; c++) s[c] = !s[c];        i = end + 1;    }} static int first_bad(const char *t) {    int len = (int)strlen(t), m = len > nslots ? len : nslots;    for (int i = 0; i < m; i++) {        if (i >= nslots || i >= len) return i + 1; /* 글자가 모자라거나 남는다 */        if (!ok[i][(unsigned char)t[i]]) return i + 1;    }    return 0;} int main(void) {    char p[256], t[128];    int n;    scanf("%255s", p); /* 틀과 질문에는 빈칸이 없다 */    parse(p);    scanf("%d", &n);    for (int k = 0; k < n; k++) {        scanf("%127s", t);        printf("%d\n", first_bad(t));    }    return 0;}
Craykonmain.cray · 70 行
틀 = コンソール.行読み().空白除去();n = コンソール.行読み().数に(); // 칸마다 [뒤집기, 받는 글자 사전]을 만든다칸들 = [];i = 0;間 (i < 틀.長さ){    글자들 = 辞書();    もし (틀[i] != "[")    {        글자들[틀[i]] = 真; // 대괄호 밖 글자는 그 글자 하나만        칸들.追加([偽, 글자들]);        i = i + 1;    }    でなければ    {        끝 = i + 1;        間 (틀[끝] != "]")        {            끝 = 끝 + 1;        }        본문 = 틀.切り出す(i + 1, 끝 - i - 1);        뒤집기 = 본문.で始まる("^"); // ^는 맨 앞에 있을 때만 뒤집기        もし (뒤집기)        {            본문 = 본문.切り出す(1);        }        j = 0;        間 (j < 본문.長さ)        {            もし (j + 2 < 본문.長さ && 본문[j + 1] == "-")            {                // a-b: 코드가 a 이상 b 이하인 글자 모두(두 끝 포함)                繰り返す (c = 文字.コード(본문[j]); c <= 文字.コード(본문[j + 2]); c++)                {                    글자들[文字.コードから(c)] = 真;                }                j = j + 3;            }            でなければ            {                글자들[본문[j]] = 真; // 맨 앞·맨 끝의 -는 그냥 글자                j = j + 1;            }        }        칸들.追加([뒤집기, 글자들]);        i = 끝 + 1;    }} 繰り返す (k = 0; k < n; k++){    t = コンソール.行読み().空白除去();    답 = 0;    繰り返す (p = 0; p < 数学.大きい方(칸들.個数, t.長さ); p++)    {        もし (p >= 칸들.個数 || p >= t.長さ)        {            답 = p + 1; // 글자가 모자라거나 남는다            抜ける;        }        もし (칸들[p][1].含む(t[p]) == 칸들[p][0])        {            답 = p + 1; // 뒤집힌 칸은 들어 있으면 안 된다            抜ける;        }    }    コンソール.書く(답);}