命令行解析与出现次数统计

题意

给定一个命令行模板字符串,由关键词(纯小写字母)、选择结构 {A | B | ...} 和可选结构 [C] 组成,支持嵌套。

需要完成两件事:

  1. 找出模板最外层(不被任何 {}[] 包裹)的所有关键词,按出现顺序输出(同一个词出现多次就输出多次)。
  2. 对每个关键词,计算它在任意合法命令中保证出现的最少次数

计算规则:

  • 顶层直接出现的关键词,保证出现 1 次。
  • 选择结构 {A | B | C}:如果某个关键词在每个分支里都保证出现,那么取各分支中的最小保证次数,加到结果里。
  • 可选结构 [...]:里面的内容可以不出现,所以不贡献任何保证次数。

思路

这道题本质上是一个递归下降解析问题。模板字符串有嵌套结构,天然适合用递归来处理。

先想清楚每一层要返回什么。对于模板中的一段序列,我们需要返回一个映射:每个关键词在这段序列中的"保证出现次数"。

然后分三种情况讨论:

  1. 纯关键词:直接计数 +1。
  2. 选择结构 {...}:用 | 分隔出若干分支,递归解析每个分支。一个关键词要想"保证出现",它必须在每个分支中都保证出现——取所有分支中的最小值。
  3. 可选结构 [...]:递归解析里面的内容,但结果直接丢弃,因为可选意味着可以不出现。

解析器的实现就是标准的递归下降。维护一个全局指针 pos,遇到 { 就进入选择结构的解析,遇到 [ 就进入可选结构的解析,遇到 }]| 就返回当前结果。

至于输出的第一行(固定关键词列表),单独做一趟扫描就行:维护一个深度计数器,遇到 {[ 加 1,遇到 }] 减 1,只在深度为 0 时收集关键词。注意同一个关键词如果在顶层出现了多次,要重复输出。

时间复杂度 ,其中 是模板字符串长度,每个 token 最多被访问常数次。

代码

#include <bits/stdc++.h>
using namespace std;

vector<string> tokenize(const string& s) {
    vector<string> tokens;
    string cur;
    for (char c : s) {
        if (c == '{' || c == '}' || c == '[' || c == ']' || c == '|') {
            if (!cur.empty()) { tokens.push_back(cur); cur.clear(); }
            tokens.push_back(string(1, c));
        } else if (c == ' ') {
            if (!cur.empty()) { tokens.push_back(cur); cur.clear(); }
        } else {
            cur += c;
        }
    }
    if (!cur.empty()) tokens.push_back(cur);
    return tokens;
}

int pos;
vector<string> tokens;

bool isKeyword(const string& s) {
    for (char c : s) if (!islower(c)) return false;
    return !s.empty();
}

map<string, int> parseSequence(bool stopOnPipe, bool stopOnBrace, bool stopOnBracket) {
    map<string, int> res;
    while (pos < (int)tokens.size()) {
        const string& t = tokens[pos];
        if (stopOnPipe && t == "|") break;
        if (stopOnBrace && t == "}") break;
        if (stopOnBracket && t == "]") break;

        if (t == "{") {
            pos++;
            vector<map<string, int>> options;
            while (true) {
                auto opt = parseSequence(true, true, false);
                options.push_back(opt);
                if (pos < (int)tokens.size() && tokens[pos] == "|") pos++;
                else break;
            }
            if (pos < (int)tokens.size() && tokens[pos] == "}") pos++;

            set<string> allKeys;
            for (auto& opt : options)
                for (auto& p : opt) allKeys.insert(p.first);
            for (const string& key : allKeys) {
                int mn = INT_MAX;
                for (auto& opt : options) {
                    auto it = opt.find(key);
                    if (it == opt.end()) { mn = 0; break; }
                    mn = min(mn, it->second);
                }
                if (mn > 0) res[key] += mn;
            }
        } else if (t == "[") {
            pos++;
            parseSequence(false, false, true);
            if (pos < (int)tokens.size() && tokens[pos] == "]") pos++;
        } else if (isKeyword(t)) {
            res[t]++;
            pos++;
        } else {
            pos++;
        }
    }
    return res;
}

