{ }codelper
查找字符串P0356

查找下一个与查找上一个

你要实现文本编辑器的“查找下一个”和“查找上一个”。
正文是一行字符串 T,位置从最前面的字符算作 0 开始数。
光标指向正文中的一个位置,开始时在位置 0。
单词 w 出现在位置 s,是指 T 从位置 s 开始、与 w 等长的那些字符和 w 完全相同。
出现的位置彼此重叠时,也全部分别计算。
命令有三种。
N w:在 w 出现的位置中,找出大于光标的最小位置。
如果没有这样的位置,就在整个正文中找 w 出现的最小位置。
这时答案也可能就是光标所在的位置。
P w:在 w 出现的位置中,找出小于光标的最大位置。
如果没有这样的位置,就在整个正文中找 w 出现的最大位置。
这时答案同样可能就是光标所在的位置。
N 和 P 找到位置后,把光标移到那个位置,并在一行中输出这个位置。
如果这个位置是在整个正文中重新查找得到的,就在位置后面加一个空格和 *。
如果 w 在正文中一次也没有出现,输出 -1,光标保持不动。
G p:把光标移到位置 p。
不输出任何内容。

输入
第一行给出正文 T。
T 的长度为 1 ~ 5,000,只含英文小写字母和空格,开头和结尾都不是空格。
第二行给出命令数 q (1 ≤ q ≤ 1,000)。
接下来的 q 行每行一条命令,命令字母与 w 或 p 之间用一个空格隔开。
w 是 1 ~ 20 个英文小写字母,可能比正文还长。
p 是 0 以上、(T 的长度 − 1) 以下的整数。
至少有一条 N 或 P 命令
输出
每条 N、P 命令输出一行,按命令的顺序输出。
位置只写数字,不加逗号
样例 1 · stdin
banana bandana
8
N ana
N ana
P ban
P ban
G 12
P ana
N ana
N apple
stdout
1
3
0
7 *
11
1 *
-1
样例 2 · stdin
hello world
9
N world
N world
P world
P hello
G 10
N d
P d
N lo
P zz
stdout
6
6 *
6 *
0
10 *
10 *
3 *
-1
main.py
输入(stdin)

运行时这些行会依次作为 input()/标准输入传入(运行中不会等待输入)。

$ 就绪

请在电脑上打开以编写和运行代码。在手机上可以查看题目和题解。

其他语言怎么写?查看各语言题解 · 8 种语言题解

向前查找在各语言里几乎一样,但向后查找时第二个参数的含义因语言而异。
把光标记作 c。
向前查找是 Python T.find(w, c + 1),JS、TS、Java T.indexOf(w, c + 1),C# T.IndexOf(w, c + 1, StringComparison.Ordinal),C++ T.find(w, c + 1)。
找不到时 Python、JS、TS、Java、C# 返回 -1,C++ 返回 string::npos。
C 的 strstr(T + c + 1, w) 返回的不是位置而是指针,找不到时是 NULL,所以用 p - T 求位置。
陷阱一:查找的起点是 c + 1。
从 c 开始找会再次找到刚才的位置,第一个例子的第二个 N ana 会得到 1 而不是 3。
从 c 加上单词长度的位置开始找,第一个 N ana 会跳过重叠的 1 而输出 3。
陷阱二:向后查找的第二个参数。
在 JS、TS、Java 的 T.lastIndexOf(w, c - 1) 和 C++ 的 T.rfind(w, c - 1) 中,它的意思是“起始位置不超过这个值的出现”。
Python T.rfind(w, 0, 终点) 的终点是查找范围的终点,整个单词必须落在它之前,所以要传 c - 1 + len(w)。
写成 T.rfind(w, 0, c) 的话,第一个例子中 G 12 之后的 P ana 会漏掉跨过光标的 11 而输出 3。
C# T.LastIndexOf(w, i, StringComparison.Ordinal) 的 i 是开始向后查找的那个字符的位置,单词的最后一个字符不能超过 i。
所以要传 c + w.Length - 2(超过正文末尾时传 T.Length - 1);传 c - 1 的话在同一处会得到 3。
陷阱三:光标为 0 时的 c - 1。
JS、TS 的 lastIndexOf 会把负数改成 0,于是找到位置 0 的单词。
第一个例子的第二个 P ban 会输出 0 而不是 7 *。
C++ 的位置是无符号的 size_t,0 - 1 变成最大的值,于是在整个正文中查找,输出不带 * 的 7。
C# 传入 -1 会因 ArgumentOutOfRangeException 而停止。
Java 对负数返回 -1,所以不需要另外判断。
陷阱四:位置 0 也算找到。
用 k > 0 判断的话,第一个例子的第一个 P ban 找到的 0 会被当成没找到,输出 7 *。
是否没找到要用 k < 0(C++ 用 k == string::npos)来判断。
陷阱五:重新查找时要看整个正文。
如果只在光标之前重新查找,像第二个例子的第二个 N world 那样单词只出现在光标位置时,会输出 -1 而不是 6 *。
C 没有向后查找的函数,所以把位置一个个往下移,用 strncmp 比较。
Craykon 的 查找位置 不接受起始位置。
向前查找时先用 正文.截取(c + 1).查找位置(w) 截开再找,然后加上 c + 1。
向后查找时在 正文.反转() 中从前面查找 w.反转(),再用 n - r - 单词长度 把找到的位置 r 换算回来。

