{ }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; // 뒤집힌 칸은 들어 있으면 안 된다            跳出;        }    }    控制台.输出(답);}