package main // input-scan-cheatsheet.go // ACM/笔试 输入解析速查表(Go)。所有模板均经本机 go1.26.3 实测通过。 // 来源:掘金 ACM 模板(starine, 2022) + CSDN fmt/bufio 对比 + 本机实测。 // // 本文件与 main.go 同属 package main,但所有模板均为独立函数, // 不与 main.go 的 main() 冲突。直接在 main.go 里调用任一 demoX() 即可验证。 // // ───────────────────────── 场景索引 ───────────────────────── // demo1 EOF 多组,每组两个数求和 fmt.Scan // demo2 先读组数 t,再 t 组每组两个数 fmt.Scan // demo3 特殊值结束(0 0 停) fmt.Scan // demo4 先读 n 再读 n 个元素(行布局无关) fmt.Scan ★最省心 // demo5 逐行读 + 行内不定个数求和 bufio.Scanner // demo6 先 n 再 n 个元素(Scanner 鲁棒版) bufio.Scanner // demo7 t 组,每组先 n 再 n 个数 bufio.Scanner // demo8 大数据量:Scanner 读 + Writer 输出 bufio 双向缓冲 // ───────────────────────────────────────────────────────────── import ( "bufio" "fmt" "os" "strconv" "strings" ) // ──────────────────────── fmt.Scan 系列 ──────────────────────── // demo1: 多组数据,每组一行两个整数,读到 EOF 结束。 // 输入示例: "1 2\n3 4\n5 7\n" → 3 7 12 // 关键:fmt.Scan 把所有空白符(空格/换行/tab)当分隔符,无视行边界。 // n 是成功填入的变量个数;n==0 表示 EOF。 func demo1() { var a, b int for { n, _ := fmt.Scan(&a, &b) if n == 0 { break } fmt.Println(a + b) } } // demo2: 第一行组数 t,接下来 t 行每行两个整数。 // 输入示例: "2\n1 2\n3 4\n" → 3 7 func demo2() { var t, a, b int fmt.Scan(&t) for i := 0; i < t; i++ { fmt.Scan(&a, &b) fmt.Println(a + b) } } // demo3: 多组数据,读到 0 0 结束(特殊哨兵)。 // 输入示例: "1 2\n3 4\n0 0\n" → 3 7 // 关键:判停条件由题目给定,与 EOF 无关。仍用 fmt.Scan 读取。 func demo3() { var a, b int for { fmt.Scan(&a, &b) if a == 0 && b == 0 { break } fmt.Println(a + b) } } // demo4 ★最省心模板:先读 n,再读 n 个元素。行布局无关(挤一行/每元素一行/乱排都行)。 // 输入1 "5 1 2 3 4 5" 输入2 "5\n1 2 3 4 5" 输入3 "5\n1\n2\n3\n4\n5" 全部 → [1 2 3 4 5] // 关键:fmt.Scan 无视行边界,只按空白切分。naive Scanner 写法在"n 与元素挤一行"时会崩,见 demo6 对比。 func demo4() { var n int fmt.Scan(&n) a := make([]int, n) for i := 0; i < n; i++ { fmt.Scan(&a[i]) } fmt.Println(a) } // ──────────────────────── bufio.Scanner 系列 ──────────────────────── // newScanner 返回带 1MB 缓冲的 Scanner,避免单行过长触发默认 64KB 上限。 func newScanner() *bufio.Scanner { sc := bufio.NewScanner(os.Stdin) sc.Buffer(make([]byte, 1024*1024), 1024*1024) return sc } // demo5: 多组数据,每行不定个数整数求和。逐行读 + strings.Fields 切分。 // 输入示例: "1 2 3\n4 5\n6 7 8 9\n" → 6 9 30 // 关键:strings.Fields 按任意空白符切,自动跳过连续空白,不会产生空元素。 // 优先用 Fields 而非 strings.Split(s, " ")——后者遇连续空格会产生空字符串。 func demo5() { sc := newScanner() for sc.Scan() { line := sc.Text() if line == "" { continue } var sum int for _, s := range strings.Fields(line) { v, _ := strconv.Atoi(s) sum += v } fmt.Println(sum) } } // demo6: 先读 n 再读 n 个元素(Scanner 鲁棒版)。兼容"n 与元素挤一行"的情况。 // 关键:naive 写法(sc.Scan 一行当 n 去解析)在 "5 1 2 3 4 5" 上会 Atoi 失败返回 0。 // 鲁棒做法:把第一行用 Fields 切开,第一个 token 是 n,余下的直接进数组,不够再读后续行。 func demo6() { sc := newScanner() sc.Scan() tokens := strings.Fields(sc.Text()) n, _ := strconv.Atoi(tokens[0]) a := make([]int, 0, n) for _, s := range tokens[1:] { v, _ := strconv.Atoi(s) a = append(a, v) } for len(a) < n && sc.Scan() { for _, s := range strings.Fields(sc.Text()) { v, _ := strconv.Atoi(s) a = append(a, v) } } fmt.Println(a) } // demo7: t 组,每组先读 n 再读 n 个整数求和。 // 输入示例: "2\n3 1 2 3\n2 4 5\n" → 6 9 // 组合模式:外层组数用 Scan,内层元素用 Scanner 按行 + Fields 收集。 func demo7() { sc := newScanner() sc.Scan() t, _ := strconv.Atoi(strings.TrimSpace(sc.Text())) for i := 0; i < t; i++ { sc.Scan() tokens := strings.Fields(sc.Text()) n, _ := strconv.Atoi(tokens[0]) a := make([]int, 0, n) for _, s := range tokens[1:] { v, _ := strconv.Atoi(s) a = append(a, v) } for len(a) < n && sc.Scan() { for _, s := range strings.Fields(sc.Text()) { v, _ := strconv.Atoi(s) a = append(a, v) } } sum := 0 for _, v := range a { sum += v } fmt.Println(sum) } } // ──────────────────────── 大数据量模板 ──────────────────────── // demo8: 大数据量(>10^5 行)。Scanner 读 + Writer 输出,双向缓冲。 // 输入示例: 大量行,每行一个整数,输出每个的 2 倍。 // 关键:fmt.Print 系列在 10^5 级输出时会明显变慢;bufio.NewWriter + Flush 提速一个量级。 // defer w.Flush() 必须有,否则缓冲区内容不会写出。 func demo8() { sc := newScanner() w := bufio.NewWriter(os.Stdout) defer w.Flush() for sc.Scan() { line := sc.Text() if line == "" { continue } v, _ := strconv.Atoi(strings.TrimSpace(line)) fmt.Fprintln(w, v*2) } } // ──────────────────────── 坑速查 ──────────────────────── // 1. fmt.Scan 读不全时,未填的变量保留上轮值(不会清零)。 // → 判停用 n < 预期个数,别信任未读到的变量。 // 2. naive Scanner(整行 Atoi 当 n)在"n 与元素挤一行"时返回 n=0。 // → 用 strings.Fields 切开第一行(见 demo6)。 // 3. strings.Split(s, " ") 遇连续空格产生空字符串元素。 // → 优先 strings.Fields(s)。 // 4. bufio.Scanner 默认 64KB 上限,超长行会读不全。 // → sc.Buffer(make([]byte, 1024*1024), 1024*1024)。 // 5. bufio.NewWriter 必须 Flush,否则输出丢失。 // → defer w.Flush()。 // 6. fmt.Scanln 遇换行即停,不跨行;fmt.Scan 无视行边界跨行连读。 // → ACM 默认用 fmt.Scan。