{ }codelper
不换行输出模拟P0271

进度条留下的字符

给出某个程序在终端屏幕上写字时使用的输出命令记录。
屏幕一开始只有一个空行,光标在这一行的第 1 格。
一行中的格子从左往右数作第 1、2、3、… 格。
命令有以下六种。
write s 不换行,从左到右逐个写出字符串 s 的字符。
写一个字符时,如果光标所在的格子已经有字符,就用新字符覆盖它;如果光标在这一行最后一个字符的紧后面,就把字符接在行末。
然后光标向右移一格。
任何命令都不会把光标移到这一行最后一个字符紧后面再往右的位置,所以写字符时总是这两种情况之一。
print s 先做 write s,再做 nl。
nl 在当前行下面新建一个空行,并把光标移到新行的第 1 格。
cr 把光标移到当前行的第 1 格。
不擦除任何字符。
back k 把光标向左移 k 格。
光标不会移到第 1 格的左边,而是停在第 1 格。
不擦除任何字符。
erase 擦除当前行中光标所在格及其右边的所有字符。
光标不动。
光标总是在最下面一行。
第一行给出命令数 q,接下来 q 行每行一条命令。
命令名与 s 或 k 之间隔一个空格。
处理完所有命令后,从上到下逐行输出屏幕上的每一行。
没有字符的行输出为空行。
但如果最下面一行是空的,就不输出这一行。

输入
1 ≤ q ≤ 2,000。
s 是由非空格 ASCII 字符(编码 33~126)组成的 1~30 个字符的字符串。
1 ≤ k ≤ 1,000。
任何一行都不超过 1,000 个字符。
屏幕上至少留下一个字符。
输出
屏幕上的各行
样例 1 · stdin
7
write loading...
cr
write 50%
cr
write done
nl
print bye
stdout
doneing...
bye
样例 2 · stdin
12
write abcdef
back 2
write XY
write Z
back 100
write 1
nl
nl
write hello
back 3
erase
write p!
stdout
1bcdXYZ

hep!
main.py
输入(stdin)

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

$ 就绪

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

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

