diff --git a/input-scan-cheatsheet.go b/input-scan-cheatsheet.go new file mode 100644 index 0000000..5525b3c --- /dev/null +++ b/input-scan-cheatsheet.go @@ -0,0 +1,197 @@ +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。 \ No newline at end of file diff --git a/main.go b/main.go new file mode 100644 index 0000000..0708ce1 --- /dev/null +++ b/main.go @@ -0,0 +1,4 @@ +package main + +func main() { +} \ No newline at end of file diff --git a/sort_test.go b/sort_test.go new file mode 100644 index 0000000..7320878 --- /dev/null +++ b/sort_test.go @@ -0,0 +1,287 @@ +package main + +import ( + "fmt" + "testing" +) + +// QuickSortHoare 使用 Hoare 双指针分区:l 主动向右找 >= pivot,r 主动向左找 <= pivot, +// 两指针相遇时分区完成。取中间元素作 pivot 避免已排序/逆序退化; +// 内层用严格不等号,相等元素也让两指针停下交换,防止全相等数组死循环。 +func QuickSortHoare(nums []int) { + var helper func(start, end int) + helper = func(start, end int) { + if start >= end { + return + } + pivot := nums[start+(end-start)/2] // 取中间元素,避免已排序/逆序退化 + l, r := start, end + for { + for nums[l] < pivot { // l 主动向右找 >= pivot 的 + l++ + } + for nums[r] > pivot { // r 主动向左找 <= pivot 的 + r-- + } + if l >= r { // 两指针相遇/交叉,分区完成 + break + } + nums[l], nums[r] = nums[r], nums[l] + l++ + r-- // 各进一步,相等元素也会交换,防止全相等数组死循环 + } + helper(start, r) + helper(r+1, end) + } + helper(0, len(nums)-1) +} + +// QuickSortLomuto 使用 Lomuto 单向分区:pivot 放 nums[end],i 维护"<= pivot 区"的右边界+1, +// j 从左扫到 end-1,遇到 <= pivot 的就与 nums[i] 交换并 i++。 +// 循环结束后 swap(nums[i], nums[end]) 让 pivot 归位到 i,左半 <= pivot、右半 > pivot。 +func QuickSortLomuto(nums []int) { + var helper func(start, end int) + helper = func(start, end int) { + if start >= end { + return + } + pivot := nums[end] // 取最后一个作 pivot + i := start // i 指向"<= pivot 区"的右边界+1 + for j := start; j < end; j++ { + if nums[j] <= pivot { + nums[i], nums[j] = nums[j], nums[i] + i++ + } + } + nums[i], nums[end] = nums[end], nums[i] // 把 pivot 放到最终位置 i + helper(start, i-1) + helper(i+1, end) + } + helper(0, len(nums)-1) +} + +func TestQuickSort(t *testing.T) { + nums := []int{1, 2, 1, 2, 1, 2, 3, 1, 3, 2} + QuickSortHoare(nums) + fmt.Printf("Hoare: %+v\n", nums) + + nums2 := []int{1, 2, 1, 2, 1, 2, 3, 1, 3, 2} + QuickSortLomuto(nums2) + fmt.Printf("Lomuto: %+v\n", nums2) +} + +func BubbleSort(nums []int) { + for i := 0; i < len(nums); i++ { + swapped := false + for j := 0; j < len(nums)-1-i; j++ { + if nums[j] > nums[j+1] { + nums[j], nums[j+1] = nums[j+1], nums[j] + swapped = true + } + } + if !swapped { + break + } + } +} + +func TestBubbleSort(t *testing.T) { + nums := []int{1, 2, 1, 2, 1, 2, 3, 1, 3, 2} + BubbleSort(nums) + fmt.Printf("TestBubbleSort: %+v\n", nums) +} + +// SelectionSort 选择排序:每轮在 [i, n) 找最小值,与 nums[i] 交换。 +// 交换次数少(每轮最多一次),但比较次数固定 O(n²),不稳定。 +func SelectionSort(nums []int) { + for i := 0; i < len(nums); i++ { + minIdx := i + for j := i; j < len(nums); j++ { + if nums[j] < nums[minIdx] { + minIdx = j + } + } + nums[i], nums[minIdx] = nums[minIdx], nums[i] + } +} + +// InsertionSort 插入排序:把 nums[i] 往前插到 [0, i) 的正确位置。 +// 近乎有序时接近 O(n);是很多高级排序在小数组时的收尾段。稳定。 +func InsertionSort(nums []int) { + // for i := 1; i < len(nums); i++ { + // for j := i; j > 0; j-- { + // if nums[j] < nums[j-1] { + // nums[j], nums[j-1] = nums[j-1], nums[j] + // } + // } + // } + + for i := 1; i < len(nums); i++ { + key := nums[i] + j := i + for j > 0 && nums[j-1] > key { + nums[j] = nums[j-1] // 往右挪一格 + j-- + } + nums[j] = key // 落到正确位置 + } +} + +// MergeSort 归并排序:分治——左半排好 + 右半排好,再合并。 +// 稳定,最坏 O(n log n),需 O(n) 额外空间。合并用双指针。 +func MergeSort(nums []int) { + tmp := make([]int, len(nums)) // 全局临时数组,只分配一次 + var helper func(start, end int) + helper = func(start, end int) { + if start >= end { + return + } + mid := start + (end-start)/2 + helper(start, mid) + helper(mid+1, end) + // merge [start,mid] 和 [mid+1,end] → tmp → 拷回 nums + p1, p2, k := start, mid+1, start + for p1 <= mid && p2 <= end { + if nums[p1] <= nums[p2] { + tmp[k] = nums[p1] + p1++ + } else { + tmp[k] = nums[p2] + p2++ + } + k++ + } + for p1 <= mid { + tmp[k] = nums[p1] + p1++ + k++ + } + for p2 <= end { + tmp[k] = nums[p2] + p2++ + k++ + } + copy(nums[start:end+1], tmp[start:end+1]) // 关键:拷回原切片 + } + helper(0, len(nums)-1) +} + +// HeapSort 堆排序:建大顶堆,反复把堆顶(最大值)与末尾交换并下沉调整。 +// 原地、最坏 O(n log n),不稳定。top-K / 优先队列的基础。 +func HeapSort(nums []int) { + n := len(nums) + if n <= 1 { + return + } + // siftDown:在 [0, end] 范围内把 nums[i] 下沉到正确位置 + var siftDown func(i, end int) + siftDown = func(i, end int) { + for 2*i+1 <= end { + l, r := 2*i+1, 2*i+2 + largest := i + if l <= end && nums[l] > nums[largest] { + largest = l + } + if r <= end && nums[r] > nums[largest] { + largest = r + } + if largest == i { + break + } + nums[i], nums[largest] = nums[largest], nums[i] + i = largest + } + } + // 阶段 1:建大顶堆,从最后一个父节点往前 + for i := n/2 - 1; i >= 0; i-- { + siftDown(i, n-1) + } + // 阶段 2:反复把堆顶(最大)与末尾交换,堆范围 -1,修复堆 + for end := n - 1; end > 0; end-- { + nums[0], nums[end] = nums[end], nums[0] + siftDown(0, end-1) // 注意:堆范围是 [0, end-1],排除已归位的 nums[end] + } +} + +// ShellSort 希尔排序:按递减步长分组做插入排序,最后步长为 1 退化为插入排序。 +// 平均约 O(n^1.3),非稳定。代码短,比插入排序在乱序数据上快很多。 +func ShellSort(nums []int) { + // TODO: 你来写 +} + +// CountingSort 计数排序:元素为 [0, k] 整数,统计频次后前缀和定位。 +// O(n+k),k=值域大小。非比较,稳定版本需倒序填充。 +func CountingSort(nums []int) { + // TODO: 你来写(假设元素均 >= 0) +} + +// BucketSort 桶排序:按值把元素分到 n 个桶,每桶用 InsertionSort 排好,再依次写回原数组。 +// 平均 O(n+k),k=桶数;最坏 O(n²)(全挤进一个桶,退化成插入排序)。 +// 稳定(InsertionSort 稳定)。需 min/max 决定桶映射区间。 +func BucketSort(nums []int) { + n := len(nums) + if n <= 1 { + return + } + // 找值域 + minVal, maxVal := nums[0], nums[0] + for _, v := range nums { + if v < minVal { + minVal = v + } + if v > maxVal { + maxVal = v + } + } + if minVal == maxVal { + return // 全相等,无需排 + } + // 桶数取 n;idx = (v-minVal) * n / (maxVal-minVal+1),分母 +1 保证 maxVal 不越界 + buckets := make([][]int, n) + for _, v := range nums { + idx := (v - minVal) * n / (maxVal - minVal + 1) + buckets[idx] = append(buckets[idx], v) + } + // 每桶内部插入排序(稳定),再依次拷回原数组 + k := 0 + for _, b := range buckets { + InsertionSort(b) + copy(nums[k:], b) + k += len(b) + } +} + +func TestSortAlgos(t *testing.T) { + cases := [][]int{ + {1, 2, 1, 2, 1, 2, 3, 1, 3, 2}, + {3, 2, 1}, + {5, 4, 3, 2, 1}, + {2, 1}, + {1, 3, 2, 3, 1}, + {10, 9, 8, 7, 6, 5, 4, 3, 2, 1}, + {1}, + {}, + {2, 2, 2, 2}, + {3, 3, 3, 1, 1, 1, 2, 2, 2}, + } + sorters := []struct { + name string + fn func([]int) + }{ + // {"SelectionSort", SelectionSort}, + // {"InsertionSort", InsertionSort}, + // {"MergeSort", MergeSort}, + {"HeapSort", HeapSort}, + {"BucketSort", BucketSort}, + // {"ShellSort", ShellSort}, + // {"CountingSort", CountingSort}, + } + for _, s := range sorters { + for _, c := range cases { + got := append([]int(nil), c...) + s.fn(got) + fmt.Printf("%s in=%v got=%v\n", s.name, c, got) + } + fmt.Println() + } +} diff --git a/top-100-liked/73/leetcode_test.go b/top-100-liked/73/leetcode_test.go new file mode 100644 index 0000000..a1f4616 --- /dev/null +++ b/top-100-liked/73/leetcode_test.go @@ -0,0 +1,48 @@ +package top100liked + +import ( + "math/rand" + "testing" +) + +// https://leetcode.cn/problems/kth-largest-element-in-an-array/?envType=study-plan-v2&envId=top-100-liked + +func findKthLargest(nums []int, k int) int { + n := len(nums) + target := n - k // 第 k 大 = 升序第 n-k 位 + var quickSelect func(l, r int) int + quickSelect = func(l, r int) int { + if l == r { + return nums[l] + } + pivot := nums[l+rand.Intn(r-l+1)] + // 三路分区:[l..lt-1] < pivot, [lt..gt] == pivot, [gt+1..r] > pivot + lt, gt, i := l, r, l + for i <= gt { + switch { + case nums[i] < pivot: + nums[lt], nums[i] = nums[i], nums[lt] + lt++ + i++ + case nums[i] > pivot: + nums[gt], nums[i] = nums[i], nums[gt] + gt-- + default: + i++ + } + } + switch { + case target < lt: + return quickSelect(l, lt-1) + case target > gt: + return quickSelect(gt+1, r) + default: + return pivot + } + } + return quickSelect(0, n-1) +} + +func Test(t *testing.T) { + findKthLargest([]int{3, 2, 1, 5, 6, 4}, 2) +} diff --git a/top-100-liked/74/leetcode_test.go b/top-100-liked/74/leetcode_test.go new file mode 100644 index 0000000..145ab7c --- /dev/null +++ b/top-100-liked/74/leetcode_test.go @@ -0,0 +1,183 @@ +package top100liked + +import ( + "reflect" + "sort" + "testing" +) + +// https://leetcode.cn/problems/top-k-frequent-elements/?envType=study-plan-v2&envId=top-100-liked +// 347. 前 K 个高频元素 +// +// 给一个整数数组 nums 和整数 k,返回出现频率前 k 高的元素,顺序任意。 +// 进阶要求时间复杂度优于 O(n log n)。 +// +// 通用第一步:统计频率 map[int]int。下面三种解法都基于此。 + +// countFreq 统计每个元素出现次数。 +func countFreq(nums []int) map[int]int { + m := make(map[int]int) + for _, v := range nums { + m[v]++ + } + return m +} + +// 解法一:最小堆 — O(n log k) / O(n) +// 维护大小为 k 的最小堆,堆顶是当前堆内频率最小者。 +// 逐个把 (元素, 频率) 入堆;堆大小超过 k 时弹出堆顶,淘汰频率最小的。 +// 遍历结束,堆里剩下的 k 个即前 k 高频元素。 +// +// 实现要点(container/heap): +// - 定义 type entry struct{ val, cnt int} +// - 定义 type minHeap []entry,实现 heap.Interface +// - Less 按 cnt 升序(堆顶最小) +// - 遍历 m:heap.Push(h, e);len(*h) > k 时 heap.Pop(h) +// - 收集堆中剩余元素返回 +type entry struct { + val int + cnt int +} + +type minHeap struct { + data []entry +} + +// 上浮:新元素放末尾后,与父比较,比父小则交换向上 +// i 是需要 siftUp 的元素的 index +func (h *minHeap) siftUp(i int) { + for i > 0 { + p := (i - 1) / 2 + if h.data[p].cnt > h.data[i].cnt { + h.data[p], h.data[i] = h.data[i], h.data[p] + i = p + } else { + break + } + } +} + +// 下沉:从 i 开始,与较小子比较,比子大则交换向下。n 是有效堆大小 +func (h *minHeap) siftDown(i, n int) { + for { + l, r := 2*i+1, 2*i+2 + smallest := i + if l < n && h.data[l].cnt < h.data[smallest].cnt { + smallest = l + } + if r < n && h.data[r].cnt < h.data[smallest].cnt { + smallest = r + } + if smallest == i { + break + } + h.data[smallest], h.data[i] = h.data[i], h.data[smallest] + i = smallest + } +} + +// push:追加末尾 + 上浮 +func (h *minHeap) push(e entry) { + idx := len(h.data) + h.data = append(h.data, e) + h.siftUp(idx) +} + +// popMin 弹出堆顶(最小 cnt):用末尾覆盖堆顶,缩容,下沉 +func (h *minHeap) popMin() entry { + if len(h.data) == 0 { + return entry{} + } + ret := h.data[0] + h.data[0] = h.data[len(h.data)-1] + h.data = h.data[:len(h.data)-1] + if len(h.data) > 0 { + h.siftDown(0, len(h.data)) + } + return ret +} + +func (h *minHeap) len() int { return len(h.data) } + +func topKFrequentHeap(nums []int, k int) []int { + m := countFreq(nums) + h := minHeap{} + for v, c := range m { + h.push(entry{v, c}) + if h.len() > k { + h.popMin() + } + } + res := make([]int, 0, k) + for h.len() > 0 { + res = append(res, h.popMin().val) + } + return res +} + +// 解法二:桶排序 — O(n) 平均 / O(n) +// 频率取值范围 [0, n],开长度 n+1 的桶:buckets[f] 存所有频率为 f 的元素。 +// 从 buckets[n] 倒序向 buckets[1] 遍历,依次把桶内元素加入结果,凑够 k 个停止。 +// +// 实现要点: +// - buckets := make([][]int, len(nums)+1) +// - for val, cnt := range m { buckets[cnt] = append(buckets[cnt], val) } +// - for f := len(buckets)-1; f >= 0 && len(res) < k; f-- { res = append(res, buckets[f]...) } +func topKFrequentBucket(nums []int, k int) []int { + m := countFreq(nums) + buckets := make([][]int, len(nums)+1) + for v, c := range m { + buckets[c] = append(buckets[c], v) + } + res := make([]int, 0, k) + for f := len(buckets) - 1; f >= 0 && len(res) < k; f-- { + res = append(res, buckets[f]...) + } + return res +} + +// equalIgnoreOrder 忽略顺序比较两个 []int。 +func equalIgnoreOrder(a, b []int) bool { + if len(a) != len(b) { + return false + } + ca := append([]int(nil), a...) + cb := append([]int(nil), b...) + sort.Ints(ca) + sort.Ints(cb) + return reflect.DeepEqual(ca, cb) +} + +func TestTopKFrequent(t *testing.T) { + cases := []struct { + name string + nums []int + k int + want []int + }{ + {"basic", []int{1, 1, 1, 2, 2, 3}, 2, []int{1, 2}}, + {"single", []int{1}, 1, []int{1}}, + {"tie", []int{1, 2}, 2, []int{1, 2}}, // 频率相同,都可返回 + {"negative", []int{4, 1, -1, 2, -1, 2, 3}, 2, []int{-1, 2}}, + {"duplicate candidates", []int{3, 0, 1, 0}, 1, []int{0}}, + } + solvers := []struct { + name string + fn func([]int, int) []int + }{ + {"heap", topKFrequentHeap}, + {"bucket", topKFrequentBucket}, + } + for _, s := range solvers { + t.Run(s.name, func(t *testing.T) { + for _, c := range cases { + // 复制 nums 防止解法修改原切片 + nums := append([]int(nil), c.nums...) + got := s.fn(nums, c.k) + if !equalIgnoreOrder(got, c.want) { + t.Errorf("%s/%s: got %v, want %v", s.name, c.name, got, c.want) + } + } + }) + } +}