int main() {
    string line;
    getline(cin, line);
    tokens = tokenize(line);

    vector<string> fixed;
    int depth = 0;
    for (const string& t : tokens) {
        if (t == "{" || t == "[") depth++;
        else if (t == "}" || t == "]") depth--;
        else if (depth == 0 && isKeyword(t)) fixed.push_back(t);
    }

    pos = 0;
    auto res = parseSequence(false, false, false);

    for (int i = 0; i < (int)fixed.size(); i++) {
        if (i) cout << " ";
        cout << fixed[i];
    }
    cout << "\n";
    for (int i = 0; i < (int)fixed.size(); i++) {
        if (i) cout << " ";
        cout << res[fixed[i]];
    }
    cout << "\n";
}
import java.util.*;

public class Main {
    static List<String> tokens;
    static int pos;

    static List<String> tokenize(String s) {
        List<String> res = new ArrayList<>();
        StringBuilder cur = new StringBuilder();
        for (char c : s.toCharArray()) {
            if (c == '{' || c == '}' || c == '[' || c == ']' || c == '|') {
                if (cur.length() > 0) { res.add(cur.toString()); cur.setLength(0); }
                res.add(String.valueOf(c));
            } else if (c == ' ') {
                if (cur.length() > 0) { res.add(cur.toString()); cur.setLength(0); }
            } else {
                cur.append(c);
            }
        }
        if (cur.length() > 0) res.add(cur.toString());
        return res;
    }

    static boolean isKeyword(String s) {
        if (s.isEmpty()) return false;
        for (char c : s.toCharArray()) if (c < 'a' || c > 'z') return false;
        return true;
    }

    static Map<String, Integer> parseSequence(boolean stopPipe, boolean stopBrace, boolean stopBracket) {
        Map<String, Integer> res = new HashMap<>();
        while (pos < tokens.size()) {
            String t = tokens.get(pos);
            if (stopPipe && t.equals("|")) break;
            if (stopBrace && t.equals("}")) break;
            if (stopBracket && t.equals("]")) break;

            if (t.equals("{")) {
                pos++;
                List<Map<String, Integer>> options = new ArrayList<>();
                while (true) {
                    Map<String, Integer> opt = parseSequence(true, true, false);
                    options.add(opt);
                    if (pos < tokens.size() && tokens.get(pos).equals("|")) pos++;
                    else break;
                }
                if (pos < tokens.size() && tokens.get(pos).equals("}")) pos++;

                Set<String> allKeys = new HashSet<>();
                for (Map<String, Integer> opt : options) allKeys.addAll(opt.keySet());
                for (String key : allKeys) {
                    int mn = Integer.MAX_VALUE;
                    for (Map<String, Integer> opt : options) {
                        if (!opt.containsKey(key)) { mn = 0; break; }
                        mn = Math.min(mn, opt.get(key));
                    }
                    if (mn > 0) res.merge(key, mn, Integer::sum);
                }
            } else if (t.equals("[")) {
                pos++;
                parseSequence(false, false, true);
                if (pos < tokens.size() && tokens.get(pos).equals("]")) pos++;
            } else if (isKeyword(t)) {
                res.merge(t, 1, Integer::sum);
                pos++;
            } else {
                pos++;
            }
        }
        return res;
    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        String line = sc.nextLine();
        tokens = tokenize(line);

        List<String> fixed = new ArrayList<>();
        int depth = 0;
        for (String t : tokens) {
            if (t.equals("{") || t.equals("[")) depth++;
            else if (t.equals("}") || t.equals("]")) depth--;
            else if (depth == 0 && isKeyword(t)) fixed.add(t);
        }

        pos = 0;
        Map<String, Integer> res = parseSequence(false, false, false);

        StringBuilder sb1 = new StringBuilder(), sb2 = new StringBuilder();
        for (int i = 0; i < fixed.size(); i++) {
            if (i > 0) { sb1.append(" "); sb2.append(" "); }
            sb1.append(fixed.get(i));
            sb2.append(res.getOrDefault(fixed.get(i), 0));
        }
        System.out.println(sb1);
        System.out.println(sb2);
    }
}
def tokenize(s):
    tokens = []
    cur = []
    for c in s:
        if c in '{|}[]':
            if cur:
                tokens.append(''.join(cur))
                cur = []
            tokens.append(c)
        elif c == ' ':
            if cur:
                tokens.append(''.join(cur))
                cur = []
        else:
            cur.append(c)
    if cur:
        tokens.append(''.join(cur))
    return tokens