不换行地输出:Python print(s, end="")・sys.stdout.write(s),JS・TS process.stdout.write(s)(console.log 总会换行),Java System.out.print(换行的是 println),C# Console.Write(换行的是 WriteLine),C fputs(s, stdout)・printf("%s", s),C++ std::cout << s(加上 std::endl 或 '\n' 才换行),Craykon 控制台.接着输出(s)(控制台.输出 会换行)。
本题的 cr 就是真实终端里的 \r。
人们常用 print(f"\r{p}%", end="") 这样的写法在一行里刷新进度,陷阱 ① \r 只移动光标,并不清空这一行。
用较短的文字覆盖时,较长文字的尾巴会留下来:在 loading... 上写 done 会看到 doneing...。
实际程序会用空格补齐,或发送相当于 erase 的 \x1b[K。
② back 停在第 1 格。
如果把光标存成从 0 开始的数直接相减,Python 的 cur[-1] 是最后一格,会不报错地覆盖错误的字符;C・C++ 会写到数组外面。
C++ 中光标用 size_t 的话没有负数,会变成很大的数,所以要先比较再相减。
③ erase 只擦除光标格及其右边:Python del cur[col:],JS・TS cur.length = col,Java setLength(col),C# Length = col,C++ resize(col),Craykon 子列表(0, col)。
④ 覆盖不是插入。
用 C++ 或 Python 的 insert 会把后面的字符往右挤。
⑤ 名字容易混:本题的 print 会换行,而 Java 的 print、C# 的 Write 不换行。
⑥ C 里把攒好的行用 printf(cur) 输出时,% 会被当成格式,50%d… 就乱了。
请用 fputs 或 printf("%s", cur)。

Pythonmain.py · 46 行
import sys lines = sys.stdin.read().split("\n")q = int(lines[0])screen = []  # 다 쓴 줄들cur = []     # 지금 줄의 글자들col = 0      # 커서 칸(0부터)  def write(s):    global col    for ch in s:        if col < len(cur):            cur[col] = ch  # 글자가 있으면 덮어쓴다        else:            cur.append(ch)  # 줄 끝 바로 뒤면 붙인다        col += 1  def newline():    global cur, col    screen.append("".join(cur))    cur = []    col = 0  for line in lines[1:1 + q]:    p = line.split()    cmd = p[0]    if cmd == "write":        write(p[1])    elif cmd == "print":        write(p[1])        newline()    elif cmd == "nl":        newline()    elif cmd == "cr":        col = 0  # 글자는 그대로 두고 커서만 맨 앞으로    elif cmd == "back":        col = max(0, col - int(p[1]))  # 음수 자리는 맨 끝 칸이 되니 0에서 멈춘다    else:  # erase        del cur[col:] if cur:    screen.append("".join(cur))sys.stdout.write("".join(row + "\n" for row in screen))
JavaScriptmain.js · 33 行
const lines = require("fs").readFileSync(0, "utf8").split("\n");const q = Number(lines[0]);const screen = []; // 다 쓴 줄들let cur = []; // 지금 줄의 글자들let col = 0; // 커서 칸(0부터) function write(s) {  for (const ch of s) {    cur[col] = ch; // 글자가 있으면 덮어쓰고, 줄 끝 바로 뒤면 붙인다    col++;  }} function newline() {  screen.push(cur.join(""));  cur = [];  col = 0;} for (const line of lines.slice(1, 1 + q)) {  const [cmd, arg] = line.trim().split(" ");  if (cmd === "write") write(arg);  else if (cmd === "print") {    write(arg);    newline();  } else if (cmd === "nl") newline();  else if (cmd === "cr") col = 0; // 글자는 그대로, 커서만 맨 앞으로  else if (cmd === "back") col = Math.max(0, col - Number(arg));  else cur.length = col; // erase: 커서 칸부터 뒤를 잘라 낸다}if (cur.length > 0) screen.push(cur.join(""));// console.log는 늘 줄을 바꾼다. 줄 바꿈 없이 쓰려면 process.stdout.writeprocess.stdout.write(screen.map((row) => row + "\n").join(""));
TypeScriptmain.ts · 35 行
import { readFileSync } from "node:fs"; const lines: string[] = readFileSync(0, "utf8").split("\n");const q = Number(lines[0]);const screen: string[] = []; // 다 쓴 줄들let cur: string[] = []; // 지금 줄의 글자들let col = 0; // 커서 칸(0부터) function write(s: string): void {  for (const ch of s) {    cur[col] = ch; // 글자가 있으면 덮어쓰고, 줄 끝 바로 뒤면 붙인다    col++;  }} function newline(): void {  screen.push(cur.join(""));  cur = [];  col = 0;} for (const line of lines.slice(1, 1 + q)) {  const [cmd, arg] = line.trim().split(" ");  if (cmd === "write") write(arg);  else if (cmd === "print") {    write(arg);    newline();  } else if (cmd === "nl") newline();  else if (cmd === "cr") col = 0; // 글자는 그대로, 커서만 맨 앞으로  else if (cmd === "back") col = Math.max(0, col - Number(arg));  else cur.length = col; // erase: 커서 칸부터 뒤를 잘라 낸다}if (cur.length > 0) screen.push(cur.join(""));// console.log는 늘 줄을 바꾼다. 줄 바꿈 없이 쓰려면 process.stdout.writeprocess.stdout.write(screen.map((row) => row + "\n").join(""));
JavaMain.java · 40 行
import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader;import java.io.PrintWriter; public class Main {    static StringBuilder cur = new StringBuilder(); // 지금 줄    static int col = 0; // 커서 칸(0부터)     static void write(String s) {        for (char ch : s.toCharArray()) {            if (col < cur.length()) cur.setCharAt(col, ch); // 덮어쓴다            else cur.append(ch); // 줄 끝 바로 뒤면 붙인다            col++;        }    }     public static void main(String[] args) throws IOException {        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));        PrintWriter out = new PrintWriter(System.out);        int q = Integer.parseInt(in.readLine().trim());        for (int t = 0; t < q; t++) {            String[] p = in.readLine().trim().split(" ");            switch (p[0]) {                case "write" -> write(p[1]);                case "print", "nl" -> {                    if (p[0].equals("print")) write(p[1]);                    out.println(cur); // 다 쓴 줄은 바로 내보낸다                    cur.setLength(0);                    col = 0;                }                case "cr" -> col = 0; // 글자는 그대로, 커서만 맨 앞으로                case "back" -> col = Math.max(0, col - Integer.parseInt(p[1]));                default -> cur.setLength(col); // erase            }        }        if (cur.length() > 0) out.println(cur);        out.flush();    }}
C#Main.cs · 58 行
using System;using System.IO;using System.Text; class Program{    static StringBuilder cur = new StringBuilder(); // 지금 줄    static int col = 0; // 커서 칸(0부터)     static void Write(string s)    {        foreach (char ch in s)        {            if (col < cur.Length) cur[col] = ch; // 덮어쓴다            else cur.Append(ch); // 줄 끝 바로 뒤면 붙인다            col++;        }    }     static void Main()    {        var output = new StreamWriter(Console.OpenStandardOutput());        int q = int.Parse(Console.ReadLine().Trim());        for (int t = 0; t < q; t++)        {            string[] p = Console.ReadLine().Trim().Split(' ');            switch (p[0])            {                case "write":                    Write(p[1]);                    break;                case "print":                case "nl":                    if (p[0] == "print") Write(p[1]);                    output.Write(cur.ToString());                    output.Write('\n');                    cur.Clear();                    col = 0;                    break;                case "cr":                    col = 0; // 글자는 그대로, 커서만 맨 앞으로                    break;                case "back":                    col = Math.Max(0, col - int.Parse(p[1]));                    break;                default: // erase                    cur.Length = col;                    break;            }        }        if (cur.Length > 0)        {            output.Write(cur.ToString());            output.Write('\n');        }        output.Flush();    }}
C++main.cpp · 41 行
#include <algorithm>#include <iostream>#include <string> int main() {    std::ios::sync_with_stdio(false);    int q;    std::cin >> q;    std::string cur; // 지금 줄    std::size_t col = 0; // 커서 칸(0부터)    std::string out;    for (int t = 0; t < q; t++) {        std::string cmd, arg;        std::cin >> cmd;        if (cmd == "write" || cmd == "print") {            std::cin >> arg;            for (char ch : arg) {                if (col < cur.size()) cur[col] = ch; // 덮어쓴다                else cur += ch; // 줄 끝 바로 뒤면 붙인다                col++;            }        }        if (cmd == "print" || cmd == "nl") {            out += cur;            out += '\n';            cur.clear();            col = 0;        } else if (cmd == "cr") {            col = 0; // 글자는 그대로, 커서만 맨 앞으로        } else if (cmd == "back") {            std::size_t k;            std::cin >> k;            col = col > k ? col - k : 0; // size_t는 음수가 없어서 빼기 전에 비교한다        } else if (cmd == "erase") {            cur.resize(col);        }    }    if (!cur.empty()) out += cur + '\n';    std::cout << out;    return 0;}
Cmain.c · 45 行
#include <stdio.h>#include <string.h> static char cur[1001]; /* 지금 줄(한 줄은 1,000칸 이하) */static int len = 0, col = 0; /* 줄 길이, 커서 칸(0부터) */ static void write_text(const char *s) {    for (; *s; s++) {        cur[col++] = *s; /* 덮어쓰거나, 줄 끝 바로 뒤면 붙인다 */        if (col > len) len = col;    }} static void newline(void) {    cur[len] = '\0';    fputs(cur, stdout); /* printf(cur)는 안 된다: %를 서식으로 읽는다 */    putchar('\n');    len = col = 0;} int main(void) {    int q;    char cmd[8], arg[32];    scanf("%d", &q);    for (int t = 0; t < q; t++) {        scanf("%7s", cmd);        if (strcmp(cmd, "write") == 0 || strcmp(cmd, "print") == 0) {            scanf("%31s", arg);            write_text(arg);            if (cmd[0] == 'p') newline();        } else if (strcmp(cmd, "nl") == 0) {            newline();        } else if (strcmp(cmd, "cr") == 0) {            col = 0;        } else if (strcmp(cmd, "back") == 0) {            int k;            scanf("%d", &k);            col = col > k ? col - k : 0;        } else { /* erase */            len = col;        }    }    if (len > 0) newline();    return 0;}
Craykonmain.cray · 57 行
q = 控制台.读行().转数字();cur = []; // 지금 줄의 글자들col = 0; // 커서 칸(0부터) 쓰기(s){    遍历 (ch 在 s)    {        如果 (col < cur.数量)        {            cur[col] = ch; // 글자가 있으면 덮어쓴다        }        否则        {            cur.添加(ch); // 줄 끝 바로 뒤면 붙인다        }        col = col + 1;    }} 줄바꾸기(){    控制台.输出(cur.连接(""));    cur = [];    col = 0;} 循环 (t = 0; t < q; t++){    p = 控制台.读行().去空白().分割(" ");    选择 (p[0])    {        情况 "write":            쓰기(p[1]);            跳出;        情况 "print":            쓰기(p[1]);            줄바꾸기();            跳出;        情况 "nl":            줄바꾸기();            跳出;        情况 "cr":            col = 0; // 글자는 그대로, 커서만 맨 앞으로            跳出;        情况 "back":            col = 数学.较大值(0, col - p[1].转数字());            跳出;        默认:            cur = cur.子列表(0, col); // erase: 커서 칸부터 뒤를 버린다            跳出;    }}如果 (cur.数量 > 0){    控制台.输出(cur.连接(""));}