Pythonmain.py · 37 行
import sys  def main():    lines = sys.stdin.read().split("\n")    text = lines[0]    q = int(lines[1])    cur = 0    out = []    for line in lines[2:2 + q]:        kind, arg = line.split()        if kind == "G":            cur = int(arg)            continue        wrapped = False        if kind == "N":            # 커서 다음 칸부터 찾는다            k = text.find(arg, cur + 1)            if k < 0:                k = text.find(arg)                wrapped = True        else:            # rfind의 끝 범위는 '시작 자리'가 아니라 '찾을 구간의 끝'이다.            # 시작 자리가 cur - 1 이하이려면 구간은 cur - 1 + len(arg)에서 끝난다            k = text.rfind(arg, 0, cur - 1 + len(arg))            if k < 0:                k = text.rfind(arg)                wrapped = True        if k < 0:            out.append("-1")        else:            cur = k            out.append(f"{k} *" if wrapped else str(k))    print("\n".join(out))  main()
JavaScriptmain.js · 35 行
const lines = require("fs").readFileSync(0, "utf8").split("\n");const text = lines[0];const q = Number(lines[1]);let cur = 0;const out = [];for (let i = 2; i < 2 + q; i++) {  const [kind, arg] = lines[i].split(" ");  if (kind === "G") {    cur = Number(arg);    continue;  }  let wrapped = false;  let k;  if (kind === "N") {    k = text.indexOf(arg, cur + 1);    if (k < 0) {      k = text.indexOf(arg);      wrapped = true;    }  } else {    // lastIndexOf(w, -1)은 -1을 0으로 고쳐 0번 자리를 찾아 버리니 cur가 0일 때는 부르지 않는다    k = cur > 0 ? text.lastIndexOf(arg, cur - 1) : -1;    if (k < 0) {      k = text.lastIndexOf(arg);      wrapped = true;    }  }  if (k < 0) {    out.push("-1");  } else {    cur = k;    out.push(wrapped ? `${k} *` : String(k));  }}console.log(out.join("\n"));
TypeScriptmain.ts · 35 行
const lines: string[] = require("fs").readFileSync(0, "utf8").split("\n");const text: string = lines[0];const q: number = Number(lines[1]);let cur = 0;const out: string[] = [];for (let i = 2; i < 2 + q; i++) {  const [kind, arg] = lines[i].split(" ");  if (kind === "G") {    cur = Number(arg);    continue;  }  let wrapped = false;  let k: number;  if (kind === "N") {    k = text.indexOf(arg, cur + 1);    if (k < 0) {      k = text.indexOf(arg);      wrapped = true;    }  } else {    // lastIndexOf(w, -1)은 -1을 0으로 고쳐 0번 자리를 찾아 버리니 cur가 0일 때는 부르지 않는다    k = cur > 0 ? text.lastIndexOf(arg, cur - 1) : -1;    if (k < 0) {      k = text.lastIndexOf(arg);      wrapped = true;    }  }  if (k < 0) {    out.push("-1");  } else {    cur = k;    out.push(wrapped ? `${k} *` : String(k));  }}console.log(out.join("\n"));
JavaMain.java · 44 行
import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader; public class Main {    public static void main(String[] args) throws IOException {        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));        String text = br.readLine();        int q = Integer.parseInt(br.readLine().trim());        int cur = 0;        StringBuilder out = new StringBuilder();        for (int i = 0; i < q; i++) {            String[] parts = br.readLine().split(" ");            String arg = parts[1];            if (parts[0].equals("G")) {                cur = Integer.parseInt(arg);                continue;            }            boolean wrapped = false;            int k;            if (parts[0].equals("N")) {                k = text.indexOf(arg, cur + 1);                if (k < 0) {                    k = text.indexOf(arg);                    wrapped = true;                }            } else {                // 둘째 값은 '시작 자리의 상한'이다. 음수를 주면 그냥 -1이 나온다                k = text.lastIndexOf(arg, cur - 1);                if (k < 0) {                    k = text.lastIndexOf(arg);                    wrapped = true;                }            }            if (k < 0) {                out.append("-1\n");            } else {                cur = k;                out.append(k).append(wrapped ? " *\n" : "\n");            }        }        System.out.print(out);    }}
C#Main.cs · 57 行
using System;using System.Text; class Program{    static void Main()    {        string text = Console.ReadLine();        int n = text.Length;        int q = int.Parse(Console.ReadLine());        int cur = 0;        var sb = new StringBuilder();        for (int i = 0; i < q; i++)        {            string[] parts = Console.ReadLine().Split(' ');            string arg = parts[1];            if (parts[0] == "G")            {                cur = int.Parse(arg);                continue;            }            bool wrapped = false;            int k;            if (parts[0] == "N")            {                k = text.IndexOf(arg, cur + 1, StringComparison.Ordinal);                if (k < 0)                {                    k = text.IndexOf(arg, StringComparison.Ordinal);                    wrapped = true;                }            }            else            {                // LastIndexOf의 둘째 값은 '거꾸로 찾기 시작하는 글자'다.                // 찾은 낱말의 끝 글자가 그 자리를 넘지 못하므로, 시작 자리가 cur - 1 이하가 되게 하려면                // cur - 1에서 시작한 낱말의 끝 글자 자리인 cur + 길이 - 2를 준다                k = cur > 0 ? text.LastIndexOf(arg, Math.Min(n - 1, cur + arg.Length - 2), StringComparison.Ordinal) : -1;                if (k < 0)                {                    k = text.LastIndexOf(arg, StringComparison.Ordinal);                    wrapped = true;                }            }            if (k < 0)            {                sb.Append("-1\n");            }            else            {                cur = k;                sb.Append(k).Append(wrapped ? " *\n" : "\n");            }        }        Console.Write(sb);    }}
C++main.cpp · 46 行
#include <iostream>#include <string>using namespace std; int main() {    ios::sync_with_stdio(false);    cin.tie(nullptr);    string text;    getline(cin, text);    int q;    cin >> q;    size_t cur = 0;    for (int i = 0; i < q; i++) {        char kind;        string arg;        cin >> kind >> arg;        if (kind == 'G') {            cur = stoul(arg);            continue;        }        bool wrapped = false;        size_t k;        if (kind == 'N') {            k = text.find(arg, cur + 1);            if (k == string::npos) {                k = text.find(arg);                wrapped = true;            }        } else {            // rfind의 둘째 값은 '시작 자리의 상한'이다.            // cur가 0일 때 cur - 1은 size_t에서 가장 큰 값(npos)이 되어 전체를 찾아 버리니 따로 막는다            k = cur > 0 ? text.rfind(arg, cur - 1) : string::npos;            if (k == string::npos) {                k = text.rfind(arg);                wrapped = true;            }        }        if (k == string::npos) {            cout << -1 << '\n';        } else {            cur = k;            cout << k << (wrapped ? " *\n" : "\n");        }    }    return 0;}
Cmain.c · 58 行
#include <stdio.h>#include <stdlib.h>#include <string.h> static char text[5005]; /* 시작 자리가 hi 이하인 것 가운데 가장 뒤의 자리. 없으면 -1.   C에는 거꾸로 찾는 strstr가 없어서 자리를 하나씩 내려가며 견준다 */static int find_last(int n, const char *w, int len, int hi) {    if (hi > n - len) hi = n - len;    for (int s = hi; s >= 0; s--) {        if (strncmp(text + s, w, (size_t)len) == 0) return s;    }    return -1;} int main(void) {    if (!fgets(text, sizeof text, stdin)) return 0;    text[strcspn(text, "\n")] = '\0';    int n = (int)strlen(text);    int q;    if (scanf("%d", &q) != 1) return 0;    int cur = 0;    for (int i = 0; i < q; i++) {        char kind;        char arg[32];        if (scanf(" %c %30s", &kind, arg) != 2) return 0;        if (kind == 'G') {            cur = atoi(arg);            continue;        }        int len = (int)strlen(arg);        int wrapped = 0;        int k;        if (kind == 'N') {            /* strstr는 자리가 아니라 포인터를 돌려준다. 없으면 NULL */            const char *p = strstr(text + cur + 1, arg);            if (p == NULL) {                p = strstr(text, arg);                wrapped = 1;            }            k = p == NULL ? -1 : (int)(p - text);        } else {            k = find_last(n, arg, len, cur - 1);            if (k < 0) {                k = find_last(n, arg, len, n - 1);                wrapped = 1;            }        }        if (k < 0) {            printf("-1\n");        } else {            cur = k;            printf(wrapped ? "%d *\n" : "%d\n", k);        }    }    return 0;}
Craykonmain.cray · 74 行
본문 = 控制台.读行();n = 본문.长度;// 거꾸로 찾는 기능이 없어서, 뒤집은 본문에서 뒤집은 낱말을 앞에서부터 찾는다거꾸로 = 본문.反转();q = 控制台.读行().转数字();커서 = 0;循环 (i = 0; i < q; i++){    칸 = 控制台.读行().分割(" ");    如果 (칸[0] == "G")    {        커서 = 칸[1].转数字();        继续;    }    낱말 = 칸[1];    길 = 낱말.长度;    넘김 = 假;    k = -1;    如果 (칸[0] == "N")    {        // 자리찾기에는 시작 자리를 줄 수 없어서, 커서 다음부터 잘라 찾고 잘라 낸 만큼 더한다        k = 본문.截取(커서 + 1).查找位置(낱말);        如果 (k >= 0)        {            k += 커서 + 1;        }        否则        {            k = 본문.查找位置(낱말);            넘김 = 真;        }    }    否则    {        뒤낱말 = 낱말.反转();        // 본문의 s번 자리에서 시작한 낱말은 뒤집은 본문의 n - s - 길 자리에서 시작한다.        // s가 커서 - 1 이하이면 그 자리는 n - 커서 + 1 - 길 이상이다        시작 = 数学.较大值(0, n - 커서 + 1 - 길);        r = -1;        如果 (시작 <= n)        {            r = 거꾸로.截取(시작).查找位置(뒤낱말);        }        如果 (r >= 0)        {            k = n - (r + 시작) - 길;        }        否则        {            r = 거꾸로.查找位置(뒤낱말);            넘김 = 真;            如果 (r >= 0)            {                k = n - r - 길;            }        }    }    如果 (k < 0)    {        控制台.输出("-1");    }    否则    {        커서 = k;        如果 (넘김)        {            控制台.输出(k + " *");        }        否则        {            控制台.输出(k + "");        }    }}