Go语言找出重复行
发布时间:2020-12-16 09:36:55 所属栏目:大数据 来源:网络整理
导读:用于文件复制、打
用于文件复制、打印、检索、排序、统计的程序通常有一个相似的结构:在输入接口上循环读取,然后对每一个元素进行一些计算,在运行时或者在最后输出结果,下面我们通过三个版本的 dup 程序,来演示如何使用Go语言来找出输入内容中重复的行,它受 UNIX 的 uniq 命令启发来找到相邻的重复行。 第一个版本的 dup1 程序输出标准输入中出现次数大于 1 的行,前面是次数,这个程序引入 if 语句、map 类型和 bufio 包。 // dup1 输出标准输入中出现次数大于1的行,前面是次数 package main import ( "bufio" "fmt" "os" ) func main() { counts := make(map[string]int) input := bufio.NewScanner(os.Stdin) for input.Scan() { counts[input.Text()]++ } //注意:忽略 input.Err() 中可能的错误 for line,n := range counts { if n > 1 { fmt.Printf("%dt%sn",n,line) } } }像 for 一样,if 语句中的条件部分也从不放在小括号里面,但是程序体中需要用到大括号,这里还可以有一个可选的 else 部分,当条件为 false 的时候执行。 map 存储一个键/值对集合,并且提供常量时间的操作来存储、获取或测试集合中的某个元素,键可以是其值能够进行相等(==)比较的任意类型,字符串是最常见的例子,值可以是任意类型,这个例子中,键的类型是字符串,值是 int,内置的函数 make 可以用来新建 map。 每次 dup 从输入读取一行内容,这一行就作为 map 中的键,对应的值递增 1。语句? counts[input.Text()]++ ?等价于下面的两个语句:
line := input.Text() 为了输出结果,我们使用基于 range 的 for 循环,这次在 map 类型的 counts 变量上遍历,每次迭代输出两个结果分别是 map 里面一个元素对应的键和值,map 里面键的迭代顺序通常是随机的,每次运行都不一致,这是有意设计的,以防止程序依赖某种特定的序列,此处不对排序做任何保证。 下面讨论 bufio 包,使用它可以简便、高效地处理输入和输出,其中一个最有用的特性是称为扫描器(Scanner)的类型,它可以读取输入,以行或者单词为单位断开,这是处理以行为单位输入内容的最简单方式。 程序使用短变量的声明方式,新建一个 bufio.Scanner 类型 input 变量: input := bufio.NewScarmer(os.Stdin) 扫描器从程序的标准输入进行读取。每一次调用 input.Scan() 读取下一行,并且将结尾的换行符去掉,通过调用 input.Text() 来获取读到的内容,Scan 函数在读到新行的时候返回 true,在没有更多内容的时候返回 false。像C语言或其他语言中的 printf 一样,函数 fmt.Printf 从一个表达式列表生成格式化的输出,它的第一个参数是格式化指示字符串,由它指定其他参数如何格式化,每一个参数的格式是一个转义字符、一个百分号加一个字符,例如:%d 将一个整数格式化为十进制的形式,%s 把参数展开为字符串变量的值。 Printf 函数有超过 10 个这样的转义字符,Go 程序员称为 verb,下表列出了一些常用的转义字符:
程序 dup1 中的格式化字符串还包含一个制表符 t 和一个换行符 n,字符串字面量可以包含类似转义序列(escape sequence)来表示不可见字符,Printf 默认不换行。 按照约定,诸如 log.Printf 和 fmt.Errorf 之类的格式化函数以 f 结尾,使用和 fmt.Printf 相同的格式化规则,而那些以 ln 结尾的函数(如 Println)则使用 %v 的方式来格式化参数,并在最后追加换行符。 许多程序既可以像 dup 一样从标准输入进行读取,也可以从具体的文件读取,下一个版本的 dup 程序可以从标准输入或一个文件列表进行读取,使用 os.Open 函数来逐个打开: // dup2 打印输入中多次出现的行的个数和文本 //它从 stdin 或指定的文件列表读取 package main import ( ??? "bufio" ??? "fmt" ??? "os" ) func main() { ??? counts := make(map[string]int) ??? files := os.Args[1:] ??? if len(files) == 0 { ??????? countLines(os.Stdin,counts) ??? } else { ??????? for _,arg := range files { ??????????? f,err := os.Open(arg) ??????????? if err != nil { ??????????????? fmt.Fprintf(os.Stderr,"dup2: %vn",err) ??????????????? continue ??????????? } ??????????? countLines(f,counts) ??????????? f.Close() ??????? } ??? } ??? for line,n := range counts { ??????? if n > 1 { ??????????? fmt.Printf("%dt%sn",line) ??????? } ??? } } func countLines(f *os.File,counts map[string]int) { ??? input := bufio.NewScanner(f) ??? for input.Scan() { ??????? counts[input.Text()]++ ??? } ??? //注意:忽略 input.Err() 中可能的错误 }函数 os.Open 返回两个值,第一个是打开的文件(*os.File),该文件随后被 Scanner 读取。 第二个返回值是一个内置的 error 类型的值,如果 err 等于 nil,则表示文件成功打开,文件在被读到结尾的时候,使用 Close 函数关闭文件,然后释放相应的资源(内存等)。 另一方面,如果 err 不等于 nil,说明出错了,这时,error 的值为描述错误原因,简单的错误处理是使用 Fprintf 和 %v 在标准错误流上输出一条消息,%v 可以使用默认格式显示任意类型的值;错误处理后,dup 开始处理下一个文件;continue 语句让循环进入下一个迭代。 为了保持示例代码简短,这里对错误处理有意进行了一定程度的忽略。很明显,必须检查 os.Open 返回的错误;但是,我们忽略了使用 input.Scan 读取文件的过程中出现概率很小的错误。我们将标记所跳过的错误检查。 值得注意的是,对 countLines 的调用出现在其声明之前。函数和其他包级别的实体可以以任意次序声明。 map 是一个使用 make 创建的数据结构的引用。当一个 map 被传递给一个函数时,函数接收到这个引用的副本,所以被调用函数中对于 map 数据结构中的改变对函数调用者使用的 map 引用也是可见的。在示例中,countLines 函数在 counts map 中插入的值,在 main 函数中也是可见的。 这个版本的 dup 使用“流式”模式读取输入,然后按需拆分为行,这样原理上这些程序可以处理海量的输入。一个可选的方式是一次读取整个输入到大块内存,一次性地分割所有行,然后处理这些行。接下去的版本 dup3 将以这种方式处理。这里引入一个 ReadFile 函数(从 io/ioutil 包),它读取整个命名文件的内容,还引入一个 strings.Split 函数,它将一个字符串分割为一个由子串组成的 slice。(Split 是前面介绍过的 strings.Join 的反操作。) 我们在某种程度上简化了 dup3:第一,它仅读取指定的文件,而非标准输入,因为 ReadFile 需要一个文件名作为参数;第二,我们将统计行数的工作放回 main 函数中,因为它当前仅在一处用到。 package main import ( "fmt" "io/ioutil" "os" "strings" ) func main() { counts := make(map[string]int) for _,filename := range os.Args[1:] { data,err := ioutil.ReadFile(filename) if err != nil { fmt.Fprintf(os.Stderr,"dup3: %vn",err) continue } for _,line := range strings.Split(string(data),"r") { counts[line]++ } } for line,line) } } }ReadFile 函数返回一个可以转化成字符串的字节 slice,这样它可以被 strings.Split 分割。 实际上,bufio.Scanner、ioutil.ReadFile 以及 ioutil.WriteFile 使用 *os.File 中的 Read 和 Write 方法,但是大多数程序员很少需要直接访问底层的例程。像 bufio 和 io/ioutil 包中上层的方法更易使用。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |