Logo
Section 1 / 7

构建一个基本的词频统计工具

学习如何创建一个简单的字数统计工具。

导入

构建一个基本的词频统计工具

学习如何创建一个简单的字数统计工具。

无论我们是想自动化任务、分析数据、解析日志、与网络服务通信,还是满足其他需求,编写自己的命令行工具可能是最快且最有趣的方式。Go 是一种现代编程语言,它结合了编译型语言的可靠性与动态类型语言的易用性和高效性:在动态类型中,变量的类型在运行时确定。Python 就是一个动态类型语言的例子。换句话说,变量的类型在其生命周期内可以发生变化。这使得编写跨平台的命令行应用程序更加容易,同时提供了确保这些工具设计良好并经过充分测试所需的功能。

...

在深入研究读写文件、解析数据文件以及通过网络通信等更复杂的程序之前,我们将先创建一个单词计数程序,以了解如何使用 Go 构建和测试命令行应用程序。我们将从基础实现开始,逐步添加功能,并在过程中探索测试驱动开发的方法。完成之后,我们将拥有一个可用的单词计数程序,并对如何构建更复杂的应用程序有更深入的理解。

在整个课程中,我们还将开发其他 CLI 应用程序,以探索更多高级概念。

基础单词计数器应用

让我们创建一个工具,通过标准输入(STDIN)接收文本,统计其中的单词数量或行数。默认情况下,该工具统计单词数量;如果接收到 -l 标志,则改为统计行数。

我们首先实现基本版本。此版本从 STDIN 读取数据并显示单词数量。虽然后续会添加更多功能,但这个初始版本将帮助我们熟悉基于 Go 的命令行应用程序的代码结构。

在开始编写单词计数器代码之前,我们先设置项目目录。

Go 程序由 包 组成。一个包包含一个或多个 Go 源代码文件,其代码可组合为可执行程序或库。

从 Go 1.11 开始,我们可以将一个或多个包组合成 Go 模块。模块是 Go 的新标准,用于将相关包组合成一个可统一版本管理的单元。模块使我们的 Go 应用程序能够实现一致的依赖管理。有关 Go 模块的更多信息,请参考官方 Wiki 页面。

初始化 Go 模块

为我们的项目初始化一个新的 Go 模块:

1go mod init usercode/firstProgram/wc

在 Go 中,通过定义名为 main 的包并包含一个名为 main() 的函数来创建可执行程序。该函数不接受参数,也不返回值。它是程序的入口点:

1package main 2func main() { 3 main contents 4}

虽然不是强制要求,但按照惯例,main 包通常定义在名为 main.go 的文件中。本课程中我们将遵循这一惯例。


Course Image

## 代码示例文件路径 >**注意:** 为简洁起见,代码示例路径省略了根目录 `/usercode/`。例如,在以下代码示例中,代码路径从 `firstProgram/wc` 开始。

main.go 文件

我们首先在文件顶部添加 package main 的定义:

firstProgam/wc/main.go

1package main

接下来,我们添加 import 部分,引入我们将用于从 STDIN 读取数据和输出结果的库:

firstProgam/wc/main.go

1import ( 2 "bufio" 3 "fmt" 4 "io" 5 "os" 6)

本工具导入了以下内容:

  • bufio 包:用于读取文本。
  • fmt 包:用于打印格式化输出。
  • io 包:提供 io.Reader 接口。
  • os 包:用于访问操作系统资源。

我们的单词计数器将包含两个函数:main() 和 count()。main() 函数是程序的起点。所有要编译为可执行文件的 Go 程序都必须包含此函数。我们通过在 main.go 文件中添加以下代码来创建该函数。此函数将调用 count() 函数,并使用 fmt.Println() 打印其返回值:

firstProgam/wc/main.go

1func main() { 2 // 调用 count 函数,统计从标准输入接收到的单词数量,并打印结果 3 fmt.Println(count(os.Stdin)) 4}

接下来,我们定义 count() 函数,该函数负责实际的单词计数。此函数接收一个参数:io.Reader 接口。目前可以将 io.Reader 理解为任何可以从中读取数据的 Go 类型。在此场景中,函数将接收 STDIN 的内容进行处理:

firstProgam/wc/main.go

1func count(r io.Reader) int { 2 // 使用扫描器从 Reader(如文件)中读取文本 3 scanner := bufio.NewScanner(r) 4 // 设置扫描器的分割方式为按单词(默认是按行分割) 5 scanner.Split(bufio.ScanWords) 6 // 定义计数器 7 wc := 0 8 // 每扫描到一个单词,计数器加一 9 for scanner.Scan() { 10 wc++ 11 } 12 // 返回总数 13 return wc 14}

count() 函数使用 bufio 包中的 NewScanner() 函数创建一个新的扫描器。扫描器是一种方便地读取以空格或换行符分隔的数据的方式。默认情况下,扫描器按行读取数据,因此我们通过将扫描器的 Split() 方法设为 bufio.ScanWords(),使其改为按单词读取。然后我们定义一个变量 wc 来保存单词计数,并通过循环遍历每个标记(token),每次调用 scanner.Scan() 时将计数器加 1。最后返回单词总数。

为了简化说明,本示例中忽略了扫描过程中可能产生的错误。但在实际代码中,我们始终会检查错误。

我们已经完成了单词计数工具的基础实现。接下来,我们将编写测试,以确保该实现按预期工作。

简介

测试基本词频统计器