004-统计(一)

这一节目标是完成统计相同行出现的次数。题目来源于 gopl 一书。

举个例子,下面一段文本:

// input 文件
hello
Jack
Allen
hello
Allen
Allen

最终我们的程序应该能统计出 hello 出现 2 次,Jack 出现 1 次,Allen 出现 3 次。为了统一输出的格式,希望得到下面的结果:

2 hello
1 Jack
3 Allen

即第 1 列是出现的次数,第 2 列是文本内容。为了再增加一点难度,对于出现少于一次的就不输出了。最终结果应该像下面这样:

2 hello
3 Allen

当然了,打印的顺序无关紧要。

明确目标后,开始吧。

1. 思路

如果是 C 语言,可能不太好写是吧。如果是 C++,你可以使用 std::map<std::string, int> 这样的数据结构来做。但是我们需要使用 Go 语言啊,得使用 Go 语言提供的 map 这种数据结构。

另外 for 循环啊 if 这种也跑不了了。还记得上一节里学过的 for 循环吗?那和 C 语言里的 for 循环格式几乎一样。

为了方便分析,还是先看一下代码吧。

// demo01.go
package main

import (
    "bufio"
    "fmt"
    "os"
)

func main() {
    counts := make(map[string]int) // 初始化一个 map
    inputScanner := bufio.NewScanner(os.Stdin) // 创建一个 Scanner 对象

    for inputScanner.Scan() { // 这个 for 相当于 C 语言的 while
        counts[inputScanner.Text()]++
    }

    // key 是字符串,value 是该串出现的次数
    for key, value := range counts {
        if value > 1 {
            fmt.Printf("%3d %s\n", value, key)
        }
    }
}

2. 演示

新的程序路径是 gogo/src/gopl/tutorial/dup/demo01.go,来运行一下看看吧!

// input 文件就是文章最开始给的那一段文本
$ cat input | go run demo01.go


这里写图片描述
图1 运行结果

值得一提的是,这里我运行了 3 次,发现最后一次打印的顺序和前 2 次不一样。后面再解释。

3. 程序分析

3.1 go 语言里的 map

可以看到 go 语言声明 map 的方式很独特,如果是 C++,你肯定是这样声明 map<string, int>,但是 go 使用的方法是 map[string]int,没事没事,习惯就好。

方括号里的自然是 key 的类型了,方括号后面的是 int 表示的是 value 的类型。 另一点很奇怪的是,代码里为啥不直接这样写:

var counts map[string]int

不如就先改成这样,再来看看结果:


这里写图片描述
图2 不使用 make 后的结果

看到了吧,程序报错了,引发恐慌(panic)了都。具体原因是我们试图往一个『空』(nil) 的 map 中添加值。

在这里 counts 有点类似于『引用』的概念,你使用 var counts map[string]int 声明了一个指向了 nil 的变量。它有点类似于 Java 里的引用的概念。

// go
var counts map[string]int

// java
Map<String, int> counts = null;

在 go 语言里,数据类型分为四大类(简单了解):基础类型、复合类型、引用类型和接口类型(basic types, aggregate types, reference types and interface types)。类型系统将在后面详细介绍。

在这里,我们把 map 这种类型归属于『引用』类型。

好了,知道上面的解释后,我们就知道为什么使用 make 函数了,make 是 go 语言的内置函数,它可以创建一个不为 nilmap.

3.2 NewScanner 是何物

学习一门语言最好的方法就是查文档,这里推荐一个在线文档,可以查阅各种语言:http://devdocs.io/

从前面的程序可以看出来,NewScanner 应该是 bufio 包导出的一个函数。来查阅一下:


这里写图片描述
图3 NewScanner 函数

有同学会吐槽了,这是什么鬼。好吧,是有点操之过急。不过为了能理解本文的程序,还是作一下类比解释。

函数 NewScanner 接口一个类型为 io.Reader 的变量,在没有讲接口前,我们『姑且』认为它是一个抽象接口类(其实本来就是)。这个函数返回一个类型为 Scanner 对象的指针(牛逼了,go 语言也有指针的概念)。另外,我们『姑且』认为 Scanner 相当于 C++/Java 中 class 的概念,也就是说声明了一个类,class Scanner

好了,点到为止。再说一下 os.Stdin,它是 os.File 类型的指针,看起来它可能是实现了 io.Reader 这个接口的方法(没有学面向对象语言的同学我对不起你了)。os.Stdin 就是标准输入,和 C++ 的 std::cin 差不多。

再来看看 ScannerNewScanner 函数返回的结果就是 Scanner 对象的指针。在我们的程序里,我们使用了该对象的两个方法,一个是 Scan 方法,另一个是 Text 方法。看文档,可以看到『类』Scanner 声明方法的格式也很奇特,以 Scan 方法为例,在 C++ 是这样声明的 bool Scanner::Scan(),但是在 go 中是这样的:func (*Scanner) Scan() bool。后面还会详细介绍,这里就先了解一下。

  • Scanner::Scan

说的简单点,这个函数就是个迭代器,每运行一次,就读取一行到当前缓冲区中。如果所有行都读完了,这个函数返回 false.


这里写图片描述
图4 ScannerScan 方法

  • Scanner::Text

而这个函数,把当前缓冲区的数据返回


这里写图片描述
图5 ScannerText 方法

最终,有了下面这些代码。

for inputScanner.Scan() {
    counts[inputScanner.Text()]++
}

值得一说的是,counts 对于不存在于其中的 key,直接调用 counts[key]++ 不会出事吗?这是 go 语言很特殊的地方,对于不存在的 key 调用 counts[key]++,它会先将 counts[key]初始化为 value 类型的零值,在我们这里 value 的类型是 int,所以零值就是 0;接下来再执行 ++ 操作。

另外,在这里这个 for 循环,和 C 语言里的 while 循环用法是一样的。

3.3 第三种 for 循环

这种 for 循环只有在一些更高级的语言里才会出现,比如 C++ 的 for (auto e : list),还有 python 的 for e in list。这种 for 一般都称之为 range-based-for,即基于范围的 for 循环,它更加智能。

在 go 里也有这样的用法,就如代码中的那样:

for key, value := range counts {
     if value > 1 {
        fmt.Printf("%3d %s\n", value, key)
    }
}

在这里,range 是 go 语言的一个关键字,配合 for 来使用。每一次循环,都会返回一对 <key, value>,直到遍历完所有键值对。

还记得刚刚图 1 中的结果吗?程序每次运行,遍历的顺序可能都不一样。这是 go 有意而为之,就是防止你误以为遍历的结果是有顺序的,所以才加了随机特性。

3.4 if 语句

if 语句和 C 语言的一样,只是没有括号而已。

4. 关于代码风格

go 语言对代码风格采取了非常强硬的措施,什么左花括号是否必须另起一行的撕逼行为永远不会在 go 中出现。go 语言硬性限制左花括号必须紧跟在语句后面。

类似的还有导入包时,包导入路径必须要按照字母表顺序排列等。

严格的限制的好处就是防止了无意义的撕逼。

go 语言提供了格式化代码的工具。使用方法:

$ go fmt hello.go

5. 总结

进一步熟悉 go 代码,慢慢产生感觉。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值