def is_keyword(s):
    return s and all('a' <= c <= 'z' for c in s)

def parse_sequence(tokens, pos, stop_pipe, stop_brace, stop_bracket):
    res = {}
    while pos < len(tokens):
        t = tokens[pos]
        if stop_pipe and t == '|': break
        if stop_brace and t == '}': break
        if stop_bracket and t == ']': break

        if t == '{':
            pos += 1
            options = []
            while True:
                opt, pos = parse_sequence(tokens, pos, True, True, False)
                options.append(opt)
                if pos < len(tokens) and tokens[pos] == '|':
                    pos += 1
                else:
                    break
            if pos < len(tokens) and tokens[pos] == '}':
                pos += 1

            all_keys = set()
            for opt in options:
                all_keys.update(opt.keys())
            for key in all_keys:
                mn = float('inf')
                for opt in options:
                    if key not in opt:
                        mn = 0
                        break
                    mn = min(mn, opt[key])
                if mn > 0:
                    res[key] = res.get(key, 0) + mn

        elif t == '[':
            pos += 1
            _, pos = parse_sequence(tokens, pos, False, False, True)
            if pos < len(tokens) and tokens[pos] == ']':
                pos += 1

        elif is_keyword(t):
            res[t] = res.get(t, 0) + 1
            pos += 1
        else:
            pos += 1

    return res, pos

line = input()
tokens = tokenize(line)

fixed = []
depth = 0
for t in tokens:
    if t in ('{', '['):
        depth += 1
    elif t in ('}', ']'):
        depth -= 1
    elif depth == 0 and is_keyword(t):
        fixed.append(t)

res, _ = parse_sequence(tokens, 0, False, False, False)

print(' '.join(fixed))
print(' '.join(str(res.get(k, 0)) for k in fixed))
const readline = require('readline');
const rl = readline.createInterface({ input: process.stdin });

rl.on('line', (line) => {
    const tokens = tokenize(line.trim());

    const fixed = [];
    let depth = 0;
    for (const t of tokens) {
        if (t === '{' || t === '[') depth++;
        else if (t === '}' || t === ']') depth--;
        else if (depth === 0 && isKeyword(t)) fixed.push(t);
    }

    const [res] = parseSequence(tokens, 0, false, false, false);

    console.log(fixed.join(' '));
    console.log(fixed.map(k => res[k] || 0).join(' '));
    rl.close();
});

function tokenize(s) {
    const tokens = [];
    let cur = '';
    for (const c of s) {
        if ('{|}[]'.includes(c)) {
            if (cur) { tokens.push(cur); cur = ''; }
            tokens.push(c);
        } else if (c === ' ') {
            if (cur) { tokens.push(cur); cur = ''; }
        } else {
            cur += c;
        }
    }
    if (cur) tokens.push(cur);
    return tokens;
}

function isKeyword(s) {
    return s.length > 0 && /^[a-z]+$/.test(s);
}

function parseSequence(tokens, pos, stopPipe, stopBrace, stopBracket) {
    const res = {};
    while (pos < tokens.length) {
        const t = tokens[pos];
        if (stopPipe && t === '|') break;
        if (stopBrace && t === '}') break;
        if (stopBracket && t === ']') break;

        if (t === '{') {
            pos++;
            const options = [];
            while (true) {
                const [opt, newPos] = parseSequence(tokens, pos, true, true, false);
                options.push(opt);
                pos = newPos;
                if (pos < tokens.length && tokens[pos] === '|') pos++;
                else break;
            }
            if (pos < tokens.length && tokens[pos] === '}') pos++;

            const allKeys = new Set();
            for (const opt of options)
                for (const key of Object.keys(opt)) allKeys.add(key);
            for (const key of allKeys) {
                let mn = Infinity;
                for (const opt of options) {
                    if (!(key in opt)) { mn = 0; break; }
                    mn = Math.min(mn, opt[key]);
                }
                if (mn > 0) res[key] = (res[key] || 0) + mn;
            }
        } else if (t === '[') {
            pos++;
            const [, newPos] = parseSequence(tokens, pos, false, false, true);
            pos = newPos;
            if (pos < tokens.length && tokens[pos] === ']') pos++;
        } else if (isKeyword(t)) {
            res[t] = (res[t] || 0) + 1;
            pos++;
        } else {
            pos++;
        }
    }
    return [res, pos